@onekeyfe/hd-core 1.2.0-alpha.2 → 1.2.0-alpha.4
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__/preInitialize.test.ts +33 -0
- package/__tests__/protocol-v2.test.ts +1577 -624
- package/dist/api/FileRead.d.ts.map +1 -1
- package/dist/api/FileWrite.d.ts.map +1 -1
- package/dist/api/FirmwareUpdateV4.d.ts +20 -2
- package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
- package/dist/api/GetPassphraseState.d.ts +4 -4
- package/dist/api/GetPassphraseState.d.ts.map +1 -1
- 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/index.d.ts +5 -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 +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/{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 +24 -19
- 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/constants/index.d.ts +2 -1
- package/dist/constants/index.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/device/Device.d.ts +5 -1
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -1
- package/dist/deviceProfile/buildDeviceProfile.d.ts.map +1 -1
- package/dist/index.d.ts +85 -58
- package/dist/index.js +1672 -1640
- 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 +10 -9
- package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
- package/dist/types/api/firmwareUpdate.d.ts +1 -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/getPassphraseState.d.ts +4 -4
- package/dist/types/api/getPassphraseState.d.ts.map +1 -1
- package/dist/types/api/index.d.ts +6 -11
- package/dist/types/api/index.d.ts.map +1 -1
- package/dist/types/api/protocolV2.d.ts +12 -17
- package/dist/types/api/protocolV2.d.ts.map +1 -1
- 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 +29 -1
- 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/FileRead.ts +14 -1
- package/src/api/FileWrite.ts +14 -1
- package/src/api/FirmwareUpdateV4.ts +933 -197
- package/src/api/GetOnekeyFeatures.ts +1 -1
- package/src/api/GetPassphraseState.ts +7 -4
- package/src/api/device/DeviceRebootToBoardloader.ts +4 -4
- package/src/api/device/DeviceRebootToBootloader.ts +4 -4
- package/src/api/firmware/FirmwareUpdateBaseMethod.ts +11 -4
- package/src/api/index.ts +5 -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 +2 -2
- package/src/api/protocol-v2/{DeviceGetDeviceInfo.ts → DeviceInfoGet.ts} +52 -22
- package/src/api/protocol-v2/DeviceReboot.ts +2 -2
- 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 +48 -57
- package/src/api/ton/TonSignData.ts +1 -5
- package/src/constants/index.ts +10 -1
- package/src/core/index.ts +19 -3
- package/src/data/messages/messages-protocol-v2.json +1224 -1804
- package/src/data-manager/DataManager.ts +7 -0
- package/src/device/Device.ts +34 -12
- package/src/device/DevicePool.ts +6 -6
- package/src/deviceProfile/buildDeviceFeatures.ts +61 -63
- package/src/deviceProfile/buildDeviceProfile.ts +78 -60
- package/src/inject.ts +10 -16
- package/src/protocols/protocol-v2/features.ts +26 -62
- package/src/protocols/protocol-v2/firmware.ts +11 -36
- package/src/types/api/firmwareUpdate.ts +10 -20
- package/src/types/api/getDeviceInfo.ts +2 -0
- package/src/types/api/getPassphraseState.ts +4 -4
- package/src/types/api/index.ts +10 -20
- package/src/types/api/protocolV2.ts +21 -42
- package/src/types/device.ts +1 -0
- package/src/types/params.ts +4 -0
- package/src/types/settings.ts +44 -30
- package/src/utils/deviceFeaturesUtils.ts +52 -11
- 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,162 @@ 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
|
+
IProtocolV2ResourceManifestPackage,
|
|
42
|
+
IVersionArray,
|
|
43
|
+
} from '../types';
|
|
39
44
|
|
|
40
45
|
const Log = getLogger(LoggerNames.Method);
|
|
41
46
|
|
|
42
47
|
const SESSION_ERROR = 'session not found';
|
|
43
48
|
const PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT = 60 * 1000;
|
|
44
49
|
const PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT = 5 * 1000;
|
|
50
|
+
const PROTOCOL_V2_START_UPDATE_TIMEOUT = 60 * 1000;
|
|
45
51
|
const PROTOCOL_V2_INSTALL_TIMEOUT = 5 * 60 * 1000;
|
|
46
|
-
const
|
|
52
|
+
const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
|
|
47
53
|
const PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS = 1;
|
|
48
|
-
const
|
|
54
|
+
const PROTOCOL_V2_TARGET_STATUS_FINISHED = 2;
|
|
55
|
+
const PROTOCOL_V2_TARGET_STATUS_FAILED_MIN = 3;
|
|
49
56
|
const PROTOCOL_V2_CONNECT_PROTOCOL = 'V2';
|
|
50
57
|
const PROTOCOL_V2_FIRMWARE_STAGING_VOLUME = 'vol1:';
|
|
51
58
|
const PROTOCOL_V2_MIN_FILE_CHUNK_SIZE = 64;
|
|
59
|
+
const PROTOCOL_V2_CONNECT_RETRY_COUNT = 10;
|
|
60
|
+
const PROTOCOL_V2_CONNECT_POLL_INTERVAL = 500;
|
|
61
|
+
const PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT = 75 * 1000;
|
|
62
|
+
const PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT = 60 * 1000;
|
|
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
|
+
|
|
104
|
+
type ProtocolV2RemoteComponentBinary = ProtocolV2RemoteComponentTarget & {
|
|
105
|
+
binary: ArrayBuffer;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
type ProtocolV2RemoteComponentTarget = {
|
|
109
|
+
fileName: string;
|
|
110
|
+
targetId: number;
|
|
111
|
+
kind: 'bootloader' | 'firmware' | 'resource';
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
type ProtocolV2OkppHeader = {
|
|
115
|
+
type: string;
|
|
116
|
+
version: IVersionArray;
|
|
117
|
+
payloadHash: string;
|
|
118
|
+
headerHash: string;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const PROTOCOL_V2_REMOTE_COMPONENT_TARGETS: Readonly<
|
|
122
|
+
Record<string, ProtocolV2RemoteComponentTarget>
|
|
123
|
+
> = {
|
|
124
|
+
BOOTLOADER: {
|
|
125
|
+
fileName: 'bootloader.bin',
|
|
126
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER,
|
|
127
|
+
kind: 'bootloader',
|
|
128
|
+
},
|
|
129
|
+
APPLICATION_P1: {
|
|
130
|
+
fileName: 'application_p1.bin',
|
|
131
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1,
|
|
132
|
+
kind: 'firmware',
|
|
133
|
+
},
|
|
134
|
+
APPLICATION_P2: {
|
|
135
|
+
fileName: 'application_p2.bin',
|
|
136
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2,
|
|
137
|
+
kind: 'firmware',
|
|
138
|
+
},
|
|
139
|
+
COPROCESSOR: {
|
|
140
|
+
fileName: 'coprocessor.bin',
|
|
141
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR,
|
|
142
|
+
kind: 'firmware',
|
|
143
|
+
},
|
|
144
|
+
SE01: {
|
|
145
|
+
fileName: 'se01.bin',
|
|
146
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE01,
|
|
147
|
+
kind: 'firmware',
|
|
148
|
+
},
|
|
149
|
+
SE02: {
|
|
150
|
+
fileName: 'se02.bin',
|
|
151
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE02,
|
|
152
|
+
kind: 'firmware',
|
|
153
|
+
},
|
|
154
|
+
SE03: {
|
|
155
|
+
fileName: 'se03.bin',
|
|
156
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE03,
|
|
157
|
+
kind: 'firmware',
|
|
158
|
+
},
|
|
159
|
+
SE04: {
|
|
160
|
+
fileName: 'se04.bin',
|
|
161
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04,
|
|
162
|
+
kind: 'firmware',
|
|
163
|
+
},
|
|
164
|
+
CRATE: {
|
|
165
|
+
fileName: 'resource.bin',
|
|
166
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_CRATE,
|
|
167
|
+
kind: 'resource',
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const PROTOCOL_V2_ROMLOADER_UNSUPPORTED_MESSAGE =
|
|
172
|
+
'FW_MGMT_TARGET_ROMLOADER is not accepted by the current Pro2 bootloader update request. Flash romloader with the loader-specific flow instead of firmwareUpdateV4.';
|
|
173
|
+
|
|
174
|
+
// hd-transport 的历史 decode 行为会把单值 enum 输出为枚举名字符串;
|
|
175
|
+
// Protocol V2 沿用这个 SDK 语义,内部比较前再映射回固件协议数值。
|
|
176
|
+
const PROTOCOL_V2_TARGET_ID_BY_DECODED_NAME = new Map<string, number>(
|
|
177
|
+
Object.entries(ProtocolV2FirmwareTargetType).map(([key, value]) => [key, value])
|
|
178
|
+
);
|
|
179
|
+
const PROTOCOL_V2_TARGET_STATUS_BY_DECODED_NAME = new Map<string, number>([
|
|
180
|
+
['FW_MGMT_UPDATER_TASK_STATUS_PENDING', PROTOCOL_V2_TARGET_STATUS_PENDING],
|
|
181
|
+
['FW_MGMT_UPDATER_TASK_STATUS_IN_PROGRESS', PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS],
|
|
182
|
+
['FW_MGMT_UPDATER_TASK_STATUS_FINISHED', PROTOCOL_V2_TARGET_STATUS_FINISHED],
|
|
183
|
+
['FW_MGMT_UPDATER_TASK_STATUS_FAILED_FILE_NOT_FOUND', 3],
|
|
184
|
+
['FW_MGMT_UPDATER_TASK_STATUS_FAILED_FILE_READ', 4],
|
|
185
|
+
['FW_MGMT_UPDATER_TASK_STATUS_FAILED_FILE_WRITE', 5],
|
|
186
|
+
['FW_MGMT_UPDATER_TASK_STATUS_FAILED_VERIFY', 6],
|
|
187
|
+
['FW_MGMT_UPDATER_TASK_STATUS_FAILED_INSTALL', 7],
|
|
188
|
+
['FW_MGMT_UPDATER_TASK_STATUS_FAILED_ABORT', 8],
|
|
189
|
+
['FW_MGMT_UPDATER_TASK_STATUS_FAILED_BUSY', 9],
|
|
190
|
+
['FW_MGMT_UPDATER_TASK_STATUS_FAILED_ENTRY_OUT_OF_BOUNDS', 10],
|
|
191
|
+
]);
|
|
192
|
+
|
|
63
193
|
const isProtocolV2ReconnectProbeError = (error: unknown) => {
|
|
64
194
|
const message = getProtocolV2UnknownErrorText(error).toLowerCase();
|
|
65
195
|
return (
|
|
@@ -73,19 +203,165 @@ const isProtocolV2PollingTransientError = (error: unknown) => {
|
|
|
73
203
|
return (
|
|
74
204
|
isProtocolV2DeviceDisconnectedError(error) ||
|
|
75
205
|
isProtocolV2ReconnectProbeError(error) ||
|
|
76
|
-
|
|
206
|
+
message.includes('libusb_transfer_timed_out') ||
|
|
207
|
+
(message.includes('response timeout') && message.includes('devicefirmwareupdatestatusget')) ||
|
|
77
208
|
message.includes('device not found') ||
|
|
78
209
|
message.includes('transportnotfound')
|
|
79
210
|
);
|
|
80
211
|
};
|
|
81
212
|
|
|
213
|
+
const isProtocolV2StartUpdateTransientError = (error: unknown) => {
|
|
214
|
+
const message = getProtocolV2UnknownErrorText(error).toLowerCase();
|
|
215
|
+
return (
|
|
216
|
+
isProtocolV2DeviceDisconnectedError(error) ||
|
|
217
|
+
isProtocolV2ReconnectProbeError(error) ||
|
|
218
|
+
message.includes('libusb_transfer_timed_out') ||
|
|
219
|
+
(message.includes('response timeout') && message.includes('devicefirmwareupdaterequest'))
|
|
220
|
+
);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const isProtocolV2TargetStatusFinished = (status: ProtocolV2FirmwareUpdateStatusTarget['status']) =>
|
|
224
|
+
normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_FINISHED;
|
|
225
|
+
|
|
226
|
+
const isProtocolV2TargetStatusInProgress = (
|
|
227
|
+
status: ProtocolV2FirmwareUpdateStatusTarget['status']
|
|
228
|
+
) =>
|
|
229
|
+
normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_PENDING ||
|
|
230
|
+
normalizeProtocolV2TargetStatus(status) === PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS;
|
|
231
|
+
|
|
232
|
+
const isProtocolV2TargetStatusFailed = (status: ProtocolV2FirmwareUpdateStatusTarget['status']) => {
|
|
233
|
+
const normalizedStatus = normalizeProtocolV2TargetStatus(status);
|
|
234
|
+
return (
|
|
235
|
+
typeof normalizedStatus === 'number' && normalizedStatus >= PROTOCOL_V2_TARGET_STATUS_FAILED_MIN
|
|
236
|
+
);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const normalizeProtocolV2TargetId = (targetId: number | string) => {
|
|
240
|
+
if (typeof targetId === 'number') {
|
|
241
|
+
return targetId;
|
|
242
|
+
}
|
|
243
|
+
return PROTOCOL_V2_TARGET_ID_BY_DECODED_NAME.get(targetId);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const normalizeProtocolV2TargetStatus = (
|
|
247
|
+
status: ProtocolV2FirmwareUpdateStatusTarget['status']
|
|
248
|
+
) => {
|
|
249
|
+
if (typeof status === 'number') {
|
|
250
|
+
return status;
|
|
251
|
+
}
|
|
252
|
+
if (typeof status === 'string') {
|
|
253
|
+
return PROTOCOL_V2_TARGET_STATUS_BY_DECODED_NAME.get(status);
|
|
254
|
+
}
|
|
255
|
+
return undefined;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const normalizeProtocolV2Hex = (value?: string) => value?.replace(/^0x/i, '').toLowerCase();
|
|
259
|
+
|
|
260
|
+
const versionArrayToNumber = (version?: IVersionArray) => {
|
|
261
|
+
if (!version) return undefined;
|
|
262
|
+
return version[0] * 0x10000 + version[1] * 0x100 + version[2];
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const versionStringToArray = (version?: string | null): IVersionArray | undefined => {
|
|
266
|
+
if (!version) return undefined;
|
|
267
|
+
const parts = version.split('.').map(part => Number(part));
|
|
268
|
+
if (parts.length !== 3 || parts.some(part => !Number.isFinite(part))) return undefined;
|
|
269
|
+
return parts as IVersionArray;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const compareProtocolV2Versions = (current?: IVersionArray, target?: IVersionArray) => {
|
|
273
|
+
const currentNumber = versionArrayToNumber(current);
|
|
274
|
+
const targetNumber = versionArrayToNumber(target);
|
|
275
|
+
if (currentNumber === undefined || targetNumber === undefined) return undefined;
|
|
276
|
+
return currentNumber - targetNumber;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const bytesToHex = (bytes: Uint8Array) =>
|
|
280
|
+
Array.from(bytes)
|
|
281
|
+
.map(byte => byte.toString(16).padStart(2, '0'))
|
|
282
|
+
.join('');
|
|
283
|
+
|
|
284
|
+
const hexToProtocolV2Bytes = (hex: string) => {
|
|
285
|
+
const normalized = hex.replace(/^0x/i, '');
|
|
286
|
+
if (!normalized || normalized.length % 2 !== 0 || /[^0-9a-f]/i.test(normalized)) {
|
|
287
|
+
return new Uint8Array(0);
|
|
288
|
+
}
|
|
289
|
+
const bytes = new Uint8Array(normalized.length / 2);
|
|
290
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
291
|
+
bytes[i] = Number.parseInt(normalized.slice(i * 2, i * 2 + 2), 16);
|
|
292
|
+
}
|
|
293
|
+
return bytes;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const toProtocolV2Bytes = (value: unknown): Uint8Array => {
|
|
297
|
+
if (value instanceof Uint8Array) return value;
|
|
298
|
+
if (value instanceof ArrayBuffer) return new Uint8Array(value);
|
|
299
|
+
if (ArrayBuffer.isView(value)) {
|
|
300
|
+
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
301
|
+
}
|
|
302
|
+
if (typeof value === 'string') return hexToProtocolV2Bytes(value);
|
|
303
|
+
return new Uint8Array(0);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const toProtocolV2FiniteNumber = (value: unknown) => {
|
|
307
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
308
|
+
if (typeof value === 'string') {
|
|
309
|
+
const numeric = Number(value);
|
|
310
|
+
return Number.isFinite(numeric) ? numeric : undefined;
|
|
311
|
+
}
|
|
312
|
+
if (value && typeof value === 'object') {
|
|
313
|
+
const longLike = value as { toNumber?: () => number };
|
|
314
|
+
if (typeof longLike.toNumber === 'function') {
|
|
315
|
+
const numeric = longLike.toNumber();
|
|
316
|
+
return Number.isFinite(numeric) ? numeric : undefined;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return undefined;
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const readProtocolV2Ascii = (bytes: Uint8Array, offset: number, length: number) =>
|
|
323
|
+
Array.from(bytes.slice(offset, offset + length))
|
|
324
|
+
.map(byte => String.fromCharCode(byte))
|
|
325
|
+
.join('');
|
|
326
|
+
|
|
327
|
+
const parseProtocolV2OkppHeader = (bytes: Uint8Array): ProtocolV2OkppHeader | null => {
|
|
328
|
+
if (bytes.byteLength < PROTOCOL_V2_OKPP_HEADER_SIZE) return null;
|
|
329
|
+
if (readProtocolV2Ascii(bytes, 0, 4) !== 'OKPP') return null;
|
|
330
|
+
|
|
331
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
332
|
+
const headerLen = view.getUint32(0x0c, true);
|
|
333
|
+
if (headerLen !== PROTOCOL_V2_OKPP_HEADER_SIZE) return null;
|
|
334
|
+
|
|
335
|
+
const packedVersion = view.getUint32(0x10, true);
|
|
336
|
+
return {
|
|
337
|
+
type: readProtocolV2Ascii(bytes, 0x08, 4),
|
|
338
|
+
version: [
|
|
339
|
+
Math.floor(packedVersion / 0x10000) % 0x100,
|
|
340
|
+
Math.floor(packedVersion / 0x100) % 0x100,
|
|
341
|
+
packedVersion % 0x100,
|
|
342
|
+
],
|
|
343
|
+
payloadHash: bytesToHex(
|
|
344
|
+
bytes.slice(
|
|
345
|
+
PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET,
|
|
346
|
+
PROTOCOL_V2_OKPP_PAYLOAD_HASH_OFFSET + PROTOCOL_V2_OKPP_HASH_SIZE
|
|
347
|
+
)
|
|
348
|
+
),
|
|
349
|
+
headerHash: bytesToHex(
|
|
350
|
+
bytes.slice(
|
|
351
|
+
PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET,
|
|
352
|
+
PROTOCOL_V2_OKPP_HEADER_HASH_OFFSET + PROTOCOL_V2_OKPP_HASH_SIZE
|
|
353
|
+
)
|
|
354
|
+
),
|
|
355
|
+
};
|
|
356
|
+
};
|
|
357
|
+
|
|
82
358
|
/**
|
|
83
359
|
* FirmwareUpdateV4 is the complete Protocol V2 firmware update flow.
|
|
84
360
|
*
|
|
85
361
|
* It intentionally does not fall back to FirmwareUpdateV3/V1 behavior:
|
|
86
362
|
* - upload uses FilesystemFileWrite
|
|
87
|
-
* - install uses
|
|
88
|
-
* - completion reboots to normal, then polls
|
|
363
|
+
* - install uses DeviceFirmwareUpdateRequest
|
|
364
|
+
* - completion waits for target status to finish, reboots to normal, then polls DeviceInfo
|
|
89
365
|
*/
|
|
90
366
|
export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareUpdateV4Params> {
|
|
91
367
|
init() {
|
|
@@ -96,15 +372,23 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
96
372
|
|
|
97
373
|
const { payload } = this;
|
|
98
374
|
|
|
375
|
+
if (typeof payload.retryCount !== 'number') {
|
|
376
|
+
payload.retryCount = PROTOCOL_V2_CONNECT_RETRY_COUNT;
|
|
377
|
+
}
|
|
378
|
+
if (typeof payload.pollIntervalTime !== 'number') {
|
|
379
|
+
payload.pollIntervalTime = PROTOCOL_V2_CONNECT_POLL_INTERVAL;
|
|
380
|
+
}
|
|
381
|
+
if (typeof payload.timeout !== 'number') {
|
|
382
|
+
payload.timeout = PROTOCOL_V2_CONNECT_SINGLE_TIMEOUT;
|
|
383
|
+
}
|
|
384
|
+
if (typeof payload.protocolV2DeviceInfoTimeoutMs !== 'number') {
|
|
385
|
+
payload.protocolV2DeviceInfoTimeoutMs = PROTOCOL_V2_DEVICE_INFO_READY_TIMEOUT;
|
|
386
|
+
}
|
|
387
|
+
|
|
99
388
|
validateParams(payload, [
|
|
100
|
-
{ name: 'bleVersion', type: 'array' },
|
|
101
|
-
{ name: 'bleBinary', type: 'buffer' },
|
|
102
389
|
{ name: 'chunkSize', type: 'number' },
|
|
103
|
-
{ name: '
|
|
104
|
-
{ name: 'firmwareBinary', type: 'buffer' },
|
|
105
|
-
{ name: 'resourceBinary', type: 'buffer' },
|
|
390
|
+
{ name: 'resourceBinaries', type: 'array', allowEmpty: true },
|
|
106
391
|
{ name: 'forcedUpdateRes', type: 'boolean' },
|
|
107
|
-
{ name: 'bootloaderVersion', type: 'array' },
|
|
108
392
|
{ name: 'bootloaderBinary', type: 'buffer' },
|
|
109
393
|
{ name: 'romloaderBinary', type: 'buffer' },
|
|
110
394
|
{ name: 'applicationP1Binary', type: 'buffer' },
|
|
@@ -119,12 +403,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
119
403
|
]);
|
|
120
404
|
|
|
121
405
|
this.params = {
|
|
122
|
-
bleBinary: payload.bleBinary,
|
|
123
406
|
chunkSize: payload.chunkSize,
|
|
124
|
-
firmwareBinary: payload.firmwareBinary,
|
|
125
407
|
forcedUpdateRes: payload.forcedUpdateRes,
|
|
126
|
-
bleVersion: payload.bleVersion,
|
|
127
|
-
bootloaderVersion: payload.bootloaderVersion,
|
|
128
408
|
bootloaderBinary: payload.bootloaderBinary,
|
|
129
409
|
romloaderBinary: payload.romloaderBinary,
|
|
130
410
|
applicationP1Binary: payload.applicationP1Binary,
|
|
@@ -134,8 +414,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
134
414
|
se02Binary: payload.se02Binary,
|
|
135
415
|
se03Binary: payload.se03Binary,
|
|
136
416
|
se04Binary: payload.se04Binary,
|
|
137
|
-
|
|
138
|
-
resourceBinary: payload.resourceBinary,
|
|
417
|
+
resourceBinaries: payload.resourceBinaries,
|
|
139
418
|
firmwareType: payload.firmwareType,
|
|
140
419
|
platform: payload.platform,
|
|
141
420
|
};
|
|
@@ -174,33 +453,39 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
174
453
|
const deviceFirmwareType = getFirmwareType(deviceFeatures);
|
|
175
454
|
const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
|
|
176
455
|
|
|
177
|
-
let
|
|
178
|
-
let fwBinaryMap:
|
|
456
|
+
let resourceBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
457
|
+
let fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
179
458
|
let bootloaderBinary: ArrayBuffer | null = null;
|
|
180
459
|
try {
|
|
181
460
|
this.postTipMessage(FirmwareUpdateTipMessage.StartDownloadFirmware);
|
|
182
|
-
|
|
183
|
-
fwBinaryMap =
|
|
184
|
-
bootloaderBinary =
|
|
185
|
-
|
|
186
|
-
|
|
461
|
+
resourceBinaryMap = await this.prepareResourceBinaries(firmwareType, deviceFeatures);
|
|
462
|
+
fwBinaryMap = this.collectExplicitTargetBinaries();
|
|
463
|
+
bootloaderBinary = this.prepareBootloaderBinary();
|
|
464
|
+
if (!this.hasExplicitProtocolV2Payload(fwBinaryMap)) {
|
|
465
|
+
const remoteBinaries = await this.prepareRemoteProtocolV2Binaries(
|
|
466
|
+
firmwareType,
|
|
467
|
+
deviceFeatures
|
|
468
|
+
);
|
|
469
|
+
resourceBinaryMap = remoteBinaries.resourceBinaryMap;
|
|
470
|
+
bootloaderBinary = remoteBinaries.bootloaderBinary;
|
|
471
|
+
fwBinaryMap = remoteBinaries.fwBinaryMap;
|
|
472
|
+
}
|
|
187
473
|
this.postTipMessage(FirmwareUpdateTipMessage.FinishDownloadFirmware);
|
|
188
474
|
} catch (err) {
|
|
189
475
|
throw ERRORS.TypedError(HardwareErrorCode.FirmwareUpdateDownloadFailed, err.message ?? err);
|
|
190
476
|
}
|
|
191
477
|
|
|
192
|
-
if (
|
|
478
|
+
if (resourceBinaryMap.length === 0 && !bootloaderBinary && fwBinaryMap.length === 0) {
|
|
193
479
|
throw ERRORS.TypedError(
|
|
194
480
|
HardwareErrorCode.FirmwareUpdateDownloadFailed,
|
|
195
481
|
'No firmware to update'
|
|
196
482
|
);
|
|
197
483
|
}
|
|
198
484
|
|
|
199
|
-
|
|
200
|
-
// await this.enterProtocolV2BootloaderMode();
|
|
485
|
+
await this.enterProtocolV2BootloaderMode();
|
|
201
486
|
|
|
202
487
|
await this.executeProtocolV2Update({
|
|
203
|
-
|
|
488
|
+
resourceBinaryMap,
|
|
204
489
|
fwBinaryMap,
|
|
205
490
|
bootloaderBinary,
|
|
206
491
|
});
|
|
@@ -215,19 +500,32 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
215
500
|
}
|
|
216
501
|
|
|
217
502
|
private async getProtocolV2DeviceFeatures(): Promise<Features> {
|
|
503
|
+
if (this.device.features) {
|
|
504
|
+
return this.device.features;
|
|
505
|
+
}
|
|
218
506
|
if (typeof this.device.getFeatures === 'function') {
|
|
219
507
|
const features = await this.device.getFeatures();
|
|
220
508
|
if (features) return features;
|
|
221
509
|
}
|
|
222
|
-
if (this.device.features) {
|
|
223
|
-
return this.device.features;
|
|
224
|
-
}
|
|
225
510
|
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Device features not available');
|
|
226
511
|
}
|
|
227
512
|
|
|
228
|
-
private async
|
|
229
|
-
if (this.params.
|
|
230
|
-
|
|
513
|
+
private async prepareResourceBinaries(firmwareType: EFirmwareType, features: Features) {
|
|
514
|
+
if (this.params.resourceBinaries?.length) {
|
|
515
|
+
this.params.resourceBinaries.forEach((binary, index) => {
|
|
516
|
+
if (!(binary instanceof ArrayBuffer)) {
|
|
517
|
+
throw ERRORS.TypedError(
|
|
518
|
+
HardwareErrorCode.CallMethodInvalidParameter,
|
|
519
|
+
`Parameter [resourceBinaries.${index}] is of type invalid and should be [buffer].`
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
return this.params.resourceBinaries.map((binary, index) => ({
|
|
525
|
+
fileName: `resource-${index + 1}.bin`,
|
|
526
|
+
binary,
|
|
527
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_CRATE,
|
|
528
|
+
}));
|
|
231
529
|
}
|
|
232
530
|
const resourceUrl = DataManager.getSysResourcesLatestRelease({
|
|
233
531
|
features,
|
|
@@ -237,28 +535,332 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
237
535
|
|
|
238
536
|
if (resourceUrl) {
|
|
239
537
|
const resource = (await getSysResourceBinary(resourceUrl)).binary;
|
|
240
|
-
return
|
|
538
|
+
return [
|
|
539
|
+
{
|
|
540
|
+
fileName: 'resource.bin',
|
|
541
|
+
binary: resource,
|
|
542
|
+
targetId: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_CRATE,
|
|
543
|
+
},
|
|
544
|
+
];
|
|
241
545
|
}
|
|
242
546
|
Log.warn('No resource url found');
|
|
243
|
-
return
|
|
547
|
+
return [];
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
private prepareBootloaderBinary(): ArrayBuffer | null {
|
|
551
|
+
return this.params.bootloaderBinary ?? null;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private hasExplicitProtocolV2Payload(fwBinaryMap: ProtocolV2TargetBinary[]) {
|
|
555
|
+
return (
|
|
556
|
+
!!this.params.resourceBinaries?.length ||
|
|
557
|
+
!!this.params.bootloaderBinary ||
|
|
558
|
+
fwBinaryMap.length > 0
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
private getRemoteComponentEntries(release: IFirmwareReleaseInfo) {
|
|
563
|
+
const { components } = release;
|
|
564
|
+
if (!components) return [];
|
|
565
|
+
|
|
566
|
+
const orderedKeys = [
|
|
567
|
+
...(release.installOrder ?? []),
|
|
568
|
+
...Object.keys(components).filter(key => !release.installOrder?.includes(key)),
|
|
569
|
+
];
|
|
570
|
+
|
|
571
|
+
return orderedKeys
|
|
572
|
+
.map(key => {
|
|
573
|
+
const component = components[key];
|
|
574
|
+
return component ? ([key, component] as const) : undefined;
|
|
575
|
+
})
|
|
576
|
+
.filter((entry): entry is readonly [string, IProtocolV2FirmwareComponent] => !!entry);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
private getRemoteComponentTarget(key: string, component: IProtocolV2FirmwareComponent) {
|
|
580
|
+
const targetName = component.target?.toUpperCase();
|
|
581
|
+
if (targetName === 'ROMLOADER') {
|
|
582
|
+
throw ERRORS.TypedError(
|
|
583
|
+
HardwareErrorCode.RuntimeError,
|
|
584
|
+
PROTOCOL_V2_ROMLOADER_UNSUPPORTED_MESSAGE
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
const target = PROTOCOL_V2_REMOTE_COMPONENT_TARGETS[targetName];
|
|
588
|
+
if (!target) {
|
|
589
|
+
throw ERRORS.TypedError(
|
|
590
|
+
HardwareErrorCode.RuntimeError,
|
|
591
|
+
`Unsupported Protocol V2 firmware component target: ${key}/${component.target}`
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
return target;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
private getProtocolV2ComponentTargetVersion(
|
|
598
|
+
release: IFirmwareReleaseInfo,
|
|
599
|
+
component: IProtocolV2FirmwareComponent,
|
|
600
|
+
target: ProtocolV2RemoteComponentTarget
|
|
601
|
+
) {
|
|
602
|
+
if (component.version) return component.version;
|
|
603
|
+
if (target.targetId === ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER) {
|
|
604
|
+
return release.bootloaderVersion;
|
|
605
|
+
}
|
|
606
|
+
if (
|
|
607
|
+
target.targetId === ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1 ||
|
|
608
|
+
target.targetId === ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2
|
|
609
|
+
) {
|
|
610
|
+
return release.version;
|
|
611
|
+
}
|
|
612
|
+
return undefined;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
private getProtocolV2ComponentCurrentVersion(
|
|
616
|
+
features: Features,
|
|
617
|
+
target: ProtocolV2RemoteComponentTarget
|
|
618
|
+
) {
|
|
619
|
+
switch (target.targetId) {
|
|
620
|
+
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER:
|
|
621
|
+
return getDeviceBootloaderVersion(features);
|
|
622
|
+
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1:
|
|
623
|
+
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2:
|
|
624
|
+
return getDeviceFirmwareVersion(features);
|
|
625
|
+
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR:
|
|
626
|
+
return getDeviceBLEFirmwareVersion(features);
|
|
627
|
+
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE01:
|
|
628
|
+
return versionStringToArray(features.se01Version);
|
|
629
|
+
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE02:
|
|
630
|
+
return versionStringToArray(features.se02Version);
|
|
631
|
+
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE03:
|
|
632
|
+
return versionStringToArray(features.se03Version);
|
|
633
|
+
case ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04:
|
|
634
|
+
return versionStringToArray(features.se04Version);
|
|
635
|
+
default:
|
|
636
|
+
return undefined;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
private isProtocolV2ComponentVersionSatisfied(
|
|
641
|
+
release: IFirmwareReleaseInfo,
|
|
642
|
+
component: IProtocolV2FirmwareComponent,
|
|
643
|
+
target: ProtocolV2RemoteComponentTarget,
|
|
644
|
+
features: Features
|
|
645
|
+
) {
|
|
646
|
+
const targetVersion = this.getProtocolV2ComponentTargetVersion(release, component, target);
|
|
647
|
+
if (!targetVersion) return false;
|
|
648
|
+
|
|
649
|
+
const currentVersion = this.getProtocolV2ComponentCurrentVersion(features, target);
|
|
650
|
+
const compareResult = compareProtocolV2Versions(currentVersion, targetVersion);
|
|
651
|
+
return compareResult !== undefined && compareResult >= 0;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
private getProtocolV2ResourceFilePath(path: string) {
|
|
655
|
+
if (path.startsWith('vol')) return path;
|
|
656
|
+
if (path.startsWith('/')) return `vol0:${path}`;
|
|
657
|
+
return `vol0:/${path}`;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
private async readProtocolV2DeviceFileHeader(path: string) {
|
|
661
|
+
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
662
|
+
const filePath = this.getProtocolV2ResourceFilePath(path);
|
|
663
|
+
const pathInfoRes = await typedCall('FilesystemPathInfoQuery', 'FilesystemPathInfo', {
|
|
664
|
+
path: filePath,
|
|
665
|
+
});
|
|
666
|
+
const fileSize = toProtocolV2FiniteNumber(pathInfoRes.message?.size);
|
|
667
|
+
if (
|
|
668
|
+
!pathInfoRes.message?.exist ||
|
|
669
|
+
pathInfoRes.message?.directory ||
|
|
670
|
+
fileSize === undefined ||
|
|
671
|
+
fileSize < PROTOCOL_V2_OKPP_HEADER_SIZE
|
|
672
|
+
) {
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
677
|
+
const chunks: Uint8Array[] = [];
|
|
678
|
+
let offset = 0;
|
|
679
|
+
while (offset < PROTOCOL_V2_OKPP_HEADER_SIZE) {
|
|
680
|
+
const readLen = Math.min(chunkSize, PROTOCOL_V2_OKPP_HEADER_SIZE - offset);
|
|
681
|
+
const res = await typedCall('FilesystemFileRead', 'FilesystemFile', {
|
|
682
|
+
file: {
|
|
683
|
+
path: filePath,
|
|
684
|
+
offset,
|
|
685
|
+
total_size: 0,
|
|
686
|
+
},
|
|
687
|
+
chunk_len: readLen,
|
|
688
|
+
ui_percentage: undefined,
|
|
689
|
+
});
|
|
690
|
+
const data = toProtocolV2Bytes(res.message?.data);
|
|
691
|
+
if (data.byteLength === 0) return null;
|
|
692
|
+
chunks.push(data);
|
|
693
|
+
offset += data.byteLength;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const headerBytes = new Uint8Array(offset);
|
|
697
|
+
let cursor = 0;
|
|
698
|
+
chunks.forEach(chunk => {
|
|
699
|
+
headerBytes.set(chunk, cursor);
|
|
700
|
+
cursor += chunk.byteLength;
|
|
701
|
+
});
|
|
702
|
+
return parseProtocolV2OkppHeader(headerBytes);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
private async isProtocolV2ResourcePackageMatched(
|
|
706
|
+
pkg: IProtocolV2ResourceManifestPackage,
|
|
707
|
+
manifestVersion?: IVersionArray
|
|
708
|
+
) {
|
|
709
|
+
try {
|
|
710
|
+
const header = await this.readProtocolV2DeviceFileHeader(pkg.path);
|
|
711
|
+
if (!header) return false;
|
|
712
|
+
|
|
713
|
+
const expectedType = pkg.type ?? 'RESC';
|
|
714
|
+
const expectedVersion = pkg.version ?? manifestVersion;
|
|
715
|
+
if (header.type !== expectedType) return false;
|
|
716
|
+
if (expectedVersion && compareProtocolV2Versions(header.version, expectedVersion) !== 0) {
|
|
717
|
+
return false;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const expectedPayloadHash = normalizeProtocolV2Hex(pkg.payloadHash);
|
|
721
|
+
if (expectedPayloadHash && header.payloadHash !== expectedPayloadHash) return false;
|
|
722
|
+
|
|
723
|
+
const expectedHeaderHash = normalizeProtocolV2Hex(pkg.headerHash);
|
|
724
|
+
if (expectedHeaderHash && header.headerHash !== expectedHeaderHash) return false;
|
|
725
|
+
|
|
726
|
+
return true;
|
|
727
|
+
} catch (error) {
|
|
728
|
+
Log.log(`Protocol V2 resource package check failed for ${pkg.path}: `, error);
|
|
729
|
+
return false;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
private async isProtocolV2ResourceManifestSatisfied(
|
|
734
|
+
manifest: IFirmwareReleaseInfo['resourceManifest']
|
|
735
|
+
) {
|
|
736
|
+
if (!manifest?.packages?.length) return false;
|
|
737
|
+
|
|
738
|
+
for (const pkg of manifest.packages) {
|
|
739
|
+
if (!(await this.isProtocolV2ResourcePackageMatched(pkg, manifest.version))) {
|
|
740
|
+
return false;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
return true;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
private getProtocolV2ResourceManifest(
|
|
747
|
+
release: IFirmwareReleaseInfo,
|
|
748
|
+
component: IProtocolV2FirmwareComponent
|
|
749
|
+
) {
|
|
750
|
+
return component.resourceManifest ?? release.resourceManifest;
|
|
244
751
|
}
|
|
245
752
|
|
|
246
|
-
private
|
|
247
|
-
|
|
753
|
+
private getProtocolV2ResourceComponentFileName(key: string) {
|
|
754
|
+
const safeKey = key.replace(/[^a-z0-9_-]/gi, '_') || 'resource';
|
|
755
|
+
return `resource-${safeKey}.bin`;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
private async shouldInstallRemoteProtocolV2Component(
|
|
759
|
+
release: IFirmwareReleaseInfo,
|
|
760
|
+
key: string,
|
|
761
|
+
component: IProtocolV2FirmwareComponent,
|
|
762
|
+
target: ProtocolV2RemoteComponentTarget,
|
|
248
763
|
features: Features
|
|
249
|
-
)
|
|
250
|
-
if (
|
|
251
|
-
|
|
764
|
+
) {
|
|
765
|
+
if (target.kind === 'resource') {
|
|
766
|
+
if (
|
|
767
|
+
this.params.forcedUpdateRes ||
|
|
768
|
+
features.bootloaderMode ||
|
|
769
|
+
features.mode === 'bootloader'
|
|
770
|
+
) {
|
|
771
|
+
return true;
|
|
772
|
+
}
|
|
773
|
+
const resourceMatched = await this.isProtocolV2ResourceManifestSatisfied(
|
|
774
|
+
this.getProtocolV2ResourceManifest(release, component)
|
|
775
|
+
);
|
|
776
|
+
if (resourceMatched) {
|
|
777
|
+
Log.log(`[FirmwareUpdateV4] skip Protocol V2 resource component ${key}; manifest matched`);
|
|
778
|
+
}
|
|
779
|
+
return !resourceMatched;
|
|
252
780
|
}
|
|
253
781
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
782
|
+
const versionSatisfied = this.isProtocolV2ComponentVersionSatisfied(
|
|
783
|
+
release,
|
|
784
|
+
component,
|
|
785
|
+
target,
|
|
786
|
+
features
|
|
787
|
+
);
|
|
788
|
+
if (versionSatisfied) {
|
|
789
|
+
Log.log(`[FirmwareUpdateV4] skip Protocol V2 component ${key}; version is up to date`);
|
|
790
|
+
}
|
|
791
|
+
return !versionSatisfied;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
private async downloadRemoteProtocolV2Component(
|
|
795
|
+
key: string,
|
|
796
|
+
component: IProtocolV2FirmwareComponent
|
|
797
|
+
): Promise<ProtocolV2RemoteComponentBinary> {
|
|
798
|
+
const target = this.getRemoteComponentTarget(key, component);
|
|
799
|
+
if (!component.url) {
|
|
800
|
+
throw ERRORS.TypedError(
|
|
801
|
+
HardwareErrorCode.RuntimeError,
|
|
802
|
+
`Missing Protocol V2 firmware component url: ${key}/${component.target}`
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const { binary } = await getSysResourceBinary(component.url);
|
|
807
|
+
return {
|
|
808
|
+
...target,
|
|
809
|
+
binary,
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
private async prepareRemoteProtocolV2Binaries(firmwareType: EFirmwareType, features: Features) {
|
|
814
|
+
const release = DataManager.getFirmwareLatestRelease(features, firmwareType);
|
|
815
|
+
|
|
816
|
+
const resourceBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
817
|
+
let bootloaderBinary: ArrayBuffer | null = null;
|
|
818
|
+
const fwBinaryMap: ProtocolV2TargetBinary[] = [];
|
|
819
|
+
|
|
820
|
+
if (!release) {
|
|
821
|
+
return {
|
|
822
|
+
resourceBinaryMap,
|
|
823
|
+
bootloaderBinary,
|
|
824
|
+
fwBinaryMap,
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
const entries = this.getRemoteComponentEntries(release);
|
|
829
|
+
|
|
830
|
+
for (const [key, component] of entries) {
|
|
831
|
+
const target = this.getRemoteComponentTarget(key, component);
|
|
832
|
+
const shouldInstall = await this.shouldInstallRemoteProtocolV2Component(
|
|
833
|
+
release,
|
|
834
|
+
key,
|
|
835
|
+
component,
|
|
836
|
+
target,
|
|
837
|
+
features
|
|
838
|
+
);
|
|
839
|
+
if (shouldInstall) {
|
|
840
|
+
const remoteBinary = await this.downloadRemoteProtocolV2Component(key, component);
|
|
841
|
+
if (remoteBinary.kind === 'resource') {
|
|
842
|
+
resourceBinaryMap.push({
|
|
843
|
+
fileName: this.getProtocolV2ResourceComponentFileName(key),
|
|
844
|
+
binary: remoteBinary.binary,
|
|
845
|
+
targetId: remoteBinary.targetId,
|
|
846
|
+
});
|
|
847
|
+
} else if (remoteBinary.kind === 'bootloader') {
|
|
848
|
+
bootloaderBinary = remoteBinary.binary;
|
|
849
|
+
} else {
|
|
850
|
+
fwBinaryMap.push({
|
|
851
|
+
fileName: remoteBinary.fileName,
|
|
852
|
+
binary: remoteBinary.binary,
|
|
853
|
+
targetId: remoteBinary.targetId,
|
|
854
|
+
});
|
|
855
|
+
}
|
|
259
856
|
}
|
|
260
857
|
}
|
|
261
|
-
|
|
858
|
+
|
|
859
|
+
return {
|
|
860
|
+
resourceBinaryMap,
|
|
861
|
+
bootloaderBinary,
|
|
862
|
+
fwBinaryMap,
|
|
863
|
+
};
|
|
262
864
|
}
|
|
263
865
|
|
|
264
866
|
private isProtocolV2BootloaderMode() {
|
|
@@ -270,18 +872,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
270
872
|
|
|
271
873
|
async enterProtocolV2BootloaderMode() {
|
|
272
874
|
if (this.isProtocolV2BootloaderMode()) {
|
|
875
|
+
Log.debug('Protocol V2 device is already in bootloader mode, skip reboot to bootloader');
|
|
273
876
|
return false;
|
|
274
877
|
}
|
|
275
878
|
|
|
276
879
|
try {
|
|
277
880
|
this.postTipMessage(FirmwareUpdateTipMessage.AutoRebootToBootloader);
|
|
278
|
-
await this.protocolV2Reboot(
|
|
881
|
+
await this.protocolV2Reboot(DeviceRebootType.Bootloader);
|
|
279
882
|
this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
|
|
280
|
-
|
|
281
|
-
await this.
|
|
282
|
-
this.checkPromise = null;
|
|
283
|
-
await wait(1500);
|
|
284
|
-
await this.device.acquire?.();
|
|
883
|
+
await wait(1000);
|
|
884
|
+
await this.waitForProtocolV2BootloaderMode();
|
|
285
885
|
return true;
|
|
286
886
|
} catch (error) {
|
|
287
887
|
if (error instanceof HardwareError) {
|
|
@@ -292,160 +892,212 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
292
892
|
}
|
|
293
893
|
}
|
|
294
894
|
|
|
895
|
+
private async waitForProtocolV2BootloaderMode(
|
|
896
|
+
timeout = PROTOCOL_V2_BOOTLOADER_RECONNECT_TIMEOUT,
|
|
897
|
+
retryInterval = 1000
|
|
898
|
+
) {
|
|
899
|
+
const startTime = Date.now();
|
|
900
|
+
let lastError: unknown;
|
|
901
|
+
|
|
902
|
+
while (Date.now() - startTime < timeout) {
|
|
903
|
+
try {
|
|
904
|
+
await this.reconnectProtocolV2Device();
|
|
905
|
+
const deviceInfo = await requestProtocolV2DeviceInfo({
|
|
906
|
+
commands: this.device.getCommands(),
|
|
907
|
+
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
908
|
+
});
|
|
909
|
+
const features = this.device.updateProtocolV2Features(deviceInfo);
|
|
910
|
+
if (features?.bootloaderMode) {
|
|
911
|
+
return features;
|
|
912
|
+
}
|
|
913
|
+
lastError = new Error('Protocol V2 device is reachable but is not in bootloader mode');
|
|
914
|
+
} catch (error) {
|
|
915
|
+
lastError = error;
|
|
916
|
+
Log.log('Protocol V2 bootloader mode not ready, polling reconnect: ', error);
|
|
917
|
+
}
|
|
918
|
+
await wait(retryInterval);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
throw ERRORS.TypedError(
|
|
922
|
+
HardwareErrorCode.FirmwareUpdateAutoEnterBootFailure,
|
|
923
|
+
`Protocol V2 bootloader not ready within ${timeout / 1000}s: ${this.normalizeErrorMessage(
|
|
924
|
+
lastError
|
|
925
|
+
)}`
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
|
|
295
929
|
/**
|
|
296
|
-
* 收集按
|
|
930
|
+
* 收集按 DeviceFirmwareTargetType 拆分的显式目标二进制。
|
|
297
931
|
* 文件名仅用于 staging 路径展示,target_id 已显式给定。
|
|
298
932
|
*/
|
|
299
933
|
private collectExplicitTargetBinaries() {
|
|
300
|
-
const entries:
|
|
934
|
+
const entries: ProtocolV2TargetBinary[] = [];
|
|
301
935
|
const push = (binary: ArrayBuffer | undefined, fileName: string, targetId: number) => {
|
|
302
936
|
if (binary) entries.push({ fileName, binary, targetId });
|
|
303
937
|
};
|
|
304
938
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
939
|
+
if (this.params.romloaderBinary) {
|
|
940
|
+
throw ERRORS.TypedError(
|
|
941
|
+
HardwareErrorCode.RuntimeError,
|
|
942
|
+
PROTOCOL_V2_ROMLOADER_UNSUPPORTED_MESSAGE
|
|
943
|
+
);
|
|
944
|
+
}
|
|
310
945
|
push(
|
|
311
946
|
this.params.applicationP1Binary,
|
|
312
947
|
'application_p1.bin',
|
|
313
|
-
ProtocolV2FirmwareTargetType.
|
|
948
|
+
ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1
|
|
314
949
|
);
|
|
315
950
|
push(
|
|
316
951
|
this.params.applicationP2Binary,
|
|
317
952
|
'application_p2.bin',
|
|
318
|
-
ProtocolV2FirmwareTargetType.
|
|
953
|
+
ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2
|
|
954
|
+
);
|
|
955
|
+
push(
|
|
956
|
+
this.params.coprocessorBinary,
|
|
957
|
+
'coprocessor.bin',
|
|
958
|
+
ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR
|
|
319
959
|
);
|
|
320
|
-
push(this.params.
|
|
321
|
-
push(this.params.
|
|
322
|
-
push(this.params.
|
|
323
|
-
push(this.params.
|
|
324
|
-
push(this.params.se04Binary, 'se04.bin', ProtocolV2FirmwareTargetType.TARGET_SE4);
|
|
960
|
+
push(this.params.se01Binary, 'se01.bin', ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE01);
|
|
961
|
+
push(this.params.se02Binary, 'se02.bin', ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE02);
|
|
962
|
+
push(this.params.se03Binary, 'se03.bin', ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE03);
|
|
963
|
+
push(this.params.se04Binary, 'se04.bin', ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_SE04);
|
|
325
964
|
return entries;
|
|
326
965
|
}
|
|
327
966
|
|
|
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
967
|
private async executeProtocolV2Update({
|
|
373
|
-
|
|
968
|
+
resourceBinaryMap,
|
|
374
969
|
fwBinaryMap,
|
|
375
970
|
bootloaderBinary,
|
|
376
971
|
}: {
|
|
377
|
-
|
|
378
|
-
fwBinaryMap:
|
|
972
|
+
resourceBinaryMap: ProtocolV2TargetBinary[];
|
|
973
|
+
fwBinaryMap: ProtocolV2TargetBinary[];
|
|
379
974
|
bootloaderBinary: ArrayBuffer | null;
|
|
380
975
|
}) {
|
|
381
976
|
let totalSize = 0;
|
|
382
977
|
let processedSize = 0;
|
|
978
|
+
let transferredSize = 0;
|
|
383
979
|
|
|
384
|
-
|
|
980
|
+
for (const resource of resourceBinaryMap) totalSize += resource.binary.byteLength;
|
|
385
981
|
for (const fwbinary of fwBinaryMap) totalSize += fwbinary.binary.byteLength;
|
|
386
982
|
if (bootloaderBinary) totalSize += bootloaderBinary.byteLength;
|
|
387
983
|
|
|
388
984
|
this.postTipMessage(FirmwareUpdateTipMessage.StartTransferData);
|
|
985
|
+
const transferStartTime = Date.now();
|
|
986
|
+
const transferTransport = this.getProtocolV2FirmwareTransferTransport();
|
|
987
|
+
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
988
|
+
const onTransferredBytes = (bytes: number) => {
|
|
989
|
+
transferredSize = bytes;
|
|
990
|
+
};
|
|
991
|
+
Log.log(
|
|
992
|
+
`[FirmwareUpdateV4] transfer started transport=${transferTransport} total=${totalSize} bytes chunk=${chunkSize} bytes`
|
|
993
|
+
);
|
|
389
994
|
|
|
995
|
+
const resourceTargets: Array<{ target_id: number; path: string }> = [];
|
|
390
996
|
const targets: Array<{ target_id: number; path: string }> = [];
|
|
391
997
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
processedSize
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
998
|
+
try {
|
|
999
|
+
for (const resource of resourceBinaryMap) {
|
|
1000
|
+
const resourceFilePath = `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${resource.fileName}`;
|
|
1001
|
+
Log.log(
|
|
1002
|
+
`[FirmwareUpdateV4] staging resource via FilesystemFileWrite target=${resource.targetId} path=${resourceFilePath} bytes=${resource.binary.byteLength}`
|
|
1003
|
+
);
|
|
1004
|
+
processedSize = await this.protocolV2CommonUpdateProcess({
|
|
1005
|
+
payload: resource.binary,
|
|
1006
|
+
filePath: resourceFilePath,
|
|
1007
|
+
processedSize,
|
|
1008
|
+
totalSize,
|
|
1009
|
+
onTransferredBytes,
|
|
1010
|
+
});
|
|
1011
|
+
transferredSize = processedSize;
|
|
1012
|
+
resourceTargets.push({
|
|
1013
|
+
target_id: resource.targetId,
|
|
1014
|
+
path: resourceFilePath,
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
406
1017
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
processedSize
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
1018
|
+
if (bootloaderBinary) {
|
|
1019
|
+
const bootloaderPath = `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}bootloader.bin`;
|
|
1020
|
+
Log.log(
|
|
1021
|
+
`[FirmwareUpdateV4] staging bootloader via FilesystemFileWrite target=${ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER} path=${bootloaderPath} bytes=${bootloaderBinary.byteLength}`
|
|
1022
|
+
);
|
|
1023
|
+
processedSize = await this.protocolV2CommonUpdateProcess({
|
|
1024
|
+
payload: bootloaderBinary,
|
|
1025
|
+
filePath: bootloaderPath,
|
|
1026
|
+
processedSize,
|
|
1027
|
+
totalSize,
|
|
1028
|
+
onTransferredBytes,
|
|
1029
|
+
});
|
|
1030
|
+
transferredSize = processedSize;
|
|
1031
|
+
targets.push({
|
|
1032
|
+
target_id: ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_BOOTLOADER,
|
|
1033
|
+
path: bootloaderPath,
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
420
1036
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
processedSize
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
1037
|
+
for (const fwbinary of fwBinaryMap) {
|
|
1038
|
+
const firmwarePath = `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}${fwbinary.fileName}`;
|
|
1039
|
+
Log.log(
|
|
1040
|
+
`[FirmwareUpdateV4] staging firmware via FilesystemFileWrite target=${fwbinary.targetId} path=${firmwarePath} bytes=${fwbinary.binary.byteLength}`
|
|
1041
|
+
);
|
|
1042
|
+
processedSize = await this.protocolV2CommonUpdateProcess({
|
|
1043
|
+
payload: fwbinary.binary,
|
|
1044
|
+
filePath: firmwarePath,
|
|
1045
|
+
processedSize,
|
|
1046
|
+
totalSize,
|
|
1047
|
+
onTransferredBytes,
|
|
1048
|
+
});
|
|
1049
|
+
transferredSize = processedSize;
|
|
1050
|
+
targets.push({
|
|
1051
|
+
target_id: fwbinary.targetId,
|
|
1052
|
+
path: firmwarePath,
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
434
1055
|
|
|
435
|
-
|
|
436
|
-
|
|
1056
|
+
if (totalSize > 0) {
|
|
1057
|
+
this.postProgressMessage(100, 'transferData');
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
const elapsedMs = Date.now() - transferStartTime;
|
|
1061
|
+
Log.log(
|
|
1062
|
+
`[FirmwareUpdateV4] transfer finished transport=${transferTransport} bytes=${totalSize} elapsed=${(
|
|
1063
|
+
elapsedMs / 1000
|
|
1064
|
+
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(totalSize, elapsedMs)} KB/s`
|
|
1065
|
+
);
|
|
1066
|
+
} catch (error) {
|
|
1067
|
+
const elapsedMs = Date.now() - transferStartTime;
|
|
1068
|
+
Log.warn(
|
|
1069
|
+
`[FirmwareUpdateV4] transfer failed transport=${transferTransport} bytes=${transferredSize}/${totalSize} elapsed=${(
|
|
1070
|
+
elapsedMs / 1000
|
|
1071
|
+
).toFixed(2)}s speed=${formatProtocolV2TransferSpeed(transferredSize, elapsedMs)} KB/s`
|
|
1072
|
+
);
|
|
1073
|
+
throw error;
|
|
437
1074
|
}
|
|
438
1075
|
|
|
439
1076
|
this.postTipMessage(FirmwareUpdateTipMessage.ConfirmOnDevice);
|
|
440
|
-
const
|
|
441
|
-
|
|
1077
|
+
for (const resourceTarget of resourceTargets) {
|
|
1078
|
+
const resourceTargetsBatch = [resourceTarget];
|
|
1079
|
+
Log.log(
|
|
1080
|
+
`[FirmwareUpdateV4] DeviceFirmwareUpdateRequest resources=${JSON.stringify(
|
|
1081
|
+
resourceTargetsBatch
|
|
1082
|
+
)}`
|
|
1083
|
+
);
|
|
1084
|
+
const startResponse = await this.protocolV2StartFirmwareUpdate({
|
|
1085
|
+
targets: resourceTargetsBatch,
|
|
1086
|
+
});
|
|
1087
|
+
await this.waitForProtocolV2FirmwareUpdateComplete(resourceTargetsBatch, startResponse);
|
|
1088
|
+
}
|
|
1089
|
+
if (targets.length > 0) {
|
|
1090
|
+
Log.log(`[FirmwareUpdateV4] DeviceFirmwareUpdateRequest targets=${JSON.stringify(targets)}`);
|
|
1091
|
+
const startResponse = await this.protocolV2StartFirmwareUpdate({ targets });
|
|
1092
|
+
await this.waitForProtocolV2FirmwareUpdateComplete(targets, startResponse);
|
|
1093
|
+
}
|
|
442
1094
|
}
|
|
443
1095
|
|
|
444
1096
|
private async queryProtocolV2FirmwareUpdateStatus() {
|
|
445
1097
|
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
446
1098
|
return typedCall(
|
|
447
|
-
'
|
|
448
|
-
'
|
|
1099
|
+
'DeviceFirmwareUpdateStatusGet',
|
|
1100
|
+
'DeviceFirmwareUpdateStatus',
|
|
449
1101
|
{},
|
|
450
1102
|
{
|
|
451
1103
|
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
@@ -465,14 +1117,32 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
465
1117
|
);
|
|
466
1118
|
}
|
|
467
1119
|
|
|
1120
|
+
private isProtocolV2NormalModeFeatures(features?: Features | null) {
|
|
1121
|
+
return !!features && !features.bootloaderMode && features.mode !== 'bootloader';
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
private async probeProtocolV2NormalMode() {
|
|
1125
|
+
const deviceInfo = await requestProtocolV2DeviceInfo({
|
|
1126
|
+
commands: this.device.getCommands(),
|
|
1127
|
+
timeoutMs: PROTOCOL_V2_SHORT_RESPONSE_TIMEOUT,
|
|
1128
|
+
request: PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
|
|
1129
|
+
});
|
|
1130
|
+
const features = this.device.updateProtocolV2Features(deviceInfo);
|
|
1131
|
+
if (this.isProtocolV2NormalModeFeatures(features)) {
|
|
1132
|
+
Log.log('Protocol V2 firmware install finished; device is back in normal mode');
|
|
1133
|
+
return true;
|
|
1134
|
+
}
|
|
1135
|
+
return false;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
468
1138
|
private assertProtocolV2TargetStatus(
|
|
469
1139
|
statusTargets: ProtocolV2FirmwareUpdateStatusTarget[],
|
|
470
1140
|
expectedTargetIds: Set<number>
|
|
471
1141
|
) {
|
|
472
1142
|
const failedTarget = statusTargets.find(
|
|
473
1143
|
target =>
|
|
474
|
-
expectedTargetIds.has(target.target_id) &&
|
|
475
|
-
target.status
|
|
1144
|
+
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1) &&
|
|
1145
|
+
isProtocolV2TargetStatusFailed(target.status)
|
|
476
1146
|
);
|
|
477
1147
|
if (failedTarget) {
|
|
478
1148
|
throw ERRORS.TypedError(
|
|
@@ -483,8 +1153,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
483
1153
|
|
|
484
1154
|
const completedTargets = statusTargets.filter(
|
|
485
1155
|
target =>
|
|
486
|
-
expectedTargetIds.has(target.target_id) &&
|
|
487
|
-
target.status
|
|
1156
|
+
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1) &&
|
|
1157
|
+
isProtocolV2TargetStatusFinished(target.status)
|
|
488
1158
|
);
|
|
489
1159
|
if (completedTargets.length === expectedTargetIds.size && expectedTargetIds.size > 0) {
|
|
490
1160
|
return true;
|
|
@@ -492,8 +1162,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
492
1162
|
|
|
493
1163
|
const inProgressTarget = statusTargets.find(
|
|
494
1164
|
target =>
|
|
495
|
-
expectedTargetIds.has(target.target_id) &&
|
|
496
|
-
target.status
|
|
1165
|
+
expectedTargetIds.has(normalizeProtocolV2TargetId(target.target_id) ?? -1) &&
|
|
1166
|
+
isProtocolV2TargetStatusInProgress(target.status)
|
|
497
1167
|
);
|
|
498
1168
|
if (inProgressTarget) {
|
|
499
1169
|
this.postProgressMessage(99, 'installingFirmware');
|
|
@@ -507,11 +1177,8 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
507
1177
|
startResponse?: ProtocolV2FirmwareUpdateStartResponse
|
|
508
1178
|
) {
|
|
509
1179
|
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 ??
|
|
1180
|
+
if (startResponse?.type === 'DeviceFirmwareUpdateStatus') {
|
|
1181
|
+
const statusTargets = (startResponse.message.records ??
|
|
515
1182
|
[]) as ProtocolV2FirmwareUpdateStatusTarget[];
|
|
516
1183
|
if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds)) {
|
|
517
1184
|
return;
|
|
@@ -524,7 +1191,7 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
524
1191
|
while (Date.now() - startTime < PROTOCOL_V2_INSTALL_TIMEOUT) {
|
|
525
1192
|
try {
|
|
526
1193
|
const statusRes = await this.queryProtocolV2FirmwareUpdateStatus();
|
|
527
|
-
const statusTargets = (statusRes.message.
|
|
1194
|
+
const statusTargets = (statusRes.message.records ??
|
|
528
1195
|
[]) as ProtocolV2FirmwareUpdateStatusTarget[];
|
|
529
1196
|
if (this.assertProtocolV2TargetStatus(statusTargets, expectedTargetIds)) {
|
|
530
1197
|
return;
|
|
@@ -538,17 +1205,19 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
538
1205
|
if (isProtocolV2PollingTransientError(error)) {
|
|
539
1206
|
try {
|
|
540
1207
|
await this.reconnectProtocolV2Device();
|
|
1208
|
+
if (await this.probeProtocolV2NormalMode()) {
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
541
1211
|
} catch (reconnectError) {
|
|
542
1212
|
lastError = reconnectError;
|
|
543
1213
|
Log.log(
|
|
544
|
-
'Protocol V2 firmware install reconnect/
|
|
1214
|
+
'Protocol V2 firmware install reconnect/normal-mode probe failed: ',
|
|
545
1215
|
reconnectError
|
|
546
1216
|
);
|
|
547
1217
|
}
|
|
548
1218
|
try {
|
|
549
1219
|
await this.pingProtocolV2Device();
|
|
550
1220
|
Log.log('Protocol V2 firmware status unavailable, Ping is ready');
|
|
551
|
-
return;
|
|
552
1221
|
} catch (pingError) {
|
|
553
1222
|
lastError = pingError;
|
|
554
1223
|
Log.log('Protocol V2 firmware install Ping polling failed: ', pingError);
|
|
@@ -566,7 +1235,16 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
566
1235
|
|
|
567
1236
|
private async exitProtocolV2BootloaderToNormal() {
|
|
568
1237
|
this.postTipMessage(FirmwareUpdateTipMessage.SwitchFirmwareReconnectDevice);
|
|
569
|
-
|
|
1238
|
+
try {
|
|
1239
|
+
await this.reconnectProtocolV2Device();
|
|
1240
|
+
if (await this.probeProtocolV2NormalMode()) {
|
|
1241
|
+
Log.log('Protocol V2 device is already in normal mode, skip normal reboot');
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
} catch (error) {
|
|
1245
|
+
Log.log('Protocol V2 normal-mode probe before reboot failed: ', error);
|
|
1246
|
+
}
|
|
1247
|
+
await this.protocolV2Reboot(DeviceRebootType.Normal);
|
|
570
1248
|
}
|
|
571
1249
|
|
|
572
1250
|
private async waitForProtocolV2FinalFeatures() {
|
|
@@ -603,7 +1281,14 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
603
1281
|
// 更新完成判定只需要各 target 版本号;scope 与请求内容保持一致
|
|
604
1282
|
request: PROTOCOL_V2_VERSIONS_DEVICE_INFO_REQUEST,
|
|
605
1283
|
});
|
|
606
|
-
|
|
1284
|
+
const features = this.device.updateProtocolV2Features(deviceInfo);
|
|
1285
|
+
if (features.bootloaderMode || features.mode === 'bootloader') {
|
|
1286
|
+
throw ERRORS.TypedError(
|
|
1287
|
+
HardwareErrorCode.DeviceNotFound,
|
|
1288
|
+
'Protocol V2 device is still in bootloader mode'
|
|
1289
|
+
);
|
|
1290
|
+
}
|
|
1291
|
+
return features;
|
|
607
1292
|
} catch (error) {
|
|
608
1293
|
lastError = error;
|
|
609
1294
|
Log.log('Protocol V2 normal mode not ready, polling Ping: ', error);
|
|
@@ -628,15 +1313,42 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
628
1313
|
const deviceDiff = await this.device.deviceConnector?.enumerate();
|
|
629
1314
|
const devicesDescriptor = deviceDiff?.descriptors ?? [];
|
|
630
1315
|
|
|
631
|
-
|
|
1316
|
+
if (
|
|
1317
|
+
DataManager.isBrowserWebUsb(DataManager.getSettings('env')) &&
|
|
1318
|
+
devicesDescriptor.length === 1
|
|
1319
|
+
) {
|
|
1320
|
+
this.device.updateDescriptor(
|
|
1321
|
+
{
|
|
1322
|
+
...devicesDescriptor[0],
|
|
1323
|
+
protocolType: PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
1324
|
+
},
|
|
1325
|
+
true
|
|
1326
|
+
);
|
|
1327
|
+
await this.device.acquire(PROTOCOL_V2_CONNECT_PROTOCOL, { throwOnRunPromiseError: true });
|
|
1328
|
+
this.device.commands.disposed = false;
|
|
1329
|
+
this.device.getCommands().mainId = this.device.mainId ?? '';
|
|
1330
|
+
await this.device.initialize();
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
// App 与 bootloader 序列号暂时可能不一致。V4 升级重连阶段只接受唯一枚举设备,
|
|
1335
|
+
// 避免继续按旧 app connectId 查缓存导致反复输出 path mismatch 日志。
|
|
1336
|
+
const { deviceList } = await DevicePool.getDevices(devicesDescriptor, undefined, {
|
|
1337
|
+
connectProtocol: PROTOCOL_V2_CONNECT_PROTOCOL,
|
|
1338
|
+
});
|
|
632
1339
|
if (deviceList.length !== 1) {
|
|
633
1340
|
throw ERRORS.TypedError(HardwareErrorCode.DeviceNotFound);
|
|
634
1341
|
}
|
|
635
1342
|
|
|
1343
|
+
Log.debug(
|
|
1344
|
+
'Protocol V2 firmware reconnect using single enumerated device:',
|
|
1345
|
+
deviceList[0].getConnectId()
|
|
1346
|
+
);
|
|
636
1347
|
this.device.updateFromCache(deviceList[0]);
|
|
637
|
-
await this.device.acquire();
|
|
1348
|
+
await this.device.acquire(PROTOCOL_V2_CONNECT_PROTOCOL, { throwOnRunPromiseError: true });
|
|
638
1349
|
this.device.commands.disposed = false;
|
|
639
1350
|
this.device.getCommands().mainId = this.device.mainId ?? '';
|
|
1351
|
+
await this.device.initialize();
|
|
640
1352
|
}
|
|
641
1353
|
|
|
642
1354
|
private async protocolV2CommonUpdateProcess({
|
|
@@ -644,10 +1356,12 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
644
1356
|
filePath,
|
|
645
1357
|
processedSize,
|
|
646
1358
|
totalSize,
|
|
1359
|
+
onTransferredBytes,
|
|
647
1360
|
}: PROTO.FirmwareUpload & {
|
|
648
1361
|
filePath: string;
|
|
649
1362
|
processedSize?: number;
|
|
650
1363
|
totalSize?: number;
|
|
1364
|
+
onTransferredBytes?: (transferredBytes: number) => void;
|
|
651
1365
|
}) {
|
|
652
1366
|
const chunkSize = this.getProtocolV2FirmwareChunkSize();
|
|
653
1367
|
let offset = 0;
|
|
@@ -663,7 +1377,11 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
663
1377
|
const chunkLength = chunkEnd - offset;
|
|
664
1378
|
const chunk = payload.slice(offset, chunkEnd);
|
|
665
1379
|
const overwrite = offset === 0;
|
|
666
|
-
const progress =
|
|
1380
|
+
const progress = getProtocolV2DeviceTransferProgress(
|
|
1381
|
+
(processedSize ?? 0) + offset,
|
|
1382
|
+
(processedSize ?? 0) + chunkEnd,
|
|
1383
|
+
totalSize ?? payload.byteLength
|
|
1384
|
+
);
|
|
667
1385
|
|
|
668
1386
|
const writeRes = await this.fileWriteWithRetry(
|
|
669
1387
|
filePath,
|
|
@@ -685,12 +1403,27 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
685
1403
|
);
|
|
686
1404
|
}
|
|
687
1405
|
offset = nextOffset;
|
|
1406
|
+
onTransferredBytes?.((processedSize ?? 0) + offset);
|
|
688
1407
|
this.postProgressMessage(getUploadProgress(offset), 'transferData');
|
|
689
1408
|
}
|
|
690
1409
|
|
|
691
1410
|
return totalSize !== undefined ? (processedSize ?? 0) + payload.byteLength : 0;
|
|
692
1411
|
}
|
|
693
1412
|
|
|
1413
|
+
private getProtocolV2FirmwareTransferTransport() {
|
|
1414
|
+
const env = DataManager.getSettings('env');
|
|
1415
|
+
if (env && DataManager.isBleConnect(env)) {
|
|
1416
|
+
return 'BLE';
|
|
1417
|
+
}
|
|
1418
|
+
if (
|
|
1419
|
+
env &&
|
|
1420
|
+
(DataManager.isBrowserWebUsb(env) || DataManager.isDesktopWebUsb(env) || env === 'web')
|
|
1421
|
+
) {
|
|
1422
|
+
return 'WebUSB';
|
|
1423
|
+
}
|
|
1424
|
+
return env ?? 'unknown';
|
|
1425
|
+
}
|
|
1426
|
+
|
|
694
1427
|
private async fileWriteWithRetry(
|
|
695
1428
|
filePath: string,
|
|
696
1429
|
totalFileSize: number,
|
|
@@ -770,24 +1503,27 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
770
1503
|
let response: ProtocolV2FirmwareUpdateStartResponse;
|
|
771
1504
|
try {
|
|
772
1505
|
response = await commands.typedCall(
|
|
773
|
-
'
|
|
1506
|
+
'DeviceFirmwareUpdateRequest',
|
|
774
1507
|
PROTOCOL_V2_FIRMWARE_UPDATE_RESPONSE_TYPES,
|
|
775
1508
|
{
|
|
776
1509
|
targets,
|
|
777
1510
|
},
|
|
778
1511
|
{
|
|
779
|
-
intermediateTypes: ['
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
if (
|
|
783
|
-
this.postProgressMessage(
|
|
1512
|
+
intermediateTypes: ['DeviceFirmwareUpdateStatus'],
|
|
1513
|
+
timeoutMs: PROTOCOL_V2_START_UPDATE_TIMEOUT,
|
|
1514
|
+
onIntermediateResponse: (response: { type?: string }) => {
|
|
1515
|
+
if (response.type === 'DeviceFirmwareUpdateStatus') {
|
|
1516
|
+
this.postProgressMessage(99, 'installingFirmware');
|
|
784
1517
|
}
|
|
785
1518
|
},
|
|
786
1519
|
}
|
|
787
1520
|
);
|
|
788
1521
|
} catch (error) {
|
|
789
|
-
if (
|
|
790
|
-
Log.log(
|
|
1522
|
+
if (isProtocolV2StartUpdateTransientError(error)) {
|
|
1523
|
+
Log.log(
|
|
1524
|
+
'Protocol V2 firmware update request did not return; continue status polling',
|
|
1525
|
+
error
|
|
1526
|
+
);
|
|
791
1527
|
} else {
|
|
792
1528
|
throw error;
|
|
793
1529
|
}
|
|
@@ -796,10 +1532,10 @@ export default class FirmwareUpdateV4 extends FirmwareUpdateBaseMethod<FirmwareU
|
|
|
796
1532
|
return response;
|
|
797
1533
|
}
|
|
798
1534
|
|
|
799
|
-
private async protocolV2Reboot(rebootType:
|
|
1535
|
+
private async protocolV2Reboot(rebootType: DeviceRebootType) {
|
|
800
1536
|
const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
|
|
801
1537
|
try {
|
|
802
|
-
const res = await typedCall('
|
|
1538
|
+
const res = await typedCall('DeviceReboot', 'Success', {
|
|
803
1539
|
reboot_type: rebootType,
|
|
804
1540
|
});
|
|
805
1541
|
return res.message;
|