@onekeyfe/hd-core 1.2.0-alpha.1 → 1.2.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/connectSettings.test.ts +19 -0
- package/__tests__/device-wallet-session-store.test.ts +131 -0
- package/__tests__/preInitialize.test.ts +33 -0
- package/__tests__/protocol-v2-firmware-targets.test.ts +19 -0
- package/__tests__/protocol-v2.test.ts +1784 -502
- package/dist/api/ClearSessionCache.d.ts +9 -0
- package/dist/api/ClearSessionCache.d.ts.map +1 -0
- package/dist/api/FileRead.d.ts.map +1 -1
- package/dist/api/FileWrite.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +24 -3
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.d.ts.map +1 -1
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +1 -0
- package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
- package/dist/api/firmware/getBinary.d.ts +4 -0
- package/dist/api/firmware/getBinary.d.ts.map +1 -1
- package/dist/api/firmware/progressThrottle.d.ts +3 -0
- package/dist/api/firmware/progressThrottle.d.ts.map +1 -0
- package/dist/api/index.d.ts +8 -10
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceFactoryInfoGet.d.ts +6 -0
- package/dist/api/protocol-v2/DeviceFactoryInfoGet.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceFactoryInfoSet.d.ts +7 -0
- package/dist/api/protocol-v2/DeviceFactoryInfoSet.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts +1 -1
- package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts +3 -2
- package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
- package/dist/api/protocol-v2/{DeviceGetDeviceInfo.d.ts → DeviceInfoGet.d.ts} +10 -10
- package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceSessionGet.d.ts +9 -0
- package/dist/api/protocol-v2/DeviceSessionGet.d.ts.map +1 -0
- package/dist/api/protocol-v2/DeviceStatusGet.d.ts +6 -0
- package/dist/api/protocol-v2/DeviceStatusGet.d.ts.map +1 -0
- package/dist/api/protocol-v2/FilesystemFormat.d.ts.map +1 -1
- package/dist/api/protocol-v2/{FilesystemFixPermission.d.ts → FilesystemPermissionFix.d.ts} +2 -2
- package/dist/api/protocol-v2/FilesystemPermissionFix.d.ts.map +1 -0
- package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts +6 -0
- package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -0
- package/dist/api/protocol-v2/helpers.d.ts +27 -18
- package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
- package/dist/api/ton/TonSignData.d.ts +1 -5
- package/dist/api/ton/TonSignData.d.ts.map +1 -1
- package/dist/api/tron/TronSignMessage.d.ts +0 -1
- package/dist/api/tron/TronSignMessage.d.ts.map +1 -1
- package/dist/api/tron/TronSignTransaction.d.ts +0 -1
- package/dist/api/tron/TronSignTransaction.d.ts.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/data-manager/DataManager.d.ts +1 -1
- package/dist/data-manager/DataManager.d.ts.map +1 -1
- package/dist/data-manager/connectSettings.d.ts.map +1 -1
- package/dist/device/Device.d.ts +6 -3
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceWalletSessionStore.d.ts +15 -0
- package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -0
- package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
- package/dist/deviceProfile/buildDeviceProfile.d.ts.map +1 -1
- package/dist/index.d.ts +96 -55
- package/dist/index.js +2077 -1609
- package/dist/inject.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/features.d.ts +12 -14
- package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/firmware.d.ts +11 -9
- package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/index.d.ts +1 -0
- package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
- package/dist/protocols/protocol-v2/walletSession.d.ts +13 -0
- package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -0
- package/dist/types/api/firmwareUpdate.d.ts +4 -6
- package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
- package/dist/types/api/getDeviceInfo.d.ts +2 -0
- package/dist/types/api/getDeviceInfo.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +11 -11
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +16 -17
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- package/dist/types/api/sessionCache.d.ts +10 -0
- package/dist/types/api/sessionCache.d.ts.map +1 -0
- package/dist/types/device.d.ts +1 -0
- package/dist/types/device.d.ts.map +1 -1
- package/dist/types/params.d.ts +1 -0
- package/dist/types/params.d.ts.map +1 -1
- package/dist/types/settings.d.ts +21 -2
- package/dist/types/settings.d.ts.map +1 -1
- package/dist/utils/deviceFeaturesUtils.d.ts +2 -0
- package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
- package/dist/utils/patch.d.ts +1 -1
- package/dist/utils/patch.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/api/ClearSessionCache.ts +28 -0
- package/src/api/FileRead.ts +16 -3
- package/src/api/FileWrite.ts +14 -1
- package/src/api/FirmwareUpdateV4.ts +1075 -264
- package/src/api/GetOnekeyFeatures.ts +1 -1
- package/src/api/GetPassphraseState.ts +4 -1
- package/src/api/device/DeviceRebootToBoardloader.ts +4 -4
- package/src/api/device/DeviceRebootToBootloader.ts +4 -4
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +18 -4
- package/src/api/firmware/progressThrottle.ts +44 -0
- package/src/api/index.ts +8 -10
- package/src/api/protocol-v2/{FactoryGetDeviceInfo.ts → DeviceFactoryInfoGet.ts} +3 -3
- package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +32 -0
- package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +3 -5
- package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +39 -5
- package/src/api/protocol-v2/{DeviceGetDeviceInfo.ts → DeviceInfoGet.ts} +52 -22
- package/src/api/protocol-v2/DeviceReboot.ts +1 -1
- package/src/api/protocol-v2/DeviceSessionGet.ts +26 -0
- package/src/api/protocol-v2/DeviceStatusGet.ts +14 -0
- package/src/api/protocol-v2/FilesystemFormat.ts +4 -1
- package/src/api/protocol-v2/{FilesystemFixPermission.ts → FilesystemPermissionFix.ts} +2 -2
- package/src/api/protocol-v2/{GetProtoVersion.ts → ProtocolInfoRequest.ts} +2 -2
- package/src/api/protocol-v2/helpers.ts +71 -47
- package/src/api/ton/TonSignData.ts +1 -5
- package/src/api/tron/TronSignMessage.ts +0 -1
- package/src/api/tron/TronSignTransaction.ts +0 -1
- package/src/core/index.ts +19 -5
- package/src/data/messages/messages-protocol-v2.json +599 -1033
- package/src/data/messages/messages.json +8 -0
- package/src/data-manager/DataManager.ts +7 -0
- package/src/data-manager/connectSettings.ts +2 -4
- package/src/device/Device.ts +89 -70
- package/src/device/DeviceWalletSessionStore.ts +78 -0
- package/src/deviceProfile/buildDeviceFeatures.ts +60 -63
- package/src/deviceProfile/buildDeviceProfile.ts +77 -58
- package/src/inject.ts +13 -16
- package/src/protocols/protocol-v2/features.ts +26 -63
- package/src/protocols/protocol-v2/firmware.ts +12 -36
- package/src/protocols/protocol-v2/index.ts +1 -0
- package/src/protocols/protocol-v2/walletSession.ts +83 -0
- package/src/types/api/firmwareUpdate.ts +16 -20
- package/src/types/api/getDeviceInfo.ts +2 -0
- package/src/types/api/index.ts +17 -20
- package/src/types/api/protocolV2.ts +36 -40
- package/src/types/api/sessionCache.ts +14 -0
- package/src/types/device.ts +1 -0
- package/src/types/params.ts +4 -0
- package/src/types/settings.ts +42 -35
- package/src/utils/deviceFeaturesUtils.ts +44 -13
- package/dist/api/protocol-v2/DevReboot.d.ts +0 -7
- package/dist/api/protocol-v2/DevReboot.d.ts.map +0 -1
- package/dist/api/protocol-v2/DeviceGetDeviceInfo.d.ts.map +0 -1
- package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts +0 -6
- package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts.map +0 -1
- package/dist/api/protocol-v2/FactoryDeviceInfoSettings.d.ts +0 -7
- package/dist/api/protocol-v2/FactoryDeviceInfoSettings.d.ts.map +0 -1
- package/dist/api/protocol-v2/FactoryGetDeviceInfo.d.ts +0 -6
- package/dist/api/protocol-v2/FactoryGetDeviceInfo.d.ts.map +0 -1
- package/dist/api/protocol-v2/FilesystemFixPermission.d.ts.map +0 -1
- package/dist/api/protocol-v2/GetProtoVersion.d.ts +0 -6
- package/dist/api/protocol-v2/GetProtoVersion.d.ts.map +0 -1
- package/src/api/protocol-v2/DevReboot.ts +0 -24
- package/src/api/protocol-v2/DeviceGetOnboardingStatus.ts +0 -16
- package/src/api/protocol-v2/FactoryDeviceInfoSettings.ts +0 -29
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ERRORS, HardwareError, HardwareErrorCode, wait } from '@onekeyfe/hd-shared';
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
DeviceRebootType,
|
|
4
4
|
PROTOCOL_V2_BLE_FILE_CHUNK_SIZE,
|
|
5
5
|
PROTOCOL_V2_WEBUSB_FILE_CHUNK_SIZE,
|
|
6
6
|
} from '@onekeyfe/hd-transport';
|
|
@@ -15,14 +15,13 @@ import {
|
|
|
15
15
|
getFirmwareType,
|
|
16
16
|
getLogger,
|
|
17
17
|
} from '../utils';
|
|
18
|
-
import {
|
|
18
|
+
import { getSysResourceBinary } from './firmware/getBinary';
|
|
19
19
|
import { DataManager } from '../data-manager';
|
|
20
20
|
import { FirmwareUpdateBaseMethod } from './firmware/FirmwareUpdateBaseMethod';
|
|
21
21
|
import { DevicePool } from '../device/DevicePool';
|
|
22
22
|
import {
|
|
23
23
|
PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
|
|
24
24
|
ProtocolV2FirmwareTargetType,
|
|
25
|
-
protocolV2FileNameToTargetId,
|
|
26
25
|
} from '../protocols/protocol-v2';
|
|
27
26
|
import { requestProtocolV2DeviceInfo } from '../protocols/protocol-v2/features';
|
|
28
27
|
import {
|
|
@@ -35,31 +34,182 @@ import type { FirmwareUpdateV4Params } from '../types/api/firmwareUpdate';
|
|
|
35
34
|
import type { EFirmwareType } from '@onekeyfe/hd-shared';
|
|
36
35
|
import type { PROTO } from '../constants';
|
|
37
36
|
import type { TypedResponseMessage } from '../device/DeviceCommands';
|
|
38
|
-
import type {
|
|
37
|
+
import type {
|
|
38
|
+
Features,
|
|
39
|
+
IFirmwareReleaseInfo,
|
|
40
|
+
IProtocolV2FirmwareComponent,
|
|
41
|
+
IVersionArray,
|
|
42
|
+
} from '../types';
|
|
39
43
|
|
|
40
44
|
const Log = getLogger(LoggerNames.Method);
|
|
41
45
|
|
|
42
46
|
const SESSION_ERROR = 'session not found';
|
|
43
47
|
const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 60 * 1000;
|
|
44
48
|
const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
|
|
49
|
+
const PROTOCOL_V2_START_UPDATE_TIMEOUT = 3 * 60 * 1000;
|
|
45
50
|
const PROTOCOL_V2_INSTALL_TIMEOUT = 5 * 60 * 1000;
|
|
46
|
-
const
|
|
51
|
+
const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
|
|
47
52
|
const PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS = 1;
|
|
48
|
-
const
|
|
53
|
+
const PROTOCOL_V2_TARGET_STATUS_FINISHED = 2;
|
|
54
|
+
const PROTOCOL_V2_TARGET_STATUS_FAILED_MIN = 3;
|
|
49
55
|
const PROTOCOL_V2_CONNECT_PROTOCOL = 'V2';
|
|
50
|
-
const PROTOCOL_V2_FIRMWARE_STAGING_VOLUME = '
|
|
56
|
+
const PROTOCOL_V2_FIRMWARE_STAGING_VOLUME = 'vol0:/';
|
|
51
57
|
const PROTOCOL_V2_MIN_FILE_CHUNK_SIZE = 64;
|
|
58
|
+
const PROTOCOL_V2_CONNECT_RETRY_COUNT = 10;
|
|
59
|
+
const PROTOCOL_V2_CONNECT_POLL_INTERVAL = 500;
|
|
60
|
+
const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
|
|
61
|
+
const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 60 * 1000;
|
|
62
|
+
const PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT = 3;
|
|
63
|
+
const PROTOCOL_V2_OKPP_HEADER_SIZE = 0x52a0;
|
|
64
|
+
const PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET = 0x200;
|
|
65
|
+
const PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET = 0x240;
|
|
66
|
+
const PROTOCOL_V2_OKPP_HASH_SIZE = 64;
|
|
67
|
+
|
|
68
|
+
const getProtocolV2DeviceTransferProgress = (
|
|
69
|
+
bytesBeforeChunk: number,
|
|
70
|
+
bytesAfterChunk: number,
|
|
71
|
+
totalBytes: number
|
|
72
|
+
) => {
|
|
73
|
+
if (!Number.isFinite(totalBytes) || totalBytes <= 0) {
|
|
74
|
+
return 100;
|
|
75
|
+
}
|
|
76
|
+
if (bytesBeforeChunk <= 0 && bytesAfterChunk < totalBytes) {
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
if (bytesAfterChunk >= totalBytes) {
|
|
80
|
+
return 100;
|
|
81
|
+
}
|
|
82
|
+
return Math.min(Math.max(Math.ceil((bytesAfterChunk / totalBytes) * 100), 1), 99);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const formatProtocolV2TransferSpeed = (bytes: number, elapsedMs: number) => {
|
|
86
|
+
const safeElapsedMs = Math.max(elapsedMs, 1);
|
|
87
|
+
return (bytes / 1024 / (safeElapsedMs / 1000)).toFixed(2);
|
|
88
|
+
};
|
|
52
89
|
|
|
53
90
|
type ProtocolV2FirmwareUpdateStatusTarget = {
|
|
54
|
-
target_id: number;
|
|
55
|
-
status
|
|
91
|
+
target_id: number | string;
|
|
92
|
+
status?: number | string;
|
|
93
|
+
payload_version?: number;
|
|
94
|
+
path?: string;
|
|
56
95
|
};
|
|
57
96
|
|
|
58
97
|
type ProtocolV2FirmwareUpdateStartResponse =
|
|
59
98
|
| TypedResponseMessage<'Success'>
|
|
60
|
-
| TypedResponseMessage<'
|
|
99
|
+
| TypedResponseMessage<'DeviceFirmwareUpdateStatus'>
|
|
61
100
|
| undefined;
|
|
62
101
|
|
|
102
|
+
type ProtocolV2TargetBinary = { fileName: string; binary: ArrayBuffer; targetId: number };
|
|
103
|
+
type ProtocolV2InstallItem = ProtocolV2TargetBinary & {
|
|
104
|
+
kind: ProtocolV2RemoteComponentTarget['kind'];
|
|
105
|
+
};
|
|
106
|
+
type ProtocolV2InstallTarget = ProtocolV2InstallItem & {
|
|
107
|
+
path: string;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
type ProtocolV2FileTransferParams = PROTO.FirmwareUpload & {
|
|
111
|
+
filePath: string;
|
|
112
|
+
processedSize?: number;
|
|
113
|
+
totalSize?: number;
|
|
114
|
+
onTransferredBytes?: (transferredBytes: number) => void;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/** RESC bundle okpkg(FileWrite 直写模式),每个独立同步到 devicePath */
|
|
118
|
+
type ProtocolV2ResourceBundleBinary = {
|
|
119
|
+
name: string;
|
|
120
|
+
binary: ArrayBuffer;
|
|
121
|
+
devicePath: string;
|
|
122
|
+
/** 远端配置模式下的下载 URL(手动模式不填) */
|
|
123
|
+
url?: string;
|
|
124
|
+
version?: IVersionArray;
|
|
125
|
+
payloadHash?: string;
|
|
126
|
+
headerHash?: string;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
type ProtocolV2RemoteComponentBinary = ProtocolV2RemoteComponentTarget & {
|
|
130
|
+
binary: ArrayBuffer;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
type ProtocolV2RemoteComponentTarget = {
|
|
134
|
+
fileName: string;
|
|
135
|
+
targetId: number;
|
|
136
|
+
kind: 'bootloader' | 'firmware';
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
type ProtocolV2OkppHeader = {
|
|
140
|
+
type: string;
|
|
141
|
+
version: IVersionArray;
|
|
142
|
+
payloadHash: string;
|
|
143
|
+
headerHash: string;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const PROTOCOL_V2_REMOTE_COMPONENT_TARGETS: Readonly<
|
|
147
|
+
Record<string, ProtocolV2RemoteComponentTarget>
|
|
148
|
+
> = {
|
|
149
|
+
BOOTLOADER: {
|
|
150
|
+
fileName: 'bootloader.bin',
|
|
151
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER,
|
|
152
|
+
kind: 'bootloader',
|
|
153
|
+
},
|
|
154
|
+
APPLICATION_P1: {
|
|
155
|
+
fileName: 'application_p1.bin',
|
|
156
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1,
|
|
157
|
+
kind: 'firmware',
|
|
158
|
+
},
|
|
159
|
+
APPLICATION_P2: {
|
|
160
|
+
fileName: 'application_p2.bin',
|
|
161
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2,
|
|
162
|
+
kind: 'firmware',
|
|
163
|
+
},
|
|
164
|
+
COPROCESSOR: {
|
|
165
|
+
fileName: 'coprocessor.bin',
|
|
166
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR,
|
|
167
|
+
kind: 'firmware',
|
|
168
|
+
},
|
|
169
|
+
SE01: {
|
|
170
|
+
fileName: 'se01.bin',
|
|
171
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE01,
|
|
172
|
+
kind: 'firmware',
|
|
173
|
+
},
|
|
174
|
+
SE02: {
|
|
175
|
+
fileName: 'se02.bin',
|
|
176
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE02,
|
|
177
|
+
kind: 'firmware',
|
|
178
|
+
},
|
|
179
|
+
SE03: {
|
|
180
|
+
fileName: 'se03.bin',
|
|
181
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE03,
|
|
182
|
+
kind: 'firmware',
|
|
183
|
+
},
|
|
184
|
+
SE04: {
|
|
185
|
+
fileName: 'se04.bin',
|
|
186
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04,
|
|
187
|
+
kind: 'firmware',
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const PROTOCOL_V2_ROMLOADER_UNSUPPORTED_MESSAGE =
|
|
192
|
+
'FW_MGMT_TARGET_ROMLOADER is not accepted by the current Pro2 bootloader update request. Flash romloader with the loader-specific flow instead of firmwareUpdateV4.';
|
|
193
|
+
|
|
194
|
+
// hd-transport 的历史 decode 行为会把单值 enum 输出为枚举名字符串;
|
|
195
|
+
// Protocol V2 沿用这个 SDK 语义,内部比较前再映射回固件协议数值。
|
|
196
|
+
const PROTOCOL_V2_TARGET_ID_BY_DECODED_NAME = new Map<string, number>(
|
|
197
|
+
Object.entries(ProtocolV2FirmwareTargetType).map(([key, value]) => [key, value])
|
|
198
|
+
);
|
|
199
|
+
const PROTOCOL_V2_TARGET_STATUS_BY_DECODED_NAME = new Map<string, number>([
|
|
200
|
+
['FW_MGMT_UPDATER_TASK_STATUS_PENDING', PROTOCOL_V2_TARGET_STATUS_PENDING],
|
|
201
|
+
['FW_MGMT_UPDATER_TASK_STATUS_IN_PROGRESS', PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS],
|
|
202
|
+
['FW_MGMT_UPDATER_TASK_STATUS_FINISHED', PROTOCOL_V2_TARGET_STATUS_FINISHED],
|
|
203
|
+
['FW_MGMT_UPDATER_TASK_STATUS_FAILED_FILE_NOT_FOUND', 3],
|
|
204
|
+
['FW_MGMT_UPDATER_TASK_STATUS_FAILED_FILE_READ', 4],
|
|
205
|
+
['FW_MGMT_UPDATER_TASK_STATUS_FAILED_FILE_WRITE', 5],
|
|
206
|
+
['FW_MGMT_UPDATER_TASK_STATUS_FAILED_VERIFY', 6],
|
|
207
|
+
['FW_MGMT_UPDATER_TASK_STATUS_FAILED_INSTALL', 7],
|
|
208
|
+
['FW_MGMT_UPDATER_TASK_STATUS_FAILED_ABORT', 8],
|
|
209
|
+
['FW_MGMT_UPDATER_TASK_STATUS_FAILED_BUSY', 9],
|
|
210
|
+
['FW_MGMT_UPDATER_TASK_STATUS_FAILED_ENTRY_OUT_OF_BOUNDS', 10],
|
|
211
|
+
]);
|
|
212
|
+
|
|
63
213
|
const isProtocolV2ReconnectProbeError = (error: unknown) => {
|
|
64
214
|
const message = getProtocolV2UnknownErrorText(error).toLowerCase();
|
|
65
215
|
return (
|
|
@@ -73,19 +223,165 @@ const isProtocolV2PollingTransientError = (error: unknown) => {
|
|
|
73
223
|
return (
|
|
74
224
|
isProtocolV2DeviceDisconnectedError(error) ||
|
|
75
225
|
isProtocolV2ReconnectProbeError(error) ||
|
|
76
|
-
|
|
226
|
+
message.includes('libusb_transfer_timed_out') ||
|
|
227
|
+
(message.includes('response timeout') && message.includes('devicefirmwareupdatestatusget')) ||
|
|
77
228
|
message.includes('device not found') ||
|
|
78
229
|
message.includes('transportnotfound')
|
|
79
230
|
);
|
|
80
231
|
};
|
|
81
232
|
|
|
233
|
+
const isProtocolV2StartUpdateTransientError = (error: unknown) => {
|
|
234
|
+
const message = getProtocolV2UnknownErrorText(error).toLowerCase();
|
|
235
|
+
return (
|
|
236
|
+
isProtocolV2DeviceDisconnectedError(error) ||
|
|
237
|
+
isProtocolV2ReconnectProbeError(error) ||
|
|
238
|
+
message.includes('libusb_transfer_timed_out') ||
|
|
239
|
+
(message.includes('response timeout') && message.includes('devicefirmwareupdaterequest'))
|
|
240
|
+
);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const isProtocolV2TargetStatusFinished = (status: ProtocolV2FirmwareUpdateStatusTarget['status']) =>
|
|
244
|
+
normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_FINISHED;
|
|
245
|
+
|
|
246
|
+
const isProtocolV2TargetStatusInProgress = (
|
|
247
|
+
status: ProtocolV2FirmwareUpdateStatusTarget['status']
|
|
248
|
+
) =>
|
|
249
|
+
normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_PENDING ||
|
|
250
|
+
normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS;
|
|
251
|
+
|
|
252
|
+
const isProtocolV2TargetStatusFailed = (status: ProtocolV2FirmwareUpdateStatusTarget['status']) => {
|
|
253
|
+
const normalizedStatus = normalizeProtocolV2TargetStatus(status);
|
|
254
|
+
return (
|
|
255
|
+
typeof normalizedStatus === 'number' && normalizedStatus >= PROTOCOL_V2_TARGET_STATUS_FAILED_MIN
|
|
256
|
+
);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const normalizeProtocolV2TargetId = (targetId: number | string) => {
|
|
260
|
+
if (typeof targetId === 'number') {
|
|
261
|
+
return targetId;
|
|
262
|
+
}
|
|
263
|
+
return PROTOCOL_V2_TARGET_ID_BY_DECODED_NAME.get(targetId);
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const normalizeProtocolV2TargetStatus = (
|
|
267
|
+
status: ProtocolV2FirmwareUpdateStatusTarget['status']
|
|
268
|
+
) => {
|
|
269
|
+
if (typeof status === 'number') {
|
|
270
|
+
return status;
|
|
271
|
+
}
|
|
272
|
+
if (typeof status === 'string') {
|
|
273
|
+
return PROTOCOL_V2_TARGET_STATUS_BY_DECODED_NAME.get(status);
|
|
274
|
+
}
|
|
275
|
+
return undefined;
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
|
|
279
|
+
|
|
280
|
+
const versionArrayToNumber = (version?: IVersionArray) => {
|
|
281
|
+
if (!version) return undefined;
|
|
282
|
+
return version[0] * 0x10000 + version[1] * 0x100 + version[2];
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const versionStringToArray = (version?: string | null): IVersionArray | undefined => {
|
|
286
|
+
if (!version) return undefined;
|
|
287
|
+
const parts = version.split('.').map(part => Number(part));
|
|
288
|
+
if (parts.length !== 3 || parts.some(part => !Number.isFinite(part))) return undefined;
|
|
289
|
+
return parts as IVersionArray;
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const compareProtocolV2Versions = (current?: IVersionArray, target?: IVersionArray) => {
|
|
293
|
+
const currentNumber = versionArrayToNumber(current);
|
|
294
|
+
const targetNumber = versionArrayToNumber(target);
|
|
295
|
+
if (currentNumber === undefined || targetNumber === undefined) return undefined;
|
|
296
|
+
return currentNumber - targetNumber;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const bytesToHex = (bytes: Uint8Array) =>
|
|
300
|
+
Array.from(bytes)
|
|
301
|
+
.map(byte => byte.toString(16).padStart(2, '0'))
|
|
302
|
+
.join('');
|
|
303
|
+
|
|
304
|
+
const hexToProtocolV2Bytes = (hex: string) => {
|
|
305
|
+
const normalized = hex.replace(/^0x/i, '');
|
|
306
|
+
if (!normalized || normalized.length % 2 !== 0 || /[^0-9a-f]/i.test(normalized)) {
|
|
307
|
+
return new Uint8Array(0);
|
|
308
|
+
}
|
|
309
|
+
const bytes = new Uint8Array(normalized.length / 2);
|
|
310
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
311
|
+
bytes[i] = Number.parseInt(normalized.slice(i * 2, i * 2 + 2), 16);
|
|
312
|
+
}
|
|
313
|
+
return bytes;
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const toProtocolV2Bytes = (value: unknown): Uint8Array => {
|
|
317
|
+
if (value instanceof Uint8Array) return value;
|
|
318
|
+
if (value instanceof ArrayBuffer) return new Uint8Array(value);
|
|
319
|
+
if (ArrayBuffer.isView(value)) {
|
|
320
|
+
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
321
|
+
}
|
|
322
|
+
if (typeof value === 'string') return hexToProtocolV2Bytes(value);
|
|
323
|
+
return new Uint8Array(0);
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const toProtocolV2FiniteNumber = (value: unknown) => {
|
|
327
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
328
|
+
if (typeof value === 'string') {
|
|
329
|
+
const numeric = Number(value);
|
|
330
|
+
return Number.isFinite(numeric) ? numeric : undefined;
|
|
331
|
+
}
|
|
332
|
+
if (value && typeof value === 'object') {
|
|
333
|
+
const longLike = value as { toNumber?: () => number };
|
|
334
|
+
if (typeof longLike.toNumber === 'function') {
|
|
335
|
+
const numeric = longLike.toNumber();
|
|
336
|
+
return Number.isFinite(numeric) ? numeric : undefined;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return undefined;
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const readProtocolV2Ascii = (bytes: Uint8Array, offset: number, length: number) =>
|
|
343
|
+
Array.from(bytes.slice(offset, offset + length))
|
|
344
|
+
.map(byte => String.fromCharCode(byte))
|
|
345
|
+
.join('');
|
|
346
|
+
|
|
347
|
+
const parseProtocolV2OkppHeader = (bytes: Uint8Array): ProtocolV2OkppHeader | null => {
|
|
348
|
+
if (bytes.byteLength < PROTOCOL_V2_OKPP_HEADER_SIZE) return null;
|
|
349
|
+
if (readProtocolV2Ascii(bytes, 0, 4) !== 'OKPP') return null;
|
|
350
|
+
|
|
351
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
352
|
+
const headerLen = view.getUint32(0x0c, true);
|
|
353
|
+
if (headerLen !== PROTOCOL_V2_OKPP_HEADER_SIZE) return null;
|
|
354
|
+
|
|
355
|
+
const packedVersion = view.getUint32(0x10, true);
|
|
356
|
+
return {
|
|
357
|
+
type: readProtocolV2Ascii(bytes, 0x08, 4),
|
|
358
|
+
version: [
|
|
359
|
+
Math.floor(packedVersion / 0x10000) % 0x100,
|
|
360
|
+
Math.floor(packedVersion / 0x100) % 0x100,
|
|
361
|
+
packedVersion % 0x100,
|
|
362
|
+
],
|
|
363
|
+
payloadHash: bytesToHex(
|
|
364
|
+
bytes.slice(
|
|
365
|
+
PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET,
|
|
366
|
+
PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET + PROTOCOL_V2_OKPP_HASH_SIZE
|
|
367
|
+
)
|
|
368
|
+
),
|
|
369
|
+
headerHash: bytesToHex(
|
|
370
|
+
bytes.slice(
|
|
371
|
+
PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET,
|
|
372
|
+
PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET + PROTOCOL_V2_OKPP_HASH_SIZE
|
|
373
|
+
)
|
|
374
|
+
),
|
|
375
|
+
};
|
|
376
|
+
};
|
|
377
|
+
|
|
82
378
|
/**
|
|
83
379
|
* FirmwareUpdateV4 is the complete Protocol V2 firmware update flow.
|
|
84
380
|
*
|
|
85
381
|
* It intentionally does not fall back to FirmwareUpdateV3/V1 behavior:
|
|
86
382
|
* - upload uses FilesystemFileWrite
|
|
87
|
-
* - install uses
|
|
88
|
-
* - completion reboots to normal, then polls
|
|
383
|
+
* - install uses DeviceFirmwareUpdateRequest
|
|
384
|
+
* - completion waits for target status to finish, reboots to normal, then polls DeviceInfo
|
|
89
385
|
*/
|
|
90
386
|
export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareUpdateV4Params> {
|
|
91
387
|
init() {
|
|
@@ -96,15 +392,22 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
96
392
|
|
|
97
393
|
const { payload } = this;
|
|
98
394
|
|
|
395
|
+
if (typeof payload.retryCount !== 'number') {
|
|
396
|
+
payload.retryCount = PROTOCOL_V2_CONNECT_RETRY_COUNT;
|
|
397
|
+
}
|
|
398
|
+
if (typeof payload.pollIntervalTime !== 'number') {
|
|
399
|
+
payload.pollIntervalTime = PROTOCOL_V2_CONNECT_POLL_INTERVAL;
|
|
400
|
+
}
|
|
401
|
+
if (typeof payload.timeout !== 'number') {
|
|
402
|
+
payload.timeout = PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT;
|
|
403
|
+
}
|
|
404
|
+
if (typeof payload.protocolV2DeviceInfoTimeoutMs !== 'number') {
|
|
405
|
+
payload.protocolV2DeviceInfoTimeoutMs = PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT;
|
|
406
|
+
}
|
|
407
|
+
|
|
99
408
|
validateParams(payload, [
|
|
100
|
-
{ name: 'bleVersion', type: 'array' },
|
|
101
|
-
{ name: 'bleBinary', type: 'buffer' },
|
|
102
409
|
{ name: 'chunkSize', type: 'number' },
|
|
103
|
-
{ name: 'firmwareVersion', type: 'array' },
|
|
104
|
-
{ name: 'firmwareBinary', type: 'buffer' },
|
|
105
|
-
{ name: 'resourceBinary', type: 'buffer' },
|
|
106
410
|
{ name: 'forcedUpdateRes', type: 'boolean' },
|
|
107
|
-
{ name: 'bootloaderVersion', type: 'array' },
|
|
108
411
|
{ name: 'bootloaderBinary', type: 'buffer' },
|
|
109
412
|
{ name: 'romloaderBinary', type: 'buffer' },
|
|
110
413
|
{ name: 'applicationP1Binary', type: 'buffer' },
|
|
@@ -116,15 +419,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
116
419
|
{ name: 'se04Binary', type: 'buffer' },
|
|
117
420
|
{ name: 'firmwareType', type: 'string' },
|
|
118
421
|
{ name: 'platform', type: 'string' },
|
|
422
|
+
{ name: 'resourceBundleFiles', type: 'array', allowEmpty: true },
|
|
119
423
|
]);
|
|
120
424
|
|
|
121
425
|
this.params = {
|
|
122
|
-
bleBinary: payload.bleBinary,
|
|
123
426
|
chunkSize: payload.chunkSize,
|
|
124
|
-
firmwareBinary: payload.firmwareBinary,
|
|
125
427
|
forcedUpdateRes: payload.forcedUpdateRes,
|
|
126
|
-
bleVersion: payload.bleVersion,
|
|
127
|
-
bootloaderVersion: payload.bootloaderVersion,
|
|
128
428
|
bootloaderBinary: payload.bootloaderBinary,
|
|
129
429
|
romloaderBinary: payload.romloaderBinary,
|
|
130
430
|
applicationP1Binary: payload.applicationP1Binary,
|
|
@@ -134,8 +434,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
134
434
|
se02Binary: payload.se02Binary,
|
|
135
435
|
se03Binary: payload.se03Binary,
|
|
136
436
|
se04Binary: payload.se04Binary,
|
|
137
|
-
|
|
138
|
-
resourceBinary: payload.resourceBinary,
|
|
437
|
+
resourceBundleFiles: payload.resourceBundleFiles,
|
|
139
438
|
firmwareType: payload.firmwareType,
|
|
140
439
|
platform: payload.platform,
|
|
141
440
|
};
|
|
@@ -174,35 +473,43 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
174
473
|
const deviceFirmwareType = getFirmwareType(deviceFeatures);
|
|
175
474
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
176
475
|
|
|
177
|
-
let
|
|
178
|
-
let fwBinaryMap: { fileName: string; binary: ArrayBuffer; targetId?: number }[] = [];
|
|
476
|
+
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
179
477
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
478
|
+
let installItems: ProtocolV2InstallItem[] | undefined;
|
|
180
479
|
try {
|
|
181
480
|
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
481
|
+
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
482
|
+
bootloaderBinary = this.prepareBootloaderBinary();
|
|
483
|
+
if (!this.hasExplicitProtocolV2Payload(fwBinaryMap)) {
|
|
484
|
+
const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
|
|
485
|
+
firmwareType,
|
|
486
|
+
deviceFeatures
|
|
487
|
+
);
|
|
488
|
+
bootloaderBinary = remoteBinaries.bootloaderBinary;
|
|
489
|
+
fwBinaryMap = remoteBinaries.fwBinaryMap;
|
|
490
|
+
installItems = remoteBinaries.installItems;
|
|
491
|
+
}
|
|
187
492
|
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
188
493
|
} catch (err) {
|
|
189
494
|
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
|
|
190
495
|
}
|
|
191
496
|
|
|
192
|
-
|
|
497
|
+
const resourceBundles = this.prepareProtocolV2ResourceBundles(firmwareType, deviceFeatures);
|
|
498
|
+
|
|
499
|
+
if (!bootloaderBinary && fwBinaryMap.length === 0 && !resourceBundles?.length) {
|
|
193
500
|
throw ERRORS.TypedError(
|
|
194
501
|
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
195
502
|
'No firmware to update'
|
|
196
503
|
);
|
|
197
504
|
}
|
|
198
505
|
|
|
199
|
-
|
|
200
|
-
// await this.enterProtocolV2BootloaderMode();
|
|
506
|
+
await this.enterProtocolV2BootloaderMode();
|
|
201
507
|
|
|
202
508
|
await this.executeProtocolV2Update({
|
|
203
|
-
resourceBinary,
|
|
204
509
|
fwBinaryMap,
|
|
205
510
|
bootloaderBinary,
|
|
511
|
+
...(installItems ? { installItems } : undefined),
|
|
512
|
+
...(resourceBundles?.length ? { resourceBundles } : undefined),
|
|
206
513
|
});
|
|
207
514
|
|
|
208
515
|
await this.exitProtocolV2BootloaderToNormal();
|
|
@@ -215,50 +522,425 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
215
522
|
}
|
|
216
523
|
|
|
217
524
|
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
525
|
+
if (this.device.features) {
|
|
526
|
+
return this.device.features;
|
|
527
|
+
}
|
|
218
528
|
if (typeof this.device.getFeatures === 'function') {
|
|
219
529
|
const features = await this.device.getFeatures();
|
|
220
530
|
if (features) return features;
|
|
221
531
|
}
|
|
222
|
-
if (this.device.features) {
|
|
223
|
-
return this.device.features;
|
|
224
|
-
}
|
|
225
532
|
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Device features not available');
|
|
226
533
|
}
|
|
227
534
|
|
|
228
|
-
private
|
|
229
|
-
|
|
230
|
-
|
|
535
|
+
private prepareBootloaderBinary(): ArrayBuffer | null {
|
|
536
|
+
return this.params.bootloaderBinary ?? null;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
private hasExplicitProtocolV2Payload(fwBinaryMap: ProtocolV2TargetBinary[]) {
|
|
540
|
+
return (
|
|
541
|
+
!!this.params.resourceBundleFiles?.length ||
|
|
542
|
+
!!this.params.bootloaderBinary ||
|
|
543
|
+
fwBinaryMap.length > 0
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
private buildProtocolV2InstallItems({
|
|
548
|
+
bootloaderBinary,
|
|
549
|
+
fwBinaryMap,
|
|
550
|
+
}: {
|
|
551
|
+
bootloaderBinary: ArrayBuffer | null;
|
|
552
|
+
fwBinaryMap: ProtocolV2TargetBinary[];
|
|
553
|
+
}): ProtocolV2InstallItem[] {
|
|
554
|
+
const installItems: ProtocolV2InstallItem[] = [];
|
|
555
|
+
|
|
556
|
+
if (bootloaderBinary) {
|
|
557
|
+
installItems.push({
|
|
558
|
+
fileName: 'bootloader.bin',
|
|
559
|
+
binary: bootloaderBinary,
|
|
560
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER,
|
|
561
|
+
kind: 'bootloader',
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
installItems.push(...fwBinaryMap.map(item => ({ ...item, kind: 'firmware' as const })));
|
|
566
|
+
return installItems;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
private getRemoteComponentEntries(release: IFirmwareReleaseInfo) {
|
|
570
|
+
const { components } = release;
|
|
571
|
+
if (!components) return [];
|
|
572
|
+
|
|
573
|
+
const orderedKeys = [
|
|
574
|
+
...(release.installOrder ?? []),
|
|
575
|
+
...Object.keys(components).filter(key => !release.installOrder?.includes(key)),
|
|
576
|
+
];
|
|
577
|
+
|
|
578
|
+
return orderedKeys
|
|
579
|
+
.map(key => {
|
|
580
|
+
const component = components[key];
|
|
581
|
+
return component ? ([key, component] as const) : undefined;
|
|
582
|
+
})
|
|
583
|
+
.filter((entry): entry is readonly [string, IProtocolV2FirmwareComponent] => !!entry);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
private getRemoteComponentTarget(key: string, component: IProtocolV2FirmwareComponent) {
|
|
587
|
+
const targetName = component.target?.toUpperCase();
|
|
588
|
+
if (targetName === 'ROMLOADER') {
|
|
589
|
+
throw ERRORS.TypedError(
|
|
590
|
+
HardwareErrorCode.RuntimeError,
|
|
591
|
+
PROTOCOL_V2_ROMLOADER_UNSUPPORTED_MESSAGE
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
const target = PROTOCOL_V2_REMOTE_COMPONENT_TARGETS[targetName];
|
|
595
|
+
if (!target) {
|
|
596
|
+
throw ERRORS.TypedError(
|
|
597
|
+
HardwareErrorCode.RuntimeError,
|
|
598
|
+
`Unsupported Protocol V2 firmware component target: ${key}/${component.target}`
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
return target;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
private getProtocolV2ComponentTargetVersion(
|
|
605
|
+
release: IFirmwareReleaseInfo,
|
|
606
|
+
component: IProtocolV2FirmwareComponent,
|
|
607
|
+
target: ProtocolV2RemoteComponentTarget
|
|
608
|
+
) {
|
|
609
|
+
if (component.version) return component.version;
|
|
610
|
+
if (target.targetId === ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER) {
|
|
611
|
+
return release.bootloaderVersion;
|
|
612
|
+
}
|
|
613
|
+
if (
|
|
614
|
+
target.targetId === ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1 ||
|
|
615
|
+
target.targetId === ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2
|
|
616
|
+
) {
|
|
617
|
+
return release.version;
|
|
618
|
+
}
|
|
619
|
+
return undefined;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
private getProtocolV2ComponentCurrentVersion(
|
|
623
|
+
features: Features,
|
|
624
|
+
target: ProtocolV2RemoteComponentTarget
|
|
625
|
+
) {
|
|
626
|
+
switch (target.targetId) {
|
|
627
|
+
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER:
|
|
628
|
+
return getDeviceBootloaderVersion(features);
|
|
629
|
+
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1:
|
|
630
|
+
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2:
|
|
631
|
+
return getDeviceFirmwareVersion(features);
|
|
632
|
+
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR:
|
|
633
|
+
return getDeviceBLEFirmwareVersion(features);
|
|
634
|
+
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE01:
|
|
635
|
+
return versionStringToArray(features.se01Version);
|
|
636
|
+
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE02:
|
|
637
|
+
return versionStringToArray(features.se02Version);
|
|
638
|
+
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE03:
|
|
639
|
+
return versionStringToArray(features.se03Version);
|
|
640
|
+
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04:
|
|
641
|
+
return versionStringToArray(features.se04Version);
|
|
642
|
+
default:
|
|
643
|
+
return undefined;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
private isProtocolV2ComponentVersionSatisfied(
|
|
648
|
+
release: IFirmwareReleaseInfo,
|
|
649
|
+
component: IProtocolV2FirmwareComponent,
|
|
650
|
+
target: ProtocolV2RemoteComponentTarget,
|
|
651
|
+
features: Features
|
|
652
|
+
) {
|
|
653
|
+
const targetVersion = this.getProtocolV2ComponentTargetVersion(release, component, target);
|
|
654
|
+
if (!targetVersion) return false;
|
|
655
|
+
|
|
656
|
+
const currentVersion = this.getProtocolV2ComponentCurrentVersion(features, target);
|
|
657
|
+
const compareResult = compareProtocolV2Versions(currentVersion, targetVersion);
|
|
658
|
+
return compareResult !== undefined && compareResult >= 0;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
private getProtocolV2ResourceFilePath(path: string) {
|
|
662
|
+
if (path.startsWith('vol')) return path;
|
|
663
|
+
if (path.startsWith('/')) return `vol0:${path}`;
|
|
664
|
+
return `vol0:/${path}`;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
private async readProtocolV2DeviceFileHeader(path: string) {
|
|
668
|
+
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
669
|
+
const filePath = this.getProtocolV2ResourceFilePath(path);
|
|
670
|
+
const pathInfoRes = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
|
|
671
|
+
path: filePath,
|
|
672
|
+
});
|
|
673
|
+
const fileSize = toProtocolV2FiniteNumber(pathInfoRes.message?.size);
|
|
674
|
+
if (
|
|
675
|
+
!pathInfoRes.message?.exist ||
|
|
676
|
+
pathInfoRes.message?.directory ||
|
|
677
|
+
fileSize === undefined ||
|
|
678
|
+
fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE
|
|
679
|
+
) {
|
|
680
|
+
return null;
|
|
231
681
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
682
|
+
|
|
683
|
+
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
684
|
+
const chunks: Uint8Array[] = [];
|
|
685
|
+
let offset = 0;
|
|
686
|
+
while (offset < PROTOCOL_V2_OKPP_HEADER_SIZE) {
|
|
687
|
+
const readLen = Math.min(chunkSize, PROTOCOL_V2_OKPP_HEADER_SIZE - offset);
|
|
688
|
+
const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
|
|
689
|
+
file: {
|
|
690
|
+
path: filePath,
|
|
691
|
+
offset,
|
|
692
|
+
total_size: 0,
|
|
693
|
+
},
|
|
694
|
+
chunk_len: readLen,
|
|
695
|
+
ui_percentage: undefined,
|
|
696
|
+
});
|
|
697
|
+
const data = toProtocolV2Bytes(res.message?.data);
|
|
698
|
+
if (data.byteLength === 0) return null;
|
|
699
|
+
chunks.push(data);
|
|
700
|
+
offset += data.byteLength;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const headerBytes = new Uint8Array(offset);
|
|
704
|
+
let cursor = 0;
|
|
705
|
+
chunks.forEach(chunk => {
|
|
706
|
+
headerBytes.set(chunk, cursor);
|
|
707
|
+
cursor += chunk.byteLength;
|
|
236
708
|
});
|
|
709
|
+
return parseProtocolV2OkppHeader(headerBytes);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
private shouldInstallRemoteProtocolV2Component(
|
|
713
|
+
release: IFirmwareReleaseInfo,
|
|
714
|
+
key: string,
|
|
715
|
+
component: IProtocolV2FirmwareComponent,
|
|
716
|
+
target: ProtocolV2RemoteComponentTarget,
|
|
717
|
+
features: Features
|
|
718
|
+
) {
|
|
719
|
+
const versionSatisfied = this.isProtocolV2ComponentVersionSatisfied(
|
|
720
|
+
release,
|
|
721
|
+
component,
|
|
722
|
+
target,
|
|
723
|
+
features
|
|
724
|
+
);
|
|
725
|
+
if (versionSatisfied) {
|
|
726
|
+
Log.log(`[FirmwareUpdateV4] skip Protocol V2 component ${key}; version is up to date`);
|
|
727
|
+
}
|
|
728
|
+
return !versionSatisfied;
|
|
729
|
+
}
|
|
237
730
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
731
|
+
private async downloadRemoteProtocolV2Component(
|
|
732
|
+
key: string,
|
|
733
|
+
component: IProtocolV2FirmwareComponent
|
|
734
|
+
): Promise<ProtocolV2RemoteComponentBinary> {
|
|
735
|
+
const target = this.getRemoteComponentTarget(key, component);
|
|
736
|
+
if (!component.url) {
|
|
737
|
+
throw ERRORS.TypedError(
|
|
738
|
+
HardwareErrorCode.RuntimeError,
|
|
739
|
+
`Missing Protocol V2 firmware component url: ${key}/${component.target}`
|
|
740
|
+
);
|
|
241
741
|
}
|
|
242
|
-
|
|
243
|
-
|
|
742
|
+
|
|
743
|
+
const { binary } = await getSysResourceBinary(component.url);
|
|
744
|
+
return {
|
|
745
|
+
...target,
|
|
746
|
+
binary,
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
private async prepareRemoteProtocolV2Binaries(firmwareType: EFirmwareType, features: Features) {
|
|
751
|
+
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
752
|
+
|
|
753
|
+
let bootloaderBinary: ArrayBuffer | null = null;
|
|
754
|
+
const fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
755
|
+
const installItems: ProtocolV2InstallItem[] = [];
|
|
756
|
+
|
|
757
|
+
if (!release) {
|
|
758
|
+
return {
|
|
759
|
+
bootloaderBinary,
|
|
760
|
+
fwBinaryMap,
|
|
761
|
+
installItems,
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
const entries = this.getRemoteComponentEntries(release);
|
|
766
|
+
|
|
767
|
+
for (const [key, component] of entries) {
|
|
768
|
+
const target = this.getRemoteComponentTarget(key, component);
|
|
769
|
+
const shouldInstall = this.shouldInstallRemoteProtocolV2Component(
|
|
770
|
+
release,
|
|
771
|
+
key,
|
|
772
|
+
component,
|
|
773
|
+
target,
|
|
774
|
+
features
|
|
775
|
+
);
|
|
776
|
+
if (shouldInstall) {
|
|
777
|
+
const remoteBinary = await this.downloadRemoteProtocolV2Component(key, component);
|
|
778
|
+
if (remoteBinary.kind === 'bootloader') {
|
|
779
|
+
bootloaderBinary = remoteBinary.binary;
|
|
780
|
+
installItems.push({
|
|
781
|
+
fileName: remoteBinary.fileName,
|
|
782
|
+
binary: remoteBinary.binary,
|
|
783
|
+
targetId: remoteBinary.targetId,
|
|
784
|
+
kind: remoteBinary.kind,
|
|
785
|
+
});
|
|
786
|
+
} else {
|
|
787
|
+
const binaryEntry = {
|
|
788
|
+
fileName: remoteBinary.fileName,
|
|
789
|
+
binary: remoteBinary.binary,
|
|
790
|
+
targetId: remoteBinary.targetId,
|
|
791
|
+
};
|
|
792
|
+
fwBinaryMap.push(binaryEntry);
|
|
793
|
+
installItems.push({ ...binaryEntry, kind: remoteBinary.kind });
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
return {
|
|
799
|
+
bootloaderBinary,
|
|
800
|
+
fwBinaryMap,
|
|
801
|
+
installItems,
|
|
802
|
+
};
|
|
244
803
|
}
|
|
245
804
|
|
|
246
|
-
|
|
805
|
+
// ============================================================
|
|
806
|
+
// RESC bundle 直写增量同步(FileRead 比对 + FilesystemFileWrite 直写)
|
|
807
|
+
// ============================================================
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* 准备 RESC bundle 列表。
|
|
811
|
+
*
|
|
812
|
+
* 两种模式(同 FirmwareUpdateV3 的 binary vs version 模式):
|
|
813
|
+
* - 用户传了 resourceBundleFiles(binary 数组):直接用,不做版本比对。
|
|
814
|
+
* - 用户没传:从远端 config.json 的 release.resourceBundles 拉取,
|
|
815
|
+
* 此时 syncProtocolV2ResourceBundles 会按需下载 + 比对设备已有 header 跳过。
|
|
816
|
+
*/
|
|
817
|
+
private prepareProtocolV2ResourceBundles(
|
|
247
818
|
firmwareType: EFirmwareType,
|
|
248
819
|
features: Features
|
|
249
|
-
):
|
|
250
|
-
|
|
251
|
-
|
|
820
|
+
): ProtocolV2ResourceBundleBinary[] | undefined {
|
|
821
|
+
// 模式1:用户手动传入 binary,直接安装,不比对
|
|
822
|
+
if (this.params.resourceBundleFiles?.length) {
|
|
823
|
+
return this.params.resourceBundleFiles.map(file => ({
|
|
824
|
+
name: file.devicePath.split('/').pop() ?? file.devicePath,
|
|
825
|
+
binary: file.binary,
|
|
826
|
+
devicePath: file.devicePath,
|
|
827
|
+
}));
|
|
252
828
|
}
|
|
253
829
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
830
|
+
// 模式2:远端配置模式,后续按需下载 + 比对
|
|
831
|
+
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
832
|
+
if (!release?.resourceBundles?.length) return undefined;
|
|
833
|
+
|
|
834
|
+
return release.resourceBundles.map(bundle => ({
|
|
835
|
+
name: bundle.name,
|
|
836
|
+
binary: new ArrayBuffer(0),
|
|
837
|
+
devicePath: bundle.devicePath,
|
|
838
|
+
url: bundle.url,
|
|
839
|
+
version: bundle.version,
|
|
840
|
+
payloadHash: bundle.payloadHash,
|
|
841
|
+
headerHash: bundle.headerHash,
|
|
842
|
+
})) as ProtocolV2ResourceBundleBinary[];
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* 同步 RESC bundles 到设备。
|
|
847
|
+
*
|
|
848
|
+
* 手动传入模式(有 binary):直接 FileWrite 直写,不比对。
|
|
849
|
+
* 远端配置模式(无 binary,有 url):按需下载 + FileRead 比对跳过已更新的。
|
|
850
|
+
*/
|
|
851
|
+
private async syncProtocolV2ResourceBundles(
|
|
852
|
+
bundles: ProtocolV2ResourceBundleBinary[],
|
|
853
|
+
firmwareSize: number
|
|
854
|
+
): Promise<{ processedSize: number; totalSize: number }> {
|
|
855
|
+
const transferStartTime = Date.now();
|
|
856
|
+
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
857
|
+
|
|
858
|
+
const isManualMode = bundles.every(b => b.binary.byteLength > 0);
|
|
859
|
+
let bundlesToSync = bundles;
|
|
860
|
+
|
|
861
|
+
// 远端配置模式:按需下载 + 比对跳过
|
|
862
|
+
if (!isManualMode) {
|
|
863
|
+
const filtered: ProtocolV2ResourceBundleBinary[] = [];
|
|
864
|
+
for (const bundle of bundles) {
|
|
865
|
+
// 下载 binary
|
|
866
|
+
if (bundle.binary.byteLength === 0 && bundle.url) {
|
|
867
|
+
Log.log(`[FirmwareUpdateV4] downloading RESC bundle ${bundle.name} from ${bundle.url}`);
|
|
868
|
+
const { binary } = await getSysResourceBinary(bundle.url);
|
|
869
|
+
bundle.binary = binary;
|
|
870
|
+
}
|
|
871
|
+
// 比对设备上已有 header
|
|
872
|
+
const upToDate = await this.isProtocolV2ResourceBundleUpToDate(bundle);
|
|
873
|
+
if (upToDate) {
|
|
874
|
+
Log.log(`[FirmwareUpdateV4] skip RESC bundle ${bundle.name}; already up to date`);
|
|
875
|
+
} else {
|
|
876
|
+
filtered.push(bundle);
|
|
877
|
+
}
|
|
259
878
|
}
|
|
879
|
+
bundlesToSync = filtered;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
if (bundlesToSync.length === 0) {
|
|
883
|
+
Log.log('[FirmwareUpdateV4] all RESC bundles up to date, nothing to sync');
|
|
884
|
+
return { processedSize: 0, totalSize: firmwareSize };
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// FileWrite 直写到设备
|
|
888
|
+
let totalSize = 0;
|
|
889
|
+
for (const b of bundlesToSync) totalSize += b.binary.byteLength;
|
|
890
|
+
|
|
891
|
+
const transferTotalSize = totalSize + firmwareSize;
|
|
892
|
+
let processedSize = 0;
|
|
893
|
+
for (const bundle of bundlesToSync) {
|
|
894
|
+
Log.log(
|
|
895
|
+
`[FirmwareUpdateV4] syncing RESC bundle ${bundle.name} -> ${bundle.devicePath} bytes=${bundle.binary.byteLength}`
|
|
896
|
+
);
|
|
897
|
+
processedSize = await this.protocolV2CommonUpdateProcess({
|
|
898
|
+
payload: bundle.binary,
|
|
899
|
+
filePath: bundle.devicePath,
|
|
900
|
+
processedSize,
|
|
901
|
+
totalSize: transferTotalSize,
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
const elapsedMs = Date.now() - transferStartTime;
|
|
906
|
+
Log.log(
|
|
907
|
+
`[FirmwareUpdateV4] RESC bundle sync finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
|
|
908
|
+
elapsedMs / 1000
|
|
909
|
+
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
|
|
910
|
+
);
|
|
911
|
+
return { processedSize, totalSize: transferTotalSize };
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/**
|
|
915
|
+
* 比对设备上已有 okpkg 的 OKPP header(仅远端配置模式使用)。
|
|
916
|
+
*/
|
|
917
|
+
private async isProtocolV2ResourceBundleUpToDate(
|
|
918
|
+
bundle: ProtocolV2ResourceBundleBinary
|
|
919
|
+
): Promise<boolean> {
|
|
920
|
+
if (this.params.forcedUpdateRes) return false;
|
|
921
|
+
if (!bundle.version && !bundle.payloadHash) return false;
|
|
922
|
+
|
|
923
|
+
try {
|
|
924
|
+
const header = await this.readProtocolV2DeviceFileHeader(bundle.devicePath);
|
|
925
|
+
if (!header) return false;
|
|
926
|
+
|
|
927
|
+
if (bundle.version) {
|
|
928
|
+
const cmp = compareProtocolV2Versions(header.version, bundle.version);
|
|
929
|
+
if (cmp === undefined || cmp !== 0) return false;
|
|
930
|
+
}
|
|
931
|
+
if (bundle.payloadHash) {
|
|
932
|
+
const expected = normalizeProtocolV2Hex(bundle.payloadHash);
|
|
933
|
+
if (expected && header.payloadHash !== expected) return false;
|
|
934
|
+
}
|
|
935
|
+
if (bundle.headerHash) {
|
|
936
|
+
const expected = normalizeProtocolV2Hex(bundle.headerHash);
|
|
937
|
+
if (expected && header.headerHash !== expected) return false;
|
|
938
|
+
}
|
|
939
|
+
return true;
|
|
940
|
+
} catch (error) {
|
|
941
|
+
Log.log(`[FirmwareUpdateV4] RESC bundle ${bundle.name} header check failed: `, error);
|
|
942
|
+
return false;
|
|
260
943
|
}
|
|
261
|
-
return null;
|
|
262
944
|
}
|
|
263
945
|
|
|
264
946
|
private isProtocolV2BootloaderMode() {
|
|
@@ -270,18 +952,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
270
952
|
|
|
271
953
|
async enterProtocolV2BootloaderMode() {
|
|
272
954
|
if (this.isProtocolV2BootloaderMode()) {
|
|
955
|
+
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot to bootloader');
|
|
273
956
|
return false;
|
|
274
957
|
}
|
|
275
958
|
|
|
276
959
|
try {
|
|
277
960
|
this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
|
|
278
|
-
await this.protocolV2Reboot(
|
|
961
|
+
await this.protocolV2Reboot(DeviceRebootType.Bootloader);
|
|
279
962
|
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
280
|
-
|
|
281
|
-
await this.
|
|
282
|
-
this.checkPromise = null;
|
|
283
|
-
await wait(1500);
|
|
284
|
-
await this.device.acquire?.();
|
|
963
|
+
await wait(1000);
|
|
964
|
+
await this.waitForProtocolV2BootloaderMode();
|
|
285
965
|
return true;
|
|
286
966
|
} catch (error) {
|
|
287
967
|
if (error instanceof HardwareError) {
|
|
@@ -292,160 +972,208 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
292
972
|
}
|
|
293
973
|
}
|
|
294
974
|
|
|
975
|
+
private async waitForProtocolV2BootloaderMode(
|
|
976
|
+
timeout = PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT,
|
|
977
|
+
retryInterval = 1000
|
|
978
|
+
) {
|
|
979
|
+
const startTime = Date.now();
|
|
980
|
+
let lastError: unknown;
|
|
981
|
+
|
|
982
|
+
while (Date.now() - startTime < timeout) {
|
|
983
|
+
try {
|
|
984
|
+
await this.reconnectProtocolV2Device();
|
|
985
|
+
const deviceInfo = await requestProtocolV2DeviceInfo({
|
|
986
|
+
commands: this.device.getCommands(),
|
|
987
|
+
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
988
|
+
});
|
|
989
|
+
const features = this.device.updateProtocolV2Features(deviceInfo);
|
|
990
|
+
if (features?.bootloaderMode) {
|
|
991
|
+
return features;
|
|
992
|
+
}
|
|
993
|
+
lastError = new Error('Protocol V2 device is reachable but is not in bootloader mode');
|
|
994
|
+
} catch (error) {
|
|
995
|
+
lastError = error;
|
|
996
|
+
Log.log('Protocol V2 bootloader mode not ready, polling reconnect: ', error);
|
|
997
|
+
}
|
|
998
|
+
await wait(retryInterval);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
throw ERRORS.TypedError(
|
|
1002
|
+
HardwareErrorCode.FirmwareUpdateAutoEnterBootFailure,
|
|
1003
|
+
`Protocol V2 bootloader not ready within ${timeout / 1000}s: ${this.normalizeErrorMessage(
|
|
1004
|
+
lastError
|
|
1005
|
+
)}`
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
295
1009
|
/**
|
|
296
|
-
* 收集按
|
|
1010
|
+
* 收集按 DeviceFirmwareTargetType 拆分的显式目标二进制。
|
|
297
1011
|
* 文件名仅用于 staging 路径展示,target_id 已显式给定。
|
|
298
1012
|
*/
|
|
299
1013
|
private collectExplicitTargetBinaries() {
|
|
300
|
-
const entries:
|
|
1014
|
+
const entries: ProtocolV2TargetBinary[] = [];
|
|
301
1015
|
const push = (binary: ArrayBuffer | undefined, fileName: string, targetId: number) => {
|
|
302
1016
|
if (binary) entries.push({ fileName, binary, targetId });
|
|
303
1017
|
};
|
|
304
1018
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
1019
|
+
if (this.params.romloaderBinary) {
|
|
1020
|
+
throw ERRORS.TypedError(
|
|
1021
|
+
HardwareErrorCode.RuntimeError,
|
|
1022
|
+
PROTOCOL_V2_ROMLOADER_UNSUPPORTED_MESSAGE
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
310
1025
|
push(
|
|
311
1026
|
this.params.applicationP1Binary,
|
|
312
1027
|
'application_p1.bin',
|
|
313
|
-
ProtocolV2FirmwareTargetType.
|
|
1028
|
+
ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1
|
|
314
1029
|
);
|
|
315
1030
|
push(
|
|
316
1031
|
this.params.applicationP2Binary,
|
|
317
1032
|
'application_p2.bin',
|
|
318
|
-
ProtocolV2FirmwareTargetType.
|
|
1033
|
+
ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2
|
|
319
1034
|
);
|
|
320
|
-
push(
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
1035
|
+
push(
|
|
1036
|
+
this.params.coprocessorBinary,
|
|
1037
|
+
'coprocessor.bin',
|
|
1038
|
+
ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR
|
|
1039
|
+
);
|
|
1040
|
+
push(this.params.se01Binary, 'se01.bin', ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE01);
|
|
1041
|
+
push(this.params.se02Binary, 'se02.bin', ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE02);
|
|
1042
|
+
push(this.params.se03Binary, 'se03.bin', ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE03);
|
|
1043
|
+
push(this.params.se04Binary, 'se04.bin', ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04);
|
|
325
1044
|
return entries;
|
|
326
1045
|
}
|
|
327
1046
|
|
|
328
|
-
private async prepareFirmwareAndBleBinary(firmwareType: EFirmwareType, features: Features) {
|
|
329
|
-
const fwBinaryMap: { fileName: string; binary: ArrayBuffer; targetId?: number }[] = [];
|
|
330
|
-
if (this.params.firmwareBinary) {
|
|
331
|
-
fwBinaryMap.push({
|
|
332
|
-
fileName: 'firmware.bin',
|
|
333
|
-
binary: this.params.firmwareBinary,
|
|
334
|
-
});
|
|
335
|
-
} else if (this.params.firmwareVersion) {
|
|
336
|
-
const firmwareBinary = (
|
|
337
|
-
await getBinary({
|
|
338
|
-
features,
|
|
339
|
-
version: this.params.firmwareVersion,
|
|
340
|
-
updateType: 'firmware',
|
|
341
|
-
isUpdateBootloader: false,
|
|
342
|
-
firmwareType,
|
|
343
|
-
})
|
|
344
|
-
).binary;
|
|
345
|
-
fwBinaryMap.push({
|
|
346
|
-
fileName: 'firmware.bin',
|
|
347
|
-
binary: firmwareBinary,
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (this.params.bleBinary) {
|
|
352
|
-
fwBinaryMap.push({
|
|
353
|
-
fileName: 'ble-firmware.bin',
|
|
354
|
-
binary: this.params.bleBinary,
|
|
355
|
-
});
|
|
356
|
-
} else if (this.params.bleVersion) {
|
|
357
|
-
const bleBinary = await getBinary({
|
|
358
|
-
features,
|
|
359
|
-
version: this.params.bleVersion,
|
|
360
|
-
updateType: 'ble',
|
|
361
|
-
firmwareType,
|
|
362
|
-
});
|
|
363
|
-
fwBinaryMap.push({
|
|
364
|
-
fileName: 'ble-firmware.bin',
|
|
365
|
-
binary: bleBinary.binary,
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
return fwBinaryMap;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
1047
|
private async executeProtocolV2Update({
|
|
373
|
-
resourceBinary,
|
|
374
1048
|
fwBinaryMap,
|
|
375
1049
|
bootloaderBinary,
|
|
1050
|
+
installItems,
|
|
1051
|
+
resourceBundles,
|
|
376
1052
|
}: {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
1053
|
+
fwBinaryMap?: ProtocolV2TargetBinary[];
|
|
1054
|
+
bootloaderBinary?: ArrayBuffer | null;
|
|
1055
|
+
installItems?: ProtocolV2InstallItem[];
|
|
1056
|
+
resourceBundles?: ProtocolV2ResourceBundleBinary[];
|
|
380
1057
|
}) {
|
|
381
|
-
|
|
382
|
-
|
|
1058
|
+
const orderedInstallItems =
|
|
1059
|
+
installItems ??
|
|
1060
|
+
this.buildProtocolV2InstallItems({
|
|
1061
|
+
bootloaderBinary: bootloaderBinary ?? null,
|
|
1062
|
+
fwBinaryMap: fwBinaryMap ?? [],
|
|
1063
|
+
});
|
|
383
1064
|
|
|
384
|
-
|
|
385
|
-
for (const
|
|
386
|
-
if (bootloaderBinary) totalSize += bootloaderBinary.byteLength;
|
|
1065
|
+
let firmwareSize = 0;
|
|
1066
|
+
for (const item of orderedInstallItems) firmwareSize += item.binary.byteLength;
|
|
387
1067
|
|
|
388
1068
|
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
389
1069
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
if (
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
totalSize,
|
|
400
|
-
});
|
|
401
|
-
targets.push({
|
|
402
|
-
target_id: ProtocolV2FirmwareTargetType.TARGET_RESOURCE,
|
|
403
|
-
path: resourceFilePath,
|
|
404
|
-
});
|
|
1070
|
+
let processedSize = 0;
|
|
1071
|
+
let totalSize = firmwareSize;
|
|
1072
|
+
if (resourceBundles?.length) {
|
|
1073
|
+
const resourceTransfer = await this.syncProtocolV2ResourceBundles(
|
|
1074
|
+
resourceBundles,
|
|
1075
|
+
firmwareSize
|
|
1076
|
+
);
|
|
1077
|
+
processedSize = resourceTransfer.processedSize;
|
|
1078
|
+
totalSize = resourceTransfer.totalSize;
|
|
405
1079
|
}
|
|
406
1080
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
});
|
|
415
|
-
targets.push({
|
|
416
|
-
target_id: ProtocolV2FirmwareTargetType.TARGET_MAIN_BOOT,
|
|
417
|
-
path: bootloaderPath,
|
|
418
|
-
});
|
|
1081
|
+
// 只有 RESC bundle、没有固件 target 时跳过 staging/install 阶段
|
|
1082
|
+
if (orderedInstallItems.length === 0) {
|
|
1083
|
+
Log.log('[FirmwareUpdateV4] no firmware targets to install (RESC bundles only)');
|
|
1084
|
+
if (totalSize > 0) {
|
|
1085
|
+
this.postProgressMessage(100, 'transferData');
|
|
1086
|
+
}
|
|
1087
|
+
return;
|
|
419
1088
|
}
|
|
1089
|
+
let transferredSize = processedSize;
|
|
1090
|
+
const transferStartTime = Date.now();
|
|
1091
|
+
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
1092
|
+
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
1093
|
+
const onTransferredBytes = (bytes: number) => {
|
|
1094
|
+
transferredSize = bytes;
|
|
1095
|
+
};
|
|
1096
|
+
Log.log(
|
|
1097
|
+
`[FirmwareUpdateV4] transfer started transport=${transferTransport} total=${totalSize} bytes chunk=${chunkSize} bytes`
|
|
1098
|
+
);
|
|
420
1099
|
|
|
421
|
-
|
|
422
|
-
const firmwarePath = `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${fwbinary.fileName}`;
|
|
423
|
-
processedSize = await this.protocolV2CommonUpdateProcess({
|
|
424
|
-
payload: fwbinary.binary,
|
|
425
|
-
filePath: firmwarePath,
|
|
426
|
-
processedSize,
|
|
427
|
-
totalSize,
|
|
428
|
-
});
|
|
429
|
-
targets.push({
|
|
430
|
-
target_id: fwbinary.targetId ?? protocolV2FileNameToTargetId(fwbinary.fileName),
|
|
431
|
-
path: firmwarePath,
|
|
432
|
-
});
|
|
433
|
-
}
|
|
1100
|
+
const stagedInstallTargets: ProtocolV2InstallTarget[] = [];
|
|
434
1101
|
|
|
435
|
-
|
|
436
|
-
|
|
1102
|
+
try {
|
|
1103
|
+
for (const item of orderedInstallItems) {
|
|
1104
|
+
const filePath = this.getProtocolV2InstallItemStagingPath(item);
|
|
1105
|
+
Log.log(
|
|
1106
|
+
`[FirmwareUpdateV4] staging ${item.kind} via FilesystemFileWrite target=${item.targetId} path=${filePath} source=${item.fileName} bytes=${item.binary.byteLength}`
|
|
1107
|
+
);
|
|
1108
|
+
processedSize = await this.protocolV2CommonUpdateProcess({
|
|
1109
|
+
payload: item.binary,
|
|
1110
|
+
filePath,
|
|
1111
|
+
processedSize,
|
|
1112
|
+
totalSize,
|
|
1113
|
+
onTransferredBytes,
|
|
1114
|
+
});
|
|
1115
|
+
transferredSize = processedSize;
|
|
1116
|
+
await this.verifyProtocolV2StagedFile(filePath, item.binary.byteLength);
|
|
1117
|
+
|
|
1118
|
+
stagedInstallTargets.push({
|
|
1119
|
+
...item,
|
|
1120
|
+
path: filePath,
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
if (totalSize > 0) {
|
|
1125
|
+
this.postProgressMessage(100, 'transferData');
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
const elapsedMs = Date.now() - transferStartTime;
|
|
1129
|
+
Log.log(
|
|
1130
|
+
`[FirmwareUpdateV4] transfer finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
|
|
1131
|
+
elapsedMs / 1000
|
|
1132
|
+
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
|
|
1133
|
+
);
|
|
1134
|
+
} catch (error) {
|
|
1135
|
+
const elapsedMs = Date.now() - transferStartTime;
|
|
1136
|
+
Log.warn(
|
|
1137
|
+
`[FirmwareUpdateV4] transfer failed transport=${transferTransport} bytes=${transferredSize}/${totalSize} elapsed=${(
|
|
1138
|
+
elapsedMs / 1000
|
|
1139
|
+
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(transferredSize, elapsedMs)} KB/s`
|
|
1140
|
+
);
|
|
1141
|
+
throw error;
|
|
437
1142
|
}
|
|
438
1143
|
|
|
439
1144
|
this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
|
|
440
|
-
|
|
441
|
-
|
|
1145
|
+
|
|
1146
|
+
const allTargets = stagedInstallTargets.map(item => ({
|
|
1147
|
+
target_id: item.targetId,
|
|
1148
|
+
path: item.path,
|
|
1149
|
+
}));
|
|
1150
|
+
Log.log(`[FirmwareUpdateV4] DeviceFirmwareUpdateRequest targets=${JSON.stringify(allTargets)}`);
|
|
1151
|
+
const startResponse = await this.protocolV2StartFirmwareUpdate({ targets: allTargets });
|
|
1152
|
+
await this.waitForProtocolV2FirmwareUpdateComplete(allTargets, startResponse);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
private getProtocolV2InstallItemStagingPath(item: ProtocolV2InstallItem) {
|
|
1156
|
+
return `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${item.fileName}`;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
private async verifyProtocolV2StagedFile(path: string, expectedSize: number) {
|
|
1160
|
+
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
1161
|
+
const response = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', { path });
|
|
1162
|
+
const actualSize = toProtocolV2FiniteNumber(response.message?.size);
|
|
1163
|
+
if (!response.message?.exist || response.message?.directory || actualSize !== expectedSize) {
|
|
1164
|
+
throw ERRORS.TypedError(
|
|
1165
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1166
|
+
`staged file verification failed: path=${path} exist=${!!response.message
|
|
1167
|
+
?.exist} expected=${expectedSize} actual=${actualSize ?? 'unknown'}`
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
442
1170
|
}
|
|
443
1171
|
|
|
444
1172
|
private async queryProtocolV2FirmwareUpdateStatus() {
|
|
445
1173
|
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
446
1174
|
return typedCall(
|
|
447
|
-
'
|
|
448
|
-
'
|
|
1175
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
1176
|
+
'DeviceFirmwareUpdateStatus',
|
|
449
1177
|
{},
|
|
450
1178
|
{
|
|
451
1179
|
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
@@ -465,14 +1193,32 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
465
1193
|
);
|
|
466
1194
|
}
|
|
467
1195
|
|
|
1196
|
+
private isProtocolV2NormalModeFeatures(features?: Features | null) {
|
|
1197
|
+
return !!features && !features.bootloaderMode && features.mode !== 'bootloader';
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
private async probeProtocolV2NormalMode() {
|
|
1201
|
+
const deviceInfo = await requestProtocolV2DeviceInfo({
|
|
1202
|
+
commands: this.device.getCommands(),
|
|
1203
|
+
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
1204
|
+
request: PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
|
|
1205
|
+
});
|
|
1206
|
+
const features = this.device.updateProtocolV2Features(deviceInfo);
|
|
1207
|
+
if (this.isProtocolV2NormalModeFeatures(features)) {
|
|
1208
|
+
Log.log('Protocol V2 firmware install finished; device is back in normal mode');
|
|
1209
|
+
return true;
|
|
1210
|
+
}
|
|
1211
|
+
return false;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
468
1214
|
private assertProtocolV2TargetStatus(
|
|
469
1215
|
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
470
1216
|
expectedTargetIds: Set<number>
|
|
471
1217
|
) {
|
|
472
1218
|
const failedTarget = statusTargets.find(
|
|
473
1219
|
target =>
|
|
474
|
-
expectedTargetIds.has(target.target_id) &&
|
|
475
|
-
target.status
|
|
1220
|
+
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1) &&
|
|
1221
|
+
isProtocolV2TargetStatusFailed(target.status)
|
|
476
1222
|
);
|
|
477
1223
|
if (failedTarget) {
|
|
478
1224
|
throw ERRORS.TypedError(
|
|
@@ -483,8 +1229,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
483
1229
|
|
|
484
1230
|
const completedTargets = statusTargets.filter(
|
|
485
1231
|
target =>
|
|
486
|
-
expectedTargetIds.has(target.target_id) &&
|
|
487
|
-
target.status
|
|
1232
|
+
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1) &&
|
|
1233
|
+
isProtocolV2TargetStatusFinished(target.status)
|
|
488
1234
|
);
|
|
489
1235
|
if (completedTargets.length === expectedTargetIds.size && expectedTargetIds.size > 0) {
|
|
490
1236
|
return true;
|
|
@@ -492,8 +1238,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
492
1238
|
|
|
493
1239
|
const inProgressTarget = statusTargets.find(
|
|
494
1240
|
target =>
|
|
495
|
-
expectedTargetIds.has(target.target_id) &&
|
|
496
|
-
target.status
|
|
1241
|
+
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1) &&
|
|
1242
|
+
isProtocolV2TargetStatusInProgress(target.status)
|
|
497
1243
|
);
|
|
498
1244
|
if (inProgressTarget) {
|
|
499
1245
|
this.postProgressMessage(99, 'installingFirmware');
|
|
@@ -507,11 +1253,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
507
1253
|
startResponse?: ProtocolV2FirmwareUpdateStartResponse
|
|
508
1254
|
) {
|
|
509
1255
|
const expectedTargetIds = new Set(targets.map(target => target.target_id));
|
|
510
|
-
if (startResponse?.type === '
|
|
511
|
-
|
|
512
|
-
}
|
|
513
|
-
if (startResponse?.type === 'DevFirmwareUpdateStatus') {
|
|
514
|
-
const statusTargets = (startResponse.message.targets ??
|
|
1256
|
+
if (startResponse?.type === 'DeviceFirmwareUpdateStatus') {
|
|
1257
|
+
const statusTargets = (startResponse.message.records ??
|
|
515
1258
|
[]) as ProtocolV2FirmwareUpdateStatusTarget[];
|
|
516
1259
|
if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds)) {
|
|
517
1260
|
return;
|
|
@@ -524,7 +1267,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
524
1267
|
while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
|
|
525
1268
|
try {
|
|
526
1269
|
const statusRes = await this.queryProtocolV2FirmwareUpdateStatus();
|
|
527
|
-
const statusTargets = (statusRes.message.
|
|
1270
|
+
const statusTargets = (statusRes.message.records ??
|
|
528
1271
|
[]) as ProtocolV2FirmwareUpdateStatusTarget[];
|
|
529
1272
|
if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds)) {
|
|
530
1273
|
return;
|
|
@@ -538,17 +1281,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
538
1281
|
if (isProtocolV2PollingTransientError(error)) {
|
|
539
1282
|
try {
|
|
540
1283
|
await this.reconnectProtocolV2Device();
|
|
1284
|
+
if (await this.probeProtocolV2NormalMode()) {
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
541
1287
|
} catch (reconnectError) {
|
|
542
1288
|
lastError = reconnectError;
|
|
543
1289
|
Log.log(
|
|
544
|
-
'Protocol V2 firmware install reconnect/
|
|
1290
|
+
'Protocol V2 firmware install reconnect/normal-mode probe failed: ',
|
|
545
1291
|
reconnectError
|
|
546
1292
|
);
|
|
547
1293
|
}
|
|
548
1294
|
try {
|
|
549
1295
|
await this.pingProtocolV2Device();
|
|
550
1296
|
Log.log('Protocol V2 firmware status unavailable, Ping is ready');
|
|
551
|
-
return;
|
|
552
1297
|
} catch (pingError) {
|
|
553
1298
|
lastError = pingError;
|
|
554
1299
|
Log.log('Protocol V2 firmware install Ping polling failed: ', pingError);
|
|
@@ -566,7 +1311,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
566
1311
|
|
|
567
1312
|
private async exitProtocolV2BootloaderToNormal() {
|
|
568
1313
|
this.postTipMessage(FirmwareUpdateTipMessage.SwitchFirmwareReconnectDevice);
|
|
569
|
-
|
|
1314
|
+
try {
|
|
1315
|
+
await this.reconnectProtocolV2Device();
|
|
1316
|
+
if (await this.probeProtocolV2NormalMode()) {
|
|
1317
|
+
Log.log('Protocol V2 device is already in normal mode, skip normal reboot');
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
} catch (error) {
|
|
1321
|
+
Log.log('Protocol V2 normal-mode probe before reboot failed: ', error);
|
|
1322
|
+
}
|
|
1323
|
+
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
570
1324
|
}
|
|
571
1325
|
|
|
572
1326
|
private async waitForProtocolV2FinalFeatures() {
|
|
@@ -603,7 +1357,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
603
1357
|
// 更新完成判定只需要各 target 版本号;scope 与请求内容保持一致
|
|
604
1358
|
request: PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
|
|
605
1359
|
});
|
|
606
|
-
|
|
1360
|
+
const features = this.device.updateProtocolV2Features(deviceInfo);
|
|
1361
|
+
if (features.bootloaderMode || features.mode === 'bootloader') {
|
|
1362
|
+
throw ERRORS.TypedError(
|
|
1363
|
+
HardwareErrorCode.DeviceNotFound,
|
|
1364
|
+
'Protocol V2 device is still in bootloader mode'
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1367
|
+
return features;
|
|
607
1368
|
} catch (error) {
|
|
608
1369
|
lastError = error;
|
|
609
1370
|
Log.log('Protocol V2 normal mode not ready, polling Ping: ', error);
|
|
@@ -628,27 +1389,75 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
628
1389
|
const deviceDiff = await this.device.deviceConnector?.enumerate();
|
|
629
1390
|
const devicesDescriptor = deviceDiff?.descriptors ?? [];
|
|
630
1391
|
|
|
631
|
-
|
|
1392
|
+
if (
|
|
1393
|
+
DataManager.isBrowserWebUsb(DataManager.getSettings('env')) &&
|
|
1394
|
+
devicesDescriptor.length === 1
|
|
1395
|
+
) {
|
|
1396
|
+
this.device.updateDescriptor(
|
|
1397
|
+
{
|
|
1398
|
+
...devicesDescriptor[0],
|
|
1399
|
+
protocolType: PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
1400
|
+
},
|
|
1401
|
+
true
|
|
1402
|
+
);
|
|
1403
|
+
await this.device.acquire(PROTOCOL_V2_CONNECT_PROTOCOL, { throwOnRunPromiseError: true });
|
|
1404
|
+
this.device.commands.disposed = false;
|
|
1405
|
+
this.device.getCommands().mainId = this.device.mainId ?? '';
|
|
1406
|
+
await this.device.initialize();
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
// App 与 bootloader 序列号暂时可能不一致。V4 升级重连阶段只接受唯一枚举设备,
|
|
1411
|
+
// 避免继续按旧 app connectId 查缓存导致反复输出 path mismatch 日志。
|
|
1412
|
+
const { deviceList } = await DevicePool.getDevices(devicesDescriptor, undefined, {
|
|
1413
|
+
connectProtocol: PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
1414
|
+
});
|
|
632
1415
|
if (deviceList.length !== 1) {
|
|
633
1416
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
634
1417
|
}
|
|
635
1418
|
|
|
1419
|
+
Log.debug(
|
|
1420
|
+
'Protocol V2 firmware reconnect using single enumerated device:',
|
|
1421
|
+
deviceList[0].getConnectId()
|
|
1422
|
+
);
|
|
636
1423
|
this.device.updateFromCache(deviceList[0]);
|
|
637
|
-
await this.device.acquire();
|
|
1424
|
+
await this.device.acquire(PROTOCOL_V2_CONNECT_PROTOCOL, { throwOnRunPromiseError: true });
|
|
638
1425
|
this.device.commands.disposed = false;
|
|
639
1426
|
this.device.getCommands().mainId = this.device.mainId ?? '';
|
|
1427
|
+
await this.device.initialize();
|
|
640
1428
|
}
|
|
641
1429
|
|
|
642
|
-
private async protocolV2CommonUpdateProcess({
|
|
1430
|
+
private async protocolV2CommonUpdateProcess(params: ProtocolV2FileTransferParams) {
|
|
1431
|
+
let lastError: unknown;
|
|
1432
|
+
for (let attempt = 1; attempt <= PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT; attempt += 1) {
|
|
1433
|
+
try {
|
|
1434
|
+
return await this.protocolV2WriteWholeFile(params);
|
|
1435
|
+
} catch (error) {
|
|
1436
|
+
lastError = error;
|
|
1437
|
+
Log.error(
|
|
1438
|
+
`Protocol V2 file transfer failed path=${params.filePath} attempt=${attempt}/${PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT}; restarting from offset 0`,
|
|
1439
|
+
error
|
|
1440
|
+
);
|
|
1441
|
+
if (attempt === PROTOCOL_V2_FILE_TRANSFER_RETRY_COUNT) {
|
|
1442
|
+
break;
|
|
1443
|
+
}
|
|
1444
|
+
await this.recoverProtocolV2FileTransfer();
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
throw ERRORS.TypedError(
|
|
1449
|
+
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
1450
|
+
`transfer data error: ${getProtocolV2UnknownErrorText(lastError)}`
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
private async protocolV2WriteWholeFile({
|
|
643
1455
|
payload,
|
|
644
1456
|
filePath,
|
|
645
1457
|
processedSize,
|
|
646
1458
|
totalSize,
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
processedSize?: number;
|
|
650
|
-
totalSize?: number;
|
|
651
|
-
}) {
|
|
1459
|
+
onTransferredBytes,
|
|
1460
|
+
}: ProtocolV2FileTransferParams) {
|
|
652
1461
|
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
653
1462
|
let offset = 0;
|
|
654
1463
|
const getUploadProgress = (fileOffset: number) => {
|
|
@@ -663,9 +1472,13 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
663
1472
|
const chunkLength = chunkEnd - offset;
|
|
664
1473
|
const chunk = payload.slice(offset, chunkEnd);
|
|
665
1474
|
const overwrite = offset === 0;
|
|
666
|
-
const progress =
|
|
1475
|
+
const progress = getProtocolV2DeviceTransferProgress(
|
|
1476
|
+
(processedSize ?? 0) + offset,
|
|
1477
|
+
(processedSize ?? 0) + chunkEnd,
|
|
1478
|
+
totalSize ?? payload.byteLength
|
|
1479
|
+
);
|
|
667
1480
|
|
|
668
|
-
const writeRes = await this.
|
|
1481
|
+
const writeRes = await this.fileWriteChunk(
|
|
669
1482
|
filePath,
|
|
670
1483
|
payload.byteLength,
|
|
671
1484
|
offset,
|
|
@@ -685,13 +1498,28 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
685
1498
|
);
|
|
686
1499
|
}
|
|
687
1500
|
offset = nextOffset;
|
|
1501
|
+
onTransferredBytes?.((processedSize ?? 0) + offset);
|
|
688
1502
|
this.postProgressMessage(getUploadProgress(offset), 'transferData');
|
|
689
1503
|
}
|
|
690
1504
|
|
|
691
1505
|
return totalSize !== undefined ? (processedSize ?? 0) + payload.byteLength : 0;
|
|
692
1506
|
}
|
|
693
1507
|
|
|
694
|
-
private
|
|
1508
|
+
private getProtocolV2FirmwareTransferTransport() {
|
|
1509
|
+
const env = DataManager.getSettings('env');
|
|
1510
|
+
if (env && DataManager.isBleConnect(env)) {
|
|
1511
|
+
return 'BLE';
|
|
1512
|
+
}
|
|
1513
|
+
if (
|
|
1514
|
+
env &&
|
|
1515
|
+
(DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env) || env === 'web')
|
|
1516
|
+
) {
|
|
1517
|
+
return 'WebUSB';
|
|
1518
|
+
}
|
|
1519
|
+
return env ?? 'unknown';
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
private async fileWriteChunk(
|
|
695
1523
|
filePath: string,
|
|
696
1524
|
totalFileSize: number,
|
|
697
1525
|
offset: number,
|
|
@@ -699,57 +1527,37 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
699
1527
|
overwrite: boolean,
|
|
700
1528
|
progress: number | null
|
|
701
1529
|
): Promise<TypedResponseMessage<'FilesystemFile'>> {
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
if (writeRes.type
|
|
716
|
-
if ((writeRes as any).
|
|
717
|
-
|
|
718
|
-
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, SESSION_ERROR);
|
|
719
|
-
}
|
|
1530
|
+
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
1531
|
+
const writeRes = await typedCall('FilesystemFileWrite', 'FilesystemFile', {
|
|
1532
|
+
file: {
|
|
1533
|
+
path: filePath,
|
|
1534
|
+
offset,
|
|
1535
|
+
total_size: totalFileSize,
|
|
1536
|
+
data: chunk,
|
|
1537
|
+
},
|
|
1538
|
+
overwrite,
|
|
1539
|
+
append: false,
|
|
1540
|
+
ui_percentage: progress ?? undefined,
|
|
1541
|
+
});
|
|
1542
|
+
if (writeRes.type !== 'FilesystemFile') {
|
|
1543
|
+
if ((writeRes as any).type === 'CallMethodError') {
|
|
1544
|
+
if (((writeRes as any).message.error ?? '').indexOf(SESSION_ERROR) > -1) {
|
|
1545
|
+
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, SESSION_ERROR);
|
|
720
1546
|
}
|
|
721
|
-
throw ERRORS.TypedError(
|
|
722
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
723
|
-
'transfer data error'
|
|
724
|
-
);
|
|
725
1547
|
}
|
|
726
|
-
|
|
727
|
-
}
|
|
1548
|
+
throw ERRORS.TypedError(HardwareErrorCode.EmmcFileWriteFirmwareError, 'transfer data error');
|
|
1549
|
+
}
|
|
1550
|
+
return writeRes;
|
|
1551
|
+
}
|
|
728
1552
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
Log.error(`fileWrite error: `, error);
|
|
736
|
-
retryCount--;
|
|
737
|
-
if (retryCount === 0) {
|
|
738
|
-
throw ERRORS.TypedError(
|
|
739
|
-
HardwareErrorCode.EmmcFileWriteFirmwareError,
|
|
740
|
-
'transfer data error'
|
|
741
|
-
);
|
|
742
|
-
}
|
|
743
|
-
const env = DataManager.getSettings('env');
|
|
744
|
-
if (DataManager.isBleConnect(env)) {
|
|
745
|
-
await wait(3000);
|
|
746
|
-
await this.acquireProtocolV2BleDevice();
|
|
747
|
-
await this.device.initialize();
|
|
748
|
-
}
|
|
749
|
-
await wait(2000);
|
|
750
|
-
}
|
|
1553
|
+
private async recoverProtocolV2FileTransfer() {
|
|
1554
|
+
const env = DataManager.getSettings('env');
|
|
1555
|
+
if (DataManager.isBleConnect(env)) {
|
|
1556
|
+
await wait(3000);
|
|
1557
|
+
await this.acquireProtocolV2BleDevice();
|
|
1558
|
+
await this.device.initialize();
|
|
751
1559
|
}
|
|
752
|
-
|
|
1560
|
+
await wait(2000);
|
|
753
1561
|
}
|
|
754
1562
|
|
|
755
1563
|
private async acquireProtocolV2BleDevice() {
|
|
@@ -770,24 +1578,27 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
770
1578
|
let response: ProtocolV2FirmwareUpdateStartResponse;
|
|
771
1579
|
try {
|
|
772
1580
|
response = await commands.typedCall(
|
|
773
|
-
'
|
|
1581
|
+
'DeviceFirmwareUpdateRequest',
|
|
774
1582
|
PROTOCOL_V2_FIRMWARE_UPDATE_RESPONSE_TYPES,
|
|
775
1583
|
{
|
|
776
1584
|
targets,
|
|
777
1585
|
},
|
|
778
1586
|
{
|
|
779
|
-
intermediateTypes: ['
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
if (
|
|
783
|
-
this.postProgressMessage(
|
|
1587
|
+
intermediateTypes: ['DeviceFirmwareUpdateStatus'],
|
|
1588
|
+
timeoutMs: PROTOCOL_V2_START_UPDATE_TIMEOUT,
|
|
1589
|
+
onIntermediateResponse: (response: { type?: string }) => {
|
|
1590
|
+
if (response.type === 'DeviceFirmwareUpdateStatus') {
|
|
1591
|
+
this.postProgressMessage(99, 'installingFirmware');
|
|
784
1592
|
}
|
|
785
1593
|
},
|
|
786
1594
|
}
|
|
787
1595
|
);
|
|
788
1596
|
} catch (error) {
|
|
789
|
-
if (
|
|
790
|
-
Log.log(
|
|
1597
|
+
if (isProtocolV2StartUpdateTransientError(error)) {
|
|
1598
|
+
Log.log(
|
|
1599
|
+
'Protocol V2 firmware update request did not return; continue status polling',
|
|
1600
|
+
error
|
|
1601
|
+
);
|
|
791
1602
|
} else {
|
|
792
1603
|
throw error;
|
|
793
1604
|
}
|
|
@@ -796,10 +1607,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
796
1607
|
return response;
|
|
797
1608
|
}
|
|
798
1609
|
|
|
799
|
-
private async protocolV2Reboot(rebootType:
|
|
1610
|
+
private async protocolV2Reboot(rebootType: DeviceRebootType) {
|
|
800
1611
|
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
801
1612
|
try {
|
|
802
|
-
const res = await typedCall('
|
|
1613
|
+
const res = await typedCall('DeviceReboot', 'Success', {
|
|
803
1614
|
reboot_type: rebootType,
|
|
804
1615
|
});
|
|
805
1616
|
return res.message;
|