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