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