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