@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.
Files changed (192) hide show
  1. package/__tests__/connectSettings.test.ts +19 -0
  2. package/__tests__/device-wallet-session-store.test.ts +131 -0
  3. package/__tests__/preInitialize.test.ts +33 -0
  4. package/__tests__/protocol-v2-firmware-targets.test.ts +19 -0
  5. package/__tests__/protocol-v2.test.ts +1930 -765
  6. package/dist/api/ClearSessionCache.d.ts +9 -0
  7. package/dist/api/ClearSessionCache.d.ts.map +1 -0
  8. package/dist/api/FileRead.d.ts.map +1 -1
  9. package/dist/api/FileWrite.d.ts.map +1 -1
  10. package/dist/api/FirmwareUpdateV2.d.ts.map +1 -1
  11. package/dist/api/FirmwareUpdateV3.d.ts.map +1 -1
  12. package/dist/api/FirmwareUpdateV4.d.ts +26 -5
  13. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  14. package/dist/api/GetDeviceInfo.d.ts.map +1 -1
  15. package/dist/api/GetFeatures.d.ts +1 -1
  16. package/dist/api/GetOnekeyFeatures.d.ts.map +1 -1
  17. package/dist/api/GetPassphraseState.d.ts +4 -4
  18. package/dist/api/GetPassphraseState.d.ts.map +1 -1
  19. package/dist/api/device/DeviceUnlock.d.ts +1 -1
  20. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts +1 -0
  21. package/dist/api/firmware/FirmwareUpdateBaseMethod.d.ts.map +1 -1
  22. package/dist/api/firmware/bootloaderHelper.d.ts.map +1 -1
  23. package/dist/api/firmware/getBinary.d.ts +4 -0
  24. package/dist/api/firmware/getBinary.d.ts.map +1 -1
  25. package/dist/api/firmware/progressThrottle.d.ts +3 -0
  26. package/dist/api/firmware/progressThrottle.d.ts.map +1 -0
  27. package/dist/api/firmware/uploadFirmware.d.ts.map +1 -1
  28. package/dist/api/index.d.ts +8 -10
  29. package/dist/api/index.d.ts.map +1 -1
  30. package/dist/api/protocol-v2/DeviceFactoryInfoGet.d.ts +6 -0
  31. package/dist/api/protocol-v2/DeviceFactoryInfoGet.d.ts.map +1 -0
  32. package/dist/api/protocol-v2/DeviceFactoryInfoSet.d.ts +7 -0
  33. package/dist/api/protocol-v2/DeviceFactoryInfoSet.d.ts.map +1 -0
  34. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts +1 -1
  35. package/dist/api/protocol-v2/DeviceFirmwareUpdate.d.ts.map +1 -1
  36. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts +3 -2
  37. package/dist/api/protocol-v2/DeviceGetFirmwareUpdateStatus.d.ts.map +1 -1
  38. package/dist/api/protocol-v2/{DeviceGetDeviceInfo.d.ts → DeviceInfoGet.d.ts} +10 -10
  39. package/dist/api/protocol-v2/DeviceInfoGet.d.ts.map +1 -0
  40. package/dist/api/protocol-v2/DeviceSessionGet.d.ts +9 -0
  41. package/dist/api/protocol-v2/DeviceSessionGet.d.ts.map +1 -0
  42. package/dist/api/protocol-v2/DeviceStatusGet.d.ts +6 -0
  43. package/dist/api/protocol-v2/DeviceStatusGet.d.ts.map +1 -0
  44. package/dist/api/protocol-v2/FilesystemFormat.d.ts.map +1 -1
  45. package/dist/api/protocol-v2/{FilesystemFixPermission.d.ts → FilesystemPermissionFix.d.ts} +2 -2
  46. package/dist/api/protocol-v2/FilesystemPermissionFix.d.ts.map +1 -0
  47. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts +6 -0
  48. package/dist/api/protocol-v2/ProtocolInfoRequest.d.ts.map +1 -0
  49. package/dist/api/protocol-v2/helpers.d.ts +27 -19
  50. package/dist/api/protocol-v2/helpers.d.ts.map +1 -1
  51. package/dist/api/ton/TonSignData.d.ts +1 -5
  52. package/dist/api/ton/TonSignData.d.ts.map +1 -1
  53. package/dist/api/tron/TronSignMessage.d.ts +0 -1
  54. package/dist/api/tron/TronSignMessage.d.ts.map +1 -1
  55. package/dist/api/tron/TronSignTransaction.d.ts +0 -1
  56. package/dist/api/tron/TronSignTransaction.d.ts.map +1 -1
  57. package/dist/constants/index.d.ts +2 -1
  58. package/dist/constants/index.d.ts.map +1 -1
  59. package/dist/core/index.d.ts.map +1 -1
  60. package/dist/data-manager/DataManager.d.ts +1 -1
  61. package/dist/data-manager/DataManager.d.ts.map +1 -1
  62. package/dist/data-manager/connectSettings.d.ts.map +1 -1
  63. package/dist/device/Device.d.ts +17 -18
  64. package/dist/device/Device.d.ts.map +1 -1
  65. package/dist/device/DeviceWalletSessionStore.d.ts +15 -0
  66. package/dist/device/DeviceWalletSessionStore.d.ts.map +1 -0
  67. package/dist/deviceProfile/buildDeviceFeatures.d.ts +6 -0
  68. package/dist/deviceProfile/buildDeviceFeatures.d.ts.map +1 -0
  69. package/dist/deviceProfile/buildDeviceProfile.d.ts +3 -4
  70. package/dist/deviceProfile/buildDeviceProfile.d.ts.map +1 -1
  71. package/dist/deviceProfile/index.d.ts +1 -1
  72. package/dist/deviceProfile/index.d.ts.map +1 -1
  73. package/dist/index.d.ts +657 -539
  74. package/dist/index.js +2579 -2161
  75. package/dist/inject.d.ts.map +1 -1
  76. package/dist/protocols/protocol-v2/features.d.ts +12 -14
  77. package/dist/protocols/protocol-v2/features.d.ts.map +1 -1
  78. package/dist/protocols/protocol-v2/firmware.d.ts +11 -9
  79. package/dist/protocols/protocol-v2/firmware.d.ts.map +1 -1
  80. package/dist/protocols/protocol-v2/index.d.ts +1 -0
  81. package/dist/protocols/protocol-v2/index.d.ts.map +1 -1
  82. package/dist/protocols/protocol-v2/walletSession.d.ts +13 -0
  83. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -0
  84. package/dist/types/api/firmwareUpdate.d.ts +4 -6
  85. package/dist/types/api/firmwareUpdate.d.ts.map +1 -1
  86. package/dist/types/api/getDeviceInfo.d.ts +4 -2
  87. package/dist/types/api/getDeviceInfo.d.ts.map +1 -1
  88. package/dist/types/api/getPassphraseState.d.ts +4 -4
  89. package/dist/types/api/getPassphraseState.d.ts.map +1 -1
  90. package/dist/types/api/index.d.ts +11 -11
  91. package/dist/types/api/index.d.ts.map +1 -1
  92. package/dist/types/api/protocolV2.d.ts +16 -17
  93. package/dist/types/api/protocolV2.d.ts.map +1 -1
  94. package/dist/types/api/sessionCache.d.ts +10 -0
  95. package/dist/types/api/sessionCache.d.ts.map +1 -0
  96. package/dist/types/device.d.ts +88 -8
  97. package/dist/types/device.d.ts.map +1 -1
  98. package/dist/types/params.d.ts +1 -0
  99. package/dist/types/params.d.ts.map +1 -1
  100. package/dist/types/settings.d.ts +21 -1
  101. package/dist/types/settings.d.ts.map +1 -1
  102. package/dist/utils/capabilitieUtils.d.ts.map +1 -1
  103. package/dist/utils/deviceFeaturesUtils.d.ts +2 -0
  104. package/dist/utils/deviceFeaturesUtils.d.ts.map +1 -1
  105. package/dist/utils/deviceInfoUtils.d.ts.map +1 -1
  106. package/dist/utils/deviceVersionUtils.d.ts.map +1 -1
  107. package/dist/utils/findDefectiveBatchDevice.d.ts +1 -1
  108. package/dist/utils/patch.d.ts +1 -1
  109. package/dist/utils/patch.d.ts.map +1 -1
  110. package/package.json +4 -4
  111. package/src/api/ClearSessionCache.ts +28 -0
  112. package/src/api/FileRead.ts +16 -3
  113. package/src/api/FileWrite.ts +14 -1
  114. package/src/api/FirmwareUpdateV2.ts +9 -2
  115. package/src/api/FirmwareUpdateV3.ts +2 -1
  116. package/src/api/FirmwareUpdateV4.ts +1089 -285
  117. package/src/api/GetDeviceInfo.ts +6 -13
  118. package/src/api/GetOnekeyFeatures.ts +3 -14
  119. package/src/api/GetPassphraseState.ts +7 -4
  120. package/src/api/device/DeviceRebootToBoardloader.ts +4 -4
  121. package/src/api/device/DeviceRebootToBootloader.ts +4 -4
  122. package/src/api/firmware/FirmwareUpdateBaseMethod.ts +18 -4
  123. package/src/api/firmware/bootloaderHelper.ts +3 -2
  124. package/src/api/firmware/getBinary.ts +1 -1
  125. package/src/api/firmware/progressThrottle.ts +44 -0
  126. package/src/api/firmware/releaseHelper.ts +3 -3
  127. package/src/api/firmware/uploadFirmware.ts +5 -2
  128. package/src/api/index.ts +8 -10
  129. package/src/api/protocol-v2/{FactoryGetDeviceInfo.ts → DeviceFactoryInfoGet.ts} +3 -3
  130. package/src/api/protocol-v2/DeviceFactoryInfoSet.ts +32 -0
  131. package/src/api/protocol-v2/DeviceFirmwareUpdate.ts +3 -5
  132. package/src/api/protocol-v2/DeviceGetFirmwareUpdateStatus.ts +39 -5
  133. package/src/api/protocol-v2/{DeviceGetDeviceInfo.ts → DeviceInfoGet.ts} +52 -22
  134. package/src/api/protocol-v2/DeviceReboot.ts +2 -2
  135. package/src/api/protocol-v2/DeviceSessionGet.ts +26 -0
  136. package/src/api/protocol-v2/DeviceStatusGet.ts +14 -0
  137. package/src/api/protocol-v2/FilesystemFormat.ts +4 -1
  138. package/src/api/protocol-v2/{FilesystemFixPermission.ts → FilesystemPermissionFix.ts} +2 -2
  139. package/src/api/protocol-v2/{GetProtoVersion.ts → ProtocolInfoRequest.ts} +2 -2
  140. package/src/api/protocol-v2/helpers.ts +58 -59
  141. package/src/api/ton/TonSignData.ts +1 -5
  142. package/src/api/tron/TronSignMessage.ts +0 -1
  143. package/src/api/tron/TronSignTransaction.ts +0 -1
  144. package/src/constants/index.ts +10 -1
  145. package/src/core/index.ts +22 -10
  146. package/src/data/messages/messages-protocol-v2.json +992 -1420
  147. package/src/data/messages/messages.json +8 -0
  148. package/src/data-manager/DataManager.ts +11 -4
  149. package/src/data-manager/connectSettings.ts +4 -0
  150. package/src/device/Device.ts +158 -294
  151. package/src/device/DevicePool.ts +9 -9
  152. package/src/device/DeviceWalletSessionStore.ts +78 -0
  153. package/src/deviceProfile/buildDeviceFeatures.ts +365 -0
  154. package/src/deviceProfile/buildDeviceProfile.ts +165 -200
  155. package/src/deviceProfile/index.ts +4 -1
  156. package/src/inject.ts +13 -16
  157. package/src/protocols/protocol-v2/features.ts +28 -67
  158. package/src/protocols/protocol-v2/firmware.ts +12 -36
  159. package/src/protocols/protocol-v2/index.ts +1 -0
  160. package/src/protocols/protocol-v2/walletSession.ts +83 -0
  161. package/src/types/api/firmwareUpdate.ts +16 -20
  162. package/src/types/api/getDeviceInfo.ts +4 -2
  163. package/src/types/api/getPassphraseState.ts +4 -4
  164. package/src/types/api/index.ts +17 -20
  165. package/src/types/api/protocolV2.ts +35 -40
  166. package/src/types/api/sessionCache.ts +14 -0
  167. package/src/types/device.ts +98 -34
  168. package/src/types/params.ts +4 -0
  169. package/src/types/settings.ts +42 -30
  170. package/src/utils/capabilitieUtils.ts +1 -2
  171. package/src/utils/deviceFeaturesUtils.ts +54 -29
  172. package/src/utils/deviceInfoUtils.ts +11 -29
  173. package/src/utils/deviceVersionUtils.ts +7 -25
  174. package/src/utils/findDefectiveBatchDevice.ts +6 -6
  175. package/dist/api/protocol-v2/DevReboot.d.ts +0 -7
  176. package/dist/api/protocol-v2/DevReboot.d.ts.map +0 -1
  177. package/dist/api/protocol-v2/DeviceGetDeviceInfo.d.ts.map +0 -1
  178. package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts +0 -6
  179. package/dist/api/protocol-v2/DeviceGetOnboardingStatus.d.ts.map +0 -1
  180. package/dist/api/protocol-v2/FactoryDeviceInfoSettings.d.ts +0 -7
  181. package/dist/api/protocol-v2/FactoryDeviceInfoSettings.d.ts.map +0 -1
  182. package/dist/api/protocol-v2/FactoryGetDeviceInfo.d.ts +0 -6
  183. package/dist/api/protocol-v2/FactoryGetDeviceInfo.d.ts.map +0 -1
  184. package/dist/api/protocol-v2/FilesystemFixPermission.d.ts.map +0 -1
  185. package/dist/api/protocol-v2/GetProtoVersion.d.ts +0 -6
  186. package/dist/api/protocol-v2/GetProtoVersion.d.ts.map +0 -1
  187. package/dist/deviceProfile/legacyFeaturesView.d.ts +0 -5
  188. package/dist/deviceProfile/legacyFeaturesView.d.ts.map +0 -1
  189. package/src/api/protocol-v2/DevReboot.ts +0 -24
  190. package/src/api/protocol-v2/DeviceGetOnboardingStatus.ts +0 -16
  191. package/src/api/protocol-v2/FactoryDeviceInfoSettings.ts +0 -29
  192. 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
- DevRebootType,
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 { LoggerNames, getFirmwareType, getLogger } from '../utils';
11
- import { getBinary, getSysResourceBinary } from './firmware/getBinary';
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 { Features } from '../types';
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 PROTOCOL_V2_TARGET_STATUS_FINISHED = 0;
51
+ const PROTOCOL_V2_TARGET_STATUS_PENDING = 0;
41
52
  const PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS = 1;
42
- const PROTOCOL_V2_TARGET_STATUS_FAILED = 2;
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 = 'vol1:';
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: number;
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<'DevFirmwareUpdateStatus'>
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
- (message.includes('response timeout') && message.includes('devicegetfirmwareupdatestatus')) ||
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 DevFirmwareUpdate
82
- * - completion reboots to normal, then polls Ping
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
- firmwareVersion: payload.firmwareVersion,
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 legacyFeatures = await this.getProtocolV2LegacyFeatures();
168
- const deviceFirmwareType = getFirmwareType(legacyFeatures);
472
+ const deviceFeatures = await this.getProtocolV2DeviceFeatures();
473
+ const deviceFirmwareType = getFirmwareType(deviceFeatures);
169
474
  const firmwareType = this.params.firmwareType ?? deviceFirmwareType;
170
475
 
171
- let resourceBinary: ArrayBuffer | null = null;
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
- resourceBinary = await this.prepareResourceBinary(firmwareType, legacyFeatures);
177
- fwBinaryMap = await this.prepareFirmwareAndBleBinary(firmwareType, legacyFeatures);
178
- bootloaderBinary = await this.prepareBootloaderBinary(firmwareType, legacyFeatures);
179
- // DevFirmwareTargetType 拆分的目标二进制(显式 target_id,不走文件名推断)
180
- fwBinaryMap.push(...this.collectExplicitTargetBinaries());
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
- if (!resourceBinary && !bootloaderBinary && fwBinaryMap.length === 0) {
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
- // TODO: 当前 firmware-pro2 子模块的 reboot/bootloader 流程还未稳定,先暂停自动进 bootloader。
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 getProtocolV2LegacyFeatures() {
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 async prepareResourceBinary(firmwareType: EFirmwareType, features: Features) {
222
- if (this.params.resourceBinary) {
223
- return this.params.resourceBinary;
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
- const resourceUrl = DataManager.getSysResourcesLatestRelease({
226
- features,
227
- forcedUpdateRes: this.params.forcedUpdateRes,
228
- firmwareType,
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
- if (resourceUrl) {
232
- const resource = (await getSysResourceBinary(resourceUrl)).binary;
233
- return resource;
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
- Log.warn('No resource url found');
236
- return null;
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
- private async prepareBootloaderBinary(
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
- ): Promise<ArrayBuffer | null> {
243
- if (this.params.bootloaderBinary) {
244
- return this.params.bootloaderBinary;
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
- if (this.params.bootloaderVersion) {
248
- const bootResourceUrl = DataManager.getBootloaderResource(features, firmwareType);
249
- if (bootResourceUrl) {
250
- const bootBinary = (await getSysResourceBinary(bootResourceUrl)).binary;
251
- return bootBinary;
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
- const profile = this.device.profile;
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(DevRebootType.Bootloader);
961
+ await this.protocolV2Reboot(DeviceRebootType.Bootloader);
276
962
  this.postTipMessage(FirmwareUpdateTipMessage.GoToBootloaderSuccess);
277
- this.checkDeviceToBootloader(this.payload.connectId);
278
- await this.checkPromise?.promise;
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
- * 收集按 DevFirmwareTargetType 拆分的显式目标二进制。
1010
+ * 收集按 DeviceFirmwareTargetType 拆分的显式目标二进制。
294
1011
  * 文件名仅用于 staging 路径展示,target_id 已显式给定。
295
1012
  */
296
1013
  private collectExplicitTargetBinaries() {
297
- const entries: { fileName: string; binary: ArrayBuffer; targetId: number }[] = [];
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
- push(
303
- this.params.romloaderBinary,
304
- 'romloader.bin',
305
- ProtocolV2FirmwareTargetType.TARGET_MAIN_BOOT
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.TARGET_MAIN_APP
1028
+ ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P1
311
1029
  );
312
1030
  push(
313
1031
  this.params.applicationP2Binary,
314
1032
  'application_p2.bin',
315
- ProtocolV2FirmwareTargetType.TARGET_MAIN_APP
1033
+ ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_APPLICATION_P2
316
1034
  );
317
1035
  push(
318
1036
  this.params.coprocessorBinary,
319
1037
  'coprocessor.bin',
320
- ProtocolV2FirmwareTargetType.TARGET_BT
1038
+ ProtocolV2FirmwareTargetType.FW_MGMT_TARGET_COPROCESSOR
321
1039
  );
322
- push(this.params.se01Binary, 'se01.bin', ProtocolV2FirmwareTargetType.TARGET_SE1);
323
- push(this.params.se02Binary, 'se02.bin', ProtocolV2FirmwareTargetType.TARGET_SE2);
324
- push(this.params.se03Binary, 'se03.bin', ProtocolV2FirmwareTargetType.TARGET_SE3);
325
- push(this.params.se04Binary, 'se04.bin', ProtocolV2FirmwareTargetType.TARGET_SE4);
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
- resourceBinary: ArrayBuffer | null;
379
- fwBinaryMap: { fileName: string; binary: ArrayBuffer; targetId?: number }[];
380
- bootloaderBinary: ArrayBuffer | null;
1053
+ fwBinaryMap?: ProtocolV2TargetBinary[];
1054
+ bootloaderBinary?: ArrayBuffer | null;
1055
+ installItems?: ProtocolV2InstallItem[];
1056
+ resourceBundles?: ProtocolV2ResourceBundleBinary[];
381
1057
  }) {
382
- let totalSize = 0;
383
- let processedSize = 0;
1058
+ const orderedInstallItems =
1059
+ installItems ??
1060
+ this.buildProtocolV2InstallItems({
1061
+ bootloaderBinary: bootloaderBinary ?? null,
1062
+ fwBinaryMap: fwBinaryMap ?? [],
1063
+ });
384
1064
 
385
- if (resourceBinary) totalSize += resourceBinary.byteLength;
386
- for (const fwbinary of fwBinaryMap) totalSize += fwbinary.binary.byteLength;
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
- const targets: Array<{ target_id: number; path: string }> = [];
392
-
393
- if (resourceBinary) {
394
- // resource 仅支持单文件 .bin:整文件一次上传,target path 指向该文件
395
- const resourceFilePath = `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}resource.bin`;
396
- processedSize = await this.protocolV2CommonUpdateProcess({
397
- payload: resourceBinary,
398
- filePath: resourceFilePath,
399
- processedSize,
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
- if (bootloaderBinary) {
409
- const bootloaderPath = `${PROTOCOL_V2_FIRMWARE_STAGING_VOLUME}bootloader.bin`;
410
- processedSize = await this.protocolV2CommonUpdateProcess({
411
- payload: bootloaderBinary,
412
- filePath: bootloaderPath,
413
- processedSize,
414
- totalSize,
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
- for (const fwbinary of fwBinaryMap) {
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
- if (totalSize > 0) {
437
- this.postProgressMessage(100, 'transferData');
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
- const startResponse = await this.protocolV2StartFirmwareUpdate({ targets });
442
- await this.waitForProtocolV2FirmwareUpdateComplete(targets, startResponse);
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
- 'DevGetFirmwareUpdateStatus',
449
- 'DevFirmwareUpdateStatus',
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 === PROTOCOL_V2_TARGET_STATUS_FAILED
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 === PROTOCOL_V2_TARGET_STATUS_FINISHED
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 === PROTOCOL_V2_TARGET_STATUS_IN_PROGRESS
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 === 'Success') {
512
- return;
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.targets ??
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/status polling failed: ',
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
- await this.protocolV2Reboot(DevRebootType.Normal);
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 profile = await this.waitForProtocolV2ReconnectAndProfile(
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 = profile.versions.bootloader ?? '0.0.0';
580
- const bleVersion = profile.versions.ble ?? '0.0.0';
581
- const firmwareVersion = profile.versions.firmware ?? '0.0.0';
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 waitForProtocolV2ReconnectAndProfile(timeout: number) {
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
- return buildProfileFromProtocolV2({
609
- deviceInfo,
610
- sources: ['deviceInfo'],
611
- scope: 'versions',
612
- fallbackSerialNo: this.device.originalDescriptor?.path,
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
- const { deviceList } = await DevicePool.getDevices(devicesDescriptor, this.connectId);
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
- }: PROTO.FirmwareUpload & {
655
- filePath: string;
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 = getUploadProgress(chunkEnd);
1475
+ const progress = getProtocolV2DeviceTransferProgress(
1476
+ (processedSize ?? 0) + offset,
1477
+ (processedSize ?? 0) + chunkEnd,
1478
+ totalSize ?? payload.byteLength
1479
+ );
674
1480
 
675
- const writeRes = await this.fileWriteWithRetry(
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 async fileWriteWithRetry(
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 writeFunc = async () => {
710
- const typedCall = this.device.getCommands().typedCall.bind(this.device.getCommands());
711
- const writeRes = await typedCall('FilesystemFileWrite', 'FilesystemFile', {
712
- file: {
713
- path: filePath,
714
- offset,
715
- total_size: totalFileSize,
716
- data: chunk,
717
- },
718
- overwrite,
719
- append: false,
720
- ui_percentage: progress ?? undefined,
721
- });
722
- if (writeRes.type !== 'FilesystemFile') {
723
- if ((writeRes as any).type === 'CallMethodError') {
724
- if (((writeRes as any).message.error ?? '').indexOf(SESSION_ERROR) > -1) {
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
- return writeRes;
734
- };
1548
+ throw ERRORS.TypedError(HardwareErrorCode.EmmcFileWriteFirmwareError, 'transfer data error');
1549
+ }
1550
+ return writeRes;
1551
+ }
735
1552
 
736
- let retryCount = 10;
737
- while (retryCount > 0) {
738
- try {
739
- const result = await writeFunc();
740
- return result;
741
- } catch (error) {
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
- throw ERRORS.TypedError(HardwareErrorCode.EmmcFileWriteFirmwareError, 'transfer data error');
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
- 'DevFirmwareUpdate',
1581
+ 'DeviceFirmwareUpdateRequest',
781
1582
  PROTOCOL_V2_FIRMWARE_UPDATE_RESPONSE_TYPES,
782
1583
  {
783
1584
  targets,
784
1585
  },
785
1586
  {
786
- intermediateTypes: ['DevFirmwareInstallProgress'],
787
- onIntermediateResponse: (response: { message?: { progress?: number } }) => {
788
- const progress = Number(response.message?.progress);
789
- if (Number.isFinite(progress)) {
790
- this.postProgressMessage(Math.min(progress, 99), 'installingFirmware');
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 (isProtocolV2DeviceDisconnectedError(error)) {
797
- Log.log('Rebooting device');
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: DevRebootType) {
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('DevReboot', 'Success', {
1613
+ const res = await typedCall('DeviceReboot', 'Success', {
810
1614
  reboot_type: rebootType,
811
1615
  });
812
1616
  return res.message;