母乳杯蓝牙改造:CAIIC-MCM 协议层(CLI/OTA)、设备/状态/升级页面、UI英文化、扫描不过滤、新应用名与图标
This commit is contained in:
parent
1bf7232929
commit
b4e8cafa61
@ -75,3 +75,18 @@
|
|||||||
version/sysinfo/bankinfo 显示、temp/fan(待固件)、本地 bin OTA 后 bankinfo 确认切 bank。
|
version/sysinfo/bankinfo 显示、temp/fan(待固件)、本地 bin OTA 后 bankinfo 确认切 bank。
|
||||||
- OTA 大数据量发送走 Write Without Response + 扇区级 ack 流控,若实测丢包导致
|
- OTA 大数据量发送走 Write Without Response + 扇区级 ack 流控,若实测丢包导致
|
||||||
status=2 重同步频繁,可在 OtaManager 扇区内加小窗口节流。
|
status=2 重同步频繁,可在 OtaManager 扇区内加小窗口节流。
|
||||||
|
|
||||||
|
## 后续调整(同日)
|
||||||
|
|
||||||
|
- **uts 编译修复**:`match()` 结果元素可空需先判空;uts 函数不能直接当 lambda 传递
|
||||||
|
(改为箭头函数包装);uvue 页面内函数需"定义在前、使用在后";`setTimeout` 不接受
|
||||||
|
async 回调(改 then 链);`java.io` 交互处需显式 `Int` 类型。
|
||||||
|
- **UI 全面英文化**:pages.json 导航/ tab(Device/Status/OTA/my/CLI Terminal)、
|
||||||
|
home/sysStatus/ota/my/CLI 终端全部文案与 toast、OtaManager 升级日志消息、
|
||||||
|
manifest 应用名 `smartAssister`、iOS 蓝牙用途英文描述。
|
||||||
|
- **扫描不过滤**:设备页去掉 `CAIIC-MCM` 名称前缀过滤,扫到的设备全部列出;
|
||||||
|
`BluetoothManager.startScan` 的 `showEmptyName` 改为 1(无广播名设备也返回,
|
||||||
|
列表中匿名设备显示 N/A)。
|
||||||
|
- **APP 图标**:新增 `static/appIcon.png`(1024×1024,蓝色渐变底 + 白色母乳杯 +
|
||||||
|
蓝牙徽标,脚本生成),manifest 的 Android 四档与 iOS appstore 图标均指向它;
|
||||||
|
图标与应用名需重打自定义基座并重装后生效。
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "smartassister",
|
"name": "smartAssister",
|
||||||
"appid": "__UNI__884AC41",
|
"appid": "__UNI__884AC41",
|
||||||
"description": "",
|
"description": "",
|
||||||
"versionName": "1.0.1",
|
"versionName": "1.0.1",
|
||||||
@ -46,10 +46,10 @@
|
|||||||
"distribute": {
|
"distribute": {
|
||||||
"modules": {},
|
"modules": {},
|
||||||
"icons": {
|
"icons": {
|
||||||
"hdpi": "/static/androidApp.png",
|
"hdpi": "/static/appIcon.png",
|
||||||
"xhdpi": "/static/androidApp.png",
|
"xhdpi": "/static/appIcon.png",
|
||||||
"xxhdpi": "/static/androidApp.png",
|
"xxhdpi": "/static/appIcon.png",
|
||||||
"xxxhdpi": "/static/androidApp.png"
|
"xxxhdpi": "/static/appIcon.png"
|
||||||
},
|
},
|
||||||
"splashScreens": {
|
"splashScreens": {
|
||||||
"default": {}
|
"default": {}
|
||||||
@ -69,12 +69,12 @@
|
|||||||
"uni-barcode-scanning": {}
|
"uni-barcode-scanning": {}
|
||||||
},
|
},
|
||||||
"icons": {
|
"icons": {
|
||||||
"appstore": "/static/androidApp.png"
|
"appstore": "/static/appIcon.png"
|
||||||
},
|
},
|
||||||
"splashScreens": {},
|
"splashScreens": {},
|
||||||
"privacyDescription": {
|
"privacyDescription": {
|
||||||
"NSBluetoothPeripheralUsageDescription": "请允许使用蓝牙功能。该功能用于:连接母乳杯设备,显示温度、风扇转速等运行参数,查询设备系统状态,并通过蓝牙为设备升级固件。",
|
"NSBluetoothPeripheralUsageDescription": "Please allow Bluetooth access. It is used to connect to the mother cup device: displaying runtime parameters such as temperature and fan speed, querying device system status, and upgrading the device firmware over Bluetooth.",
|
||||||
"NSBluetoothAlwaysUsageDescription": "请允许使用蓝牙功能。该功能用于:连接母乳杯设备,显示温度、风扇转速等运行参数,查询设备系统状态,并通过蓝牙为设备升级固件。",
|
"NSBluetoothAlwaysUsageDescription": "Please allow Bluetooth access. It is used to connect to the mother cup device: displaying runtime parameters such as temperature and fan speed, querying device system status, and upgrading the device firmware over Bluetooth.",
|
||||||
"NSLocationWhenInUseUsageDescription": "Please allow the use of location services to identify the position of charging stations. This app promises that this function will not be used for any other purposes.",
|
"NSLocationWhenInUseUsageDescription": "Please allow the use of location services to identify the position of charging stations. This app promises that this function will not be used for any other purposes.",
|
||||||
"NSLocalNetworkUsageDescription": "Please allow the use of network connectivity for communication between the app and the cloud service backend to enable charging-related features. This app promises that it will only be used to complete charging-related services with the cloud backend and will not be utilized for any other purposes.",
|
"NSLocalNetworkUsageDescription": "Please allow the use of network connectivity for communication between the app and the cloud service backend to enable charging-related features. This app promises that it will only be used to complete charging-related services with the cloud backend and will not be utilized for any other purposes.",
|
||||||
"NSCameraUsageDescription": "Please allow the use of the camera function. This function is solely used for scanning the QR codes on charging piles to obtain information corresponding to the QR codes. This app promises that the function will not be used for any other purposes."
|
"NSCameraUsageDescription": "Please allow the use of the camera function. This function is solely used for scanning the QR codes on charging piles to obtain information corresponding to the QR codes. This app promises that the function will not be used for any other purposes."
|
||||||
@ -94,4 +94,4 @@
|
|||||||
"mode": ""
|
"mode": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
pages.json
14
pages.json
@ -3,7 +3,7 @@
|
|||||||
{
|
{
|
||||||
"path": "pages/tabbar/home",
|
"path": "pages/tabbar/home",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "母乳杯设备",
|
"navigationBarTitleText": "Device",
|
||||||
"navigationBarBackgroundColor": "#3A6FA1",
|
"navigationBarBackgroundColor": "#3A6FA1",
|
||||||
"enablePullDownRefresh": true
|
"enablePullDownRefresh": true
|
||||||
}
|
}
|
||||||
@ -11,7 +11,7 @@
|
|||||||
{
|
{
|
||||||
"path": "pages/tabbar/sysStatus",
|
"path": "pages/tabbar/sysStatus",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "系统状态",
|
"navigationBarTitleText": "System Status",
|
||||||
"navigationBarBackgroundColor": "#3A6FA1",
|
"navigationBarBackgroundColor": "#3A6FA1",
|
||||||
"enablePullDownRefresh": true
|
"enablePullDownRefresh": true
|
||||||
}
|
}
|
||||||
@ -19,7 +19,7 @@
|
|||||||
{
|
{
|
||||||
"path": "pages/tabbar/ota",
|
"path": "pages/tabbar/ota",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "固件升级",
|
"navigationBarTitleText": "Firmware Upgrade",
|
||||||
"navigationBarBackgroundColor": "#3A6FA1"
|
"navigationBarBackgroundColor": "#3A6FA1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -63,7 +63,7 @@
|
|||||||
{
|
{
|
||||||
"path": "pages/bleTest/bluetoothOperation/bluetoothOperation",
|
"path": "pages/bleTest/bluetoothOperation/bluetoothOperation",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "CLI 调试终端"
|
"navigationBarTitleText": "CLI Terminal"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -75,19 +75,19 @@
|
|||||||
"list": [
|
"list": [
|
||||||
{
|
{
|
||||||
"pagePath": "pages/tabbar/home",
|
"pagePath": "pages/tabbar/home",
|
||||||
"text": "设备",
|
"text": "Device",
|
||||||
"iconPath": "/static/tabbar/homeDefault.png",
|
"iconPath": "/static/tabbar/homeDefault.png",
|
||||||
"selectedIconPath": "/static/tabbar/homeActive.png"
|
"selectedIconPath": "/static/tabbar/homeActive.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pagePath": "pages/tabbar/sysStatus",
|
"pagePath": "pages/tabbar/sysStatus",
|
||||||
"text": "状态",
|
"text": "Status",
|
||||||
"iconPath": "/static/tabbar/mapDefault.png",
|
"iconPath": "/static/tabbar/mapDefault.png",
|
||||||
"selectedIconPath": "/static/tabbar/mapActive.png"
|
"selectedIconPath": "/static/tabbar/mapActive.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pagePath": "pages/tabbar/ota",
|
"pagePath": "pages/tabbar/ota",
|
||||||
"text": "升级",
|
"text": "OTA",
|
||||||
"iconPath": "/static/tabbar/chargeDefault.png",
|
"iconPath": "/static/tabbar/chargeDefault.png",
|
||||||
"selectedIconPath": "/static/tabbar/chargeActive.png"
|
"selectedIconPath": "/static/tabbar/chargeActive.png"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
<!-- 底部:命令输入 + 按钮 -->
|
<!-- 底部:命令输入 + 按钮 -->
|
||||||
<view class="bottomPanel">
|
<view class="bottomPanel">
|
||||||
<view class="cmdRow">
|
<view class="cmdRow">
|
||||||
<input class="cmdInput" v-model="cmdText" placeholder="输入 CLI 命令,如 help / sysinfo" :disabled="!bleIsConnect" />
|
<input class="cmdInput" v-model="cmdText" placeholder="Enter CLI command, e.g. help / sysinfo" :disabled="!bleIsConnect" />
|
||||||
<button type="primary" size="mini" class="sendBtn" :disabled="!bleIsConnect || sending" @tap="onSendCmd">{{ sending ? '...' : 'send' }}</button>
|
<button type="primary" size="mini" class="sendBtn" :disabled="!bleIsConnect || sending" @tap="onSendCmd">{{ sending ? '...' : 'send' }}</button>
|
||||||
</view>
|
</view>
|
||||||
<view class="btnRow">
|
<view class="btnRow">
|
||||||
@ -147,9 +147,9 @@ async function onSendCmd() {
|
|||||||
if (r.status == 0) {
|
if (r.status == 0) {
|
||||||
pushMsg("rx", r.text.length > 0 ? r.text : "(ok)")
|
pushMsg("rx", r.text.length > 0 ? r.text : "(ok)")
|
||||||
} else if (r.status == 1) {
|
} else if (r.status == 1) {
|
||||||
pushMsg("message", "未知命令或行超长")
|
pushMsg("message", "Unknown command or line too long")
|
||||||
} else {
|
} else {
|
||||||
pushMsg("message", "命令失败 status=" + r.status + (r.text.length > 0 ? (", 已收: " + r.text) : ""))
|
pushMsg("message", "Command failed, status=" + r.status + (r.text.length > 0 ? (", received: " + r.text) : ""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,23 +8,23 @@
|
|||||||
<view class="card">
|
<view class="card">
|
||||||
<view class="statusRow">
|
<view class="statusRow">
|
||||||
<view class="statusDot" :class="connected ? 'dotOn' : 'dotOff'"></view>
|
<view class="statusDot" :class="connected ? 'dotOn' : 'dotOff'"></view>
|
||||||
<text class="statusText">{{ connected ? '已连接' : (connecting ? '连接中...' : '未连接') }}</text>
|
<text class="statusText">{{ connected ? 'Connected' : (connecting ? 'Connecting...' : 'Disconnected') }}</text>
|
||||||
<text class="deviceText" v-if="connected">{{ deviceName }} ({{ deviceMac }})</text>
|
<text class="deviceText" v-if="connected">{{ deviceName }} ({{ deviceMac }})</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="btnRow">
|
<view class="btnRow">
|
||||||
<button class="btn" size="mini" v-if="!connected && !connecting" @tap="onBleStarScan">{{ scanning ? '扫描中...' : '扫描设备' }}</button>
|
<button class="btn" size="mini" v-if="!connected && !connecting" @tap="onBleStarScan">{{ scanning ? 'Scanning...' : 'Scan' }}</button>
|
||||||
<button class="btn btnWarn" size="mini" v-if="connected" @tap="onDisconnect">断开连接</button>
|
<button class="btn btnWarn" size="mini" v-if="connected" @tap="onDisconnect">Disconnect</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 参数显示 -->
|
<!-- 参数显示 -->
|
||||||
<view class="card paramCard" v-if="connected">
|
<view class="card paramCard" v-if="connected">
|
||||||
<view class="paramItem">
|
<view class="paramItem">
|
||||||
<text class="paramLabel">温度</text>
|
<text class="paramLabel">Temperature</text>
|
||||||
<text class="paramValue">{{ temperature }}<text class="paramUnit"> ℃</text></text>
|
<text class="paramValue">{{ temperature }}<text class="paramUnit"> ℃</text></text>
|
||||||
</view>
|
</view>
|
||||||
<view class="paramItem">
|
<view class="paramItem">
|
||||||
<text class="paramLabel">风扇转速</text>
|
<text class="paramLabel">Fan Speed</text>
|
||||||
<text class="paramValue">{{ fanSpeed }}<text class="paramUnit"> RPM</text></text>
|
<text class="paramValue">{{ fanSpeed }}<text class="paramUnit"> RPM</text></text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -32,11 +32,11 @@
|
|||||||
<!-- 设备信息 -->
|
<!-- 设备信息 -->
|
||||||
<view class="card" v-if="connected">
|
<view class="card" v-if="connected">
|
||||||
<view class="infoRow">
|
<view class="infoRow">
|
||||||
<text class="infoLabel">固件版本</text>
|
<text class="infoLabel">Firmware</text>
|
||||||
<text class="infoValue">{{ fwVersion }}</text>
|
<text class="infoValue">{{ fwVersion }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="infoRow">
|
<view class="infoRow">
|
||||||
<text class="infoLabel">运行 Bank</text>
|
<text class="infoLabel">Bank</text>
|
||||||
<text class="infoValue">{{ bankInfo }}</text>
|
<text class="infoValue">{{ bankInfo }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -49,13 +49,13 @@
|
|||||||
<uni-icons type="paperclip" size="20"/>
|
<uni-icons type="paperclip" size="20"/>
|
||||||
</view>
|
</view>
|
||||||
<view class="rightContent">
|
<view class="rightContent">
|
||||||
<text>{{ item.device.name }}</text>
|
<text>{{ item.device.name ?? 'N/A' }}</text>
|
||||||
<text>{{ item.device.address }}</text>
|
<text>{{ item.device.address }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="!scanning && scanArray.length == 0" class="emptyBox">
|
<view v-if="!scanning && scanArray.length == 0" class="emptyBox">
|
||||||
<text class="emptyText">未发现 CAIIC-MCM 设备,点击"扫描设备"重试</text>
|
<text class="emptyText">No device found. Tap "Scan" to retry</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@ -166,7 +166,7 @@ function onBleStatueListener(state: number) {
|
|||||||
} else if (state == 10000) { // 连接失败
|
} else if (state == 10000) { // 连接失败
|
||||||
connecting.value = false
|
connecting.value = false
|
||||||
connected.value = false
|
connected.value = false
|
||||||
uni.showToast({ title: '连接失败', icon: 'error' })
|
uni.showToast({ title: 'Connection failed', icon: 'error' })
|
||||||
} else if (state == 10001) { // 已断开
|
} else if (state == 10001) { // 已断开
|
||||||
onDisConnectUi()
|
onDisConnectUi()
|
||||||
}
|
}
|
||||||
@ -188,17 +188,15 @@ function onBleStarScan() {
|
|||||||
Ble.startScan(scanMs, function(res: MyApiResult) {
|
Ble.startScan(scanMs, function(res: MyApiResult) {
|
||||||
if (res.type == 0) {
|
if (res.type == 0) {
|
||||||
const scan = res.data as BleScanResult
|
const scan = res.data as BleScanResult
|
||||||
const name = scan.device.name ?? ""
|
|
||||||
if (name.indexOf(DEVICE_NAME_FILTER) < 0) return // 只显示母乳杯设备
|
|
||||||
// 去重
|
// 去重
|
||||||
for (let i = 0; i < scanArray.value.length; i++) {
|
for (let i = 0; i < scanArray.value.length; i++) {
|
||||||
if (scanArray.value[i].device.address == scan.device.address) return
|
if (scanArray.value[i].device.address == scan.device.address) return
|
||||||
}
|
}
|
||||||
scanArray.value.push(scan)
|
scanArray.value.push(scan)
|
||||||
} else if (res.type == 10003) {
|
} else if (res.type == 10003) {
|
||||||
uni.showToast({ title: '无蓝牙权限', icon: 'none' })
|
uni.showToast({ title: 'No Bluetooth permission', icon: 'none' })
|
||||||
} else if (res.type == 10002) {
|
} else if (res.type == 10002) {
|
||||||
uni.showToast({ title: '蓝牙未开启', icon: 'none' })
|
uni.showToast({ title: 'Bluetooth is not turned on', icon: 'none' })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<!-- 后台服务器选择 -->
|
<!-- 后台服务器选择 -->
|
||||||
<view class="card server-card" @tap="onServerTap">
|
<view class="card server-card" @tap="onServerTap">
|
||||||
<text class="server-label">后台服务器</text>
|
<text class="server-label">Server</text>
|
||||||
<view class="server-value">
|
<view class="server-value">
|
||||||
<text class="server-name">{{currentServerName}}</text>
|
<text class="server-name">{{currentServerName}}</text>
|
||||||
<uni-icons type="bottom" size="16" color="#3A6FA1" />
|
<uni-icons type="bottom" size="16" color="#3A6FA1" />
|
||||||
@ -31,7 +31,7 @@
|
|||||||
<!-- CLI 调试终端 -->
|
<!-- CLI 调试终端 -->
|
||||||
<view class="cardBox" @tap="onBluetooth">
|
<view class="cardBox" @tap="onBluetooth">
|
||||||
<uni-icons type="link" size="20" class="input-icon" />
|
<uni-icons type="link" size="20" class="input-icon" />
|
||||||
<text class="textType">CLI 调试终端</text>
|
<text class="textType">CLI Terminal</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -80,7 +80,7 @@ function onServerTap() {
|
|||||||
setBaseUrl(SERVER_LIST[idx].url)
|
setBaseUrl(SERVER_LIST[idx].url)
|
||||||
currentServerName.value = SERVER_LIST[idx].name
|
currentServerName.value = SERVER_LIST[idx].name
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '已切换到: ' + SERVER_LIST[idx].name,
|
title: 'Switched to: ' + SERVER_LIST[idx].name,
|
||||||
icon: 'none',
|
icon: 'none',
|
||||||
duration: 2000
|
duration: 2000
|
||||||
})
|
})
|
||||||
|
|||||||
@ -5,42 +5,42 @@
|
|||||||
<view class="page">
|
<view class="page">
|
||||||
|
|
||||||
<view class="card" v-if="!connected">
|
<view class="card" v-if="!connected">
|
||||||
<text class="tipText">设备未连接,请先在"设备"页连接 CAIIC-MCM</text>
|
<text class="tipText">Not connected. Please connect a CAIIC-MCM device on the Device tab first</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<template v-if="connected">
|
<template v-if="connected">
|
||||||
<!-- 当前设备信息 -->
|
<!-- 当前设备信息 -->
|
||||||
<view class="card">
|
<view class="card">
|
||||||
<view class="infoRow">
|
<view class="infoRow">
|
||||||
<text class="infoLabel">当前固件版本</text>
|
<text class="infoLabel">Current Firmware</text>
|
||||||
<text class="infoValue">{{ curVersion }}</text>
|
<text class="infoValue">{{ curVersion }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="infoRow">
|
<view class="infoRow">
|
||||||
<text class="infoLabel">运行 Bank</text>
|
<text class="infoLabel">Bank</text>
|
||||||
<text class="infoValue">{{ bankInfo }}</text>
|
<text class="infoValue">{{ bankInfo }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 固件文件 -->
|
<!-- 固件文件 -->
|
||||||
<view class="card">
|
<view class="card">
|
||||||
<text class="sectionTitle">固件文件(Keil 产物 bin,≤224KB)</text>
|
<text class="sectionTitle">Firmware file (Keil bin, ≤224KB)</text>
|
||||||
<input class="input" v-model="filePath" placeholder="/sdcard/Download/embeddedSrc.bin" :disabled="upgrading" />
|
<input class="input" v-model="filePath" placeholder="/sdcard/Download/embeddedSrc.bin" :disabled="upgrading" />
|
||||||
<view class="btnRow">
|
<view class="btnRow">
|
||||||
<button class="btn" size="mini" :disabled="upgrading" @tap="onLoadFile">读取文件</button>
|
<button class="btn" size="mini" :disabled="upgrading" @tap="onLoadFile">Load File</button>
|
||||||
</view>
|
</view>
|
||||||
<view class="infoRow" v-if="fileLoaded">
|
<view class="infoRow" v-if="fileLoaded">
|
||||||
<text class="infoLabel">文件大小</text>
|
<text class="infoLabel">File Size</text>
|
||||||
<text class="infoValue">{{ fileSize }} 字节</text>
|
<text class="infoValue">{{ fileSize }} bytes</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="infoRow" v-if="fileLoaded">
|
<view class="infoRow" v-if="fileLoaded">
|
||||||
<text class="infoLabel">镜像 CRC32</text>
|
<text class="infoLabel">Image CRC32</text>
|
||||||
<text class="infoValue">0x{{ fileCrcHex }}</text>
|
<text class="infoValue">0x{{ fileCrcHex }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 目标版本 -->
|
<!-- 目标版本 -->
|
||||||
<view class="card">
|
<view class="card">
|
||||||
<text class="sectionTitle">目标版本号(如 1.00.02)</text>
|
<text class="sectionTitle">Target version (e.g. 1.00.02)</text>
|
||||||
<input class="input" v-model="versionText" placeholder="1.00.02" :disabled="upgrading" />
|
<input class="input" v-model="versionText" placeholder="1.00.02" :disabled="upgrading" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@ -51,15 +51,15 @@
|
|||||||
</view>
|
</view>
|
||||||
<text class="progressText">{{ progress }}%</text>
|
<text class="progressText">{{ progress }}%</text>
|
||||||
<view class="btnRow">
|
<view class="btnRow">
|
||||||
<button class="btn" size="mini" v-if="!upgrading" :disabled="!fileLoaded" @tap="onStartOta">开始升级</button>
|
<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">中止升级</button>
|
<button class="btn btnWarn" size="mini" v-if="upgrading" @tap="onAbortOta">Abort</button>
|
||||||
</view>
|
</view>
|
||||||
<text class="warnText" v-if="upgrading">升级中请勿断开连接或退出应用</text>
|
<text class="warnText" v-if="upgrading">Do not disconnect or exit during upgrade</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 日志 -->
|
<!-- 日志 -->
|
||||||
<view class="card" v-if="logs.length > 0">
|
<view class="card" v-if="logs.length > 0">
|
||||||
<text class="sectionTitle">升级日志</text>
|
<text class="sectionTitle">Upgrade Log</text>
|
||||||
<text class="logText" v-for="(log, index) in logs" :key="index">{{ log }}</text>
|
<text class="logText" v-for="(log, index) in logs" :key="index">{{ log }}</text>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@ -141,19 +141,19 @@ function onLoadFile() {
|
|||||||
const bytes = readFileBytes(filePath.value.trim())
|
const bytes = readFileBytes(filePath.value.trim())
|
||||||
if (bytes == null) {
|
if (bytes == null) {
|
||||||
fileLoaded.value = false
|
fileLoaded.value = false
|
||||||
uni.showToast({ title: '文件读取失败,请检查路径', icon: 'error' })
|
uni.showToast({ title: 'Failed to read file, check the path', icon: 'error' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (bytes.length > 0x38000) {
|
if (bytes.length > 0x38000) {
|
||||||
fileLoaded.value = false
|
fileLoaded.value = false
|
||||||
uni.showToast({ title: '文件超过 224KB 上限', icon: 'error' })
|
uni.showToast({ title: 'File exceeds the 224KB limit', icon: 'error' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
imageBytes = bytes
|
imageBytes = bytes
|
||||||
fileSize.value = bytes.length
|
fileSize.value = bytes.length
|
||||||
fileCrcHex.value = crc32Ieee(bytes).toString(16)
|
fileCrcHex.value = crc32Ieee(bytes).toString(16)
|
||||||
fileLoaded.value = true
|
fileLoaded.value = true
|
||||||
addLog("已加载文件: " + bytes.length + " 字节, CRC32=0x" + fileCrcHex.value)
|
addLog("File loaded: " + bytes.length + " bytes, CRC32=0x" + fileCrcHex.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================= 版本号解析 "1.00.02" -> 0x010002 =================
|
// ================= 版本号解析 "1.00.02" -> 0x010002 =================
|
||||||
@ -174,17 +174,17 @@ function parseVersion(v: string): number | null {
|
|||||||
async function onStartOta() {
|
async function onStartOta() {
|
||||||
const version = parseVersion(versionText.value)
|
const version = parseVersion(versionText.value)
|
||||||
if (version == null) {
|
if (version == null) {
|
||||||
uni.showToast({ title: '版本号格式错误(如 1.00.02)', icon: 'error' })
|
uni.showToast({ title: 'Invalid version format (e.g. 1.00.02)', icon: 'error' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!Ble.isConnected()) {
|
if (!Ble.isConnected()) {
|
||||||
uni.showToast({ title: '设备未连接', icon: 'error' })
|
uni.showToast({ title: 'Device not connected', icon: 'error' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
upgrading.value = true
|
upgrading.value = true
|
||||||
progress.value = 0
|
progress.value = 0
|
||||||
logs.value = []
|
logs.value = []
|
||||||
addLog("开始升级,目标版本 " + versionText.value)
|
addLog("Upgrade started, target version " + versionText.value)
|
||||||
const cbs: OtaCallbacks = {
|
const cbs: OtaCallbacks = {
|
||||||
onProgress: (percent: number) => {
|
onProgress: (percent: number) => {
|
||||||
progress.value = percent
|
progress.value = percent
|
||||||
@ -196,15 +196,15 @@ async function onStartOta() {
|
|||||||
const ok = await Ota.start(imageBytes, version, cbs)
|
const ok = await Ota.start(imageBytes, version, cbs)
|
||||||
upgrading.value = false
|
upgrading.value = false
|
||||||
if (ok) {
|
if (ok) {
|
||||||
uni.showToast({ title: '升级成功,设备将自动复位', icon: 'success' })
|
uni.showToast({ title: 'Upgrade succeeded, device will reboot', icon: 'success' })
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({ title: '升级失败', icon: 'error' })
|
uni.showToast({ title: 'Upgrade failed', icon: 'error' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onAbortOta() {
|
function onAbortOta() {
|
||||||
Ota.abort()
|
Ota.abort()
|
||||||
addLog("用户请求中止 ...")
|
addLog("Abort requested...")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================= 设备信息 =================
|
// ================= 设备信息 =================
|
||||||
@ -224,7 +224,7 @@ function onBleStatueListener(state: number) {
|
|||||||
if (state == 10000 || state == 10001) {
|
if (state == 10000 || state == 10001) {
|
||||||
connected.value = false
|
connected.value = false
|
||||||
if (upgrading.value) {
|
if (upgrading.value) {
|
||||||
addLog("连接断开,升级中止")
|
addLog("Disconnected, upgrade aborted")
|
||||||
upgrading.value = false
|
upgrading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,30 +5,30 @@
|
|||||||
<view class="page">
|
<view class="page">
|
||||||
|
|
||||||
<view class="card" v-if="!connected">
|
<view class="card" v-if="!connected">
|
||||||
<text class="tipText">设备未连接,请先在"设备"页连接 CAIIC-MCM</text>
|
<text class="tipText">Not connected. Please connect a CAIIC-MCM device on the Device tab first</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<template v-if="connected">
|
<template v-if="connected">
|
||||||
<!-- 概要 -->
|
<!-- 概要 -->
|
||||||
<view class="card">
|
<view class="card">
|
||||||
<view class="infoRow">
|
<view class="infoRow">
|
||||||
<text class="infoLabel">固件版本</text>
|
<text class="infoLabel">Firmware</text>
|
||||||
<text class="infoValue">{{ version }}</text>
|
<text class="infoValue">{{ version }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="infoRow">
|
<view class="infoRow">
|
||||||
<text class="infoLabel">Bank 信息</text>
|
<text class="infoLabel">Bank Info</text>
|
||||||
<text class="infoValue">{{ bankInfo }}</text>
|
<text class="infoValue">{{ bankInfo }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="btnRow">
|
<view class="btnRow">
|
||||||
<button class="btn" size="mini" :disabled="loading" @tap="refreshAll">
|
<button class="btn" size="mini" :disabled="loading" @tap="refreshAll">
|
||||||
{{ loading ? '查询中...' : '刷新' }}
|
{{ loading ? 'Loading...' : 'Refresh' }}
|
||||||
</button>
|
</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- sysinfo 控制台输出 -->
|
<!-- sysinfo 控制台输出 -->
|
||||||
<view class="card">
|
<view class="card">
|
||||||
<text class="consoleTitle">sysinfo(系统运行状态)</text>
|
<text class="consoleTitle">sysinfo (system runtime status)</text>
|
||||||
<text class="consoleText">{{ sysInfo }}</text>
|
<text class="consoleText">{{ sysInfo }}</text>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export function startScan(timeoutMs: number, cb: (res: MyApiResult) => void) {
|
|||||||
// ble().startScanBleDevice(timeoutMs, cb) // 旧版本
|
// ble().startScanBleDevice(timeoutMs, cb) // 旧版本
|
||||||
ble().onStartScanBle({
|
ble().onStartScanBle({
|
||||||
scantime:timeoutMs, // 超时时间 ms
|
scantime:timeoutMs, // 超时时间 ms
|
||||||
showEmptyName:0, // 0表示 不返回空的名称 1 表示可返回空名称蓝牙
|
showEmptyName:1, // 0表示 不返回空的名称 1 表示可返回空名称蓝牙
|
||||||
btNameFilter:"", // showEmptyName 为0有效 代表过滤相关的名字 为空表示不过滤
|
btNameFilter:"", // showEmptyName 为0有效 代表过滤相关的名字 为空表示不过滤
|
||||||
onScanResult:(cb)
|
onScanResult:(cb)
|
||||||
} as ScanPara)
|
} as ScanPara)
|
||||||
|
|||||||
@ -99,18 +99,18 @@ async function run(image: number[], version: number, cb: OtaCallbacks): Promise<
|
|||||||
pushU32LE(pb, total)
|
pushU32LE(pb, total)
|
||||||
pushU32LE(pb, crc)
|
pushU32LE(pb, crc)
|
||||||
pushU32LE(pb, version)
|
pushU32LE(pb, version)
|
||||||
cb.onState("发送 OTA_BEGIN(" + total + " 字节)...")
|
cb.onState("Sending OTA_BEGIN (" + total + " bytes)...")
|
||||||
if (!await sendFrame(TYPE_OTA_BEGIN, pb)) {
|
if (!await sendFrame(TYPE_OTA_BEGIN, pb)) {
|
||||||
cb.onState("OTA_BEGIN 发送失败")
|
cb.onState("Failed to send OTA_BEGIN")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
let ack = await waitRsp(TYPE_OTA_BEGIN, 5000)
|
let ack = await waitRsp(TYPE_OTA_BEGIN, 5000)
|
||||||
if (!ack.received) {
|
if (!ack.received) {
|
||||||
cb.onState(ack.status == -2 ? "升级已中止" : "等待 OTA_BEGIN 应答超时")
|
cb.onState(ack.status == -2 ? "Upgrade aborted" : "Timeout waiting for OTA_BEGIN ack")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (ack.status != 0) {
|
if (ack.status != 0) {
|
||||||
cb.onState("设备拒绝升级: " + statusText(ack.status))
|
cb.onState("Device rejected upgrade: " + statusText(ack.status))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ async function run(image: number[], version: number, cb: OtaCallbacks): Promise<
|
|||||||
let offset = 0
|
let offset = 0
|
||||||
while (offset < total) {
|
while (offset < total) {
|
||||||
if (aborted) {
|
if (aborted) {
|
||||||
cb.onState("升级已中止")
|
cb.onState("Upgrade aborted")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const sectorEnd = Math.min(offset + SECTOR_SIZE, total)
|
const sectorEnd = Math.min(offset + SECTOR_SIZE, total)
|
||||||
@ -131,7 +131,7 @@ async function run(image: number[], version: number, cb: OtaCallbacks): Promise<
|
|||||||
payload.push(image[pos + i])
|
payload.push(image[pos + i])
|
||||||
}
|
}
|
||||||
if (!await sendFrame(TYPE_OTA_DATA, payload)) {
|
if (!await sendFrame(TYPE_OTA_DATA, payload)) {
|
||||||
cb.onState("OTA_DATA 写入失败 offset=" + pos)
|
cb.onState("Failed to write OTA_DATA, offset=" + pos)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
pos += n
|
pos += n
|
||||||
@ -141,17 +141,17 @@ async function run(image: number[], version: number, cb: OtaCallbacks): Promise<
|
|||||||
// 满扇区:设备擦写完成后回 OTA_RSP(0x11)(擦写期间关中断数十 ms,超时给足)
|
// 满扇区:设备擦写完成后回 OTA_RSP(0x11)(擦写期间关中断数十 ms,超时给足)
|
||||||
ack = await waitRsp(TYPE_OTA_DATA, 8000)
|
ack = await waitRsp(TYPE_OTA_DATA, 8000)
|
||||||
if (!ack.received) {
|
if (!ack.received) {
|
||||||
cb.onState(ack.status == -2 ? "升级已中止" : "等待扇区应答超时 offset=" + offset)
|
cb.onState(ack.status == -2 ? "Upgrade aborted" : "Timeout waiting for sector ack, offset=" + offset)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (ack.status == 2) {
|
if (ack.status == 2) {
|
||||||
// offset 乱序:从设备期望的 offset 重发重同步
|
// offset 乱序:从设备期望的 offset 重发重同步
|
||||||
offset = ack.offset
|
offset = ack.offset
|
||||||
cb.onState("重同步至 offset=" + offset)
|
cb.onState("Resync to offset=" + offset)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (ack.status != 0) {
|
if (ack.status != 0) {
|
||||||
cb.onState("设备写入错误: " + statusText(ack.status))
|
cb.onState("Device write error: " + statusText(ack.status))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,22 +161,22 @@ async function run(image: number[], version: number, cb: OtaCallbacks): Promise<
|
|||||||
// 3. OTA_END:设备 flush 尾部并整镜像回读校验 CRC32
|
// 3. OTA_END:设备 flush 尾部并整镜像回读校验 CRC32
|
||||||
const pe = [] as number[]
|
const pe = [] as number[]
|
||||||
pushU32LE(pe, crc)
|
pushU32LE(pe, crc)
|
||||||
cb.onState("发送 OTA_END,设备校验镜像 ...")
|
cb.onState("Sending OTA_END, verifying image on device...")
|
||||||
if (!await sendFrame(TYPE_OTA_END, pe)) {
|
if (!await sendFrame(TYPE_OTA_END, pe)) {
|
||||||
cb.onState("OTA_END 发送失败")
|
cb.onState("Failed to send OTA_END")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
ack = await waitRsp(TYPE_OTA_END, 15000)
|
ack = await waitRsp(TYPE_OTA_END, 15000)
|
||||||
if (!ack.received) {
|
if (!ack.received) {
|
||||||
cb.onState(ack.status == -2 ? "升级已中止" : "等待 OTA_END 应答超时")
|
cb.onState(ack.status == -2 ? "Upgrade aborted" : "Timeout waiting for OTA_END ack")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (ack.status != 0) {
|
if (ack.status != 0) {
|
||||||
cb.onState("镜像校验失败: " + statusText(ack.status))
|
cb.onState("Image verification failed: " + statusText(ack.status))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
cb.onProgress(100)
|
cb.onProgress(100)
|
||||||
cb.onState("升级成功,设备约 200ms 后自动复位运行新固件,请重新连接")
|
cb.onState("Upgrade succeeded. Device will reboot into the new firmware in ~200ms, please reconnect.")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,11 +187,11 @@ async function run(image: number[], version: number, cb: OtaCallbacks): Promise<
|
|||||||
*/
|
*/
|
||||||
export async function start(image: number[], version: number, cb: OtaCallbacks): Promise<boolean> {
|
export async function start(image: number[], version: number, cb: OtaCallbacks): Promise<boolean> {
|
||||||
if (running) {
|
if (running) {
|
||||||
cb.onState("已有升级任务进行中")
|
cb.onState("Upgrade already in progress")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (image.length < 4 || image.length > BANK_MAX) {
|
if (image.length < 4 || image.length > BANK_MAX) {
|
||||||
cb.onState("镜像大小非法(4 ~ " + BANK_MAX + " 字节)")
|
cb.onState("Invalid image size (4 ~ " + BANK_MAX + " bytes)")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
running = true
|
running = true
|
||||||
|
|||||||
BIN
static/appIcon.png
Normal file
BIN
static/appIcon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
Loading…
x
Reference in New Issue
Block a user