diff --git a/app/(tabs)/explore.tsx b/app/(tabs)/explore.tsx
index cb8ba9e..4eb8430 100644
--- a/app/(tabs)/explore.tsx
+++ b/app/(tabs)/explore.tsx
@@ -30,6 +30,7 @@ export default function TabTwoScreen() {
updateActivationTime,
sendIdentityCheck,
transferSampleFile,
+ transferEmptyTestPackage,
} = useBleExplorer();
@@ -166,6 +167,11 @@ export default function TabTwoScreen() {
onPress={transferSampleFile}
disabled={!isConnected || loading.transferring}
/>
+
diff --git a/ble/core/BleClient.ts b/ble/core/BleClient.ts
index d3e992c..4d76ea7 100644
--- a/ble/core/BleClient.ts
+++ b/ble/core/BleClient.ts
@@ -95,9 +95,11 @@ export class BleClient {
autoConnect: false,
requestMTU: BLE_UUIDS.REQUEST_MTU
});
- if (device.mtu !== BLE_UUIDS.REQUEST_MTU) {
+ if (device.mtu < BLE_UUIDS.REQUEST_MTU) {
console.log("MTU not supported, requesting default to ", BLE_UUIDS.REQUEST_MTU);
- device = await device.requestMTU(BLE_UUIDS.REQUEST_MTU)
+ device = await device.requestMTU(BLE_UUIDS.REQUEST_MTU);
+ // Give some time for the stack to stabilize after MTU change
+ await new Promise(resolve => setTimeout(resolve, 500));
}
console.log("Connected to device with MTU = ", device.mtu);
this.connectedDevice = await device.discoverAllServicesAndCharacteristics();
diff --git a/ble/hooks/useBleExplorer.ts b/ble/hooks/useBleExplorer.ts
index 37d3512..df9e2fb 100644
--- a/ble/hooks/useBleExplorer.ts
+++ b/ble/hooks/useBleExplorer.ts
@@ -456,6 +456,19 @@ export const useBleExplorer = () => {
}
}, [state.connectedDevice, fileTransferService, convertToANI, setError]);
+ const transferEmptyTestPackage = useCallback(async () => {
+ if (!state.connectedDevice) {
+ setError('No device connected');
+ return;
+ }
+ await fileTransferService.transferTestPackage(
+ state.connectedDevice.id,
+ (progress) => {
+ setState(prev => ({...prev, transferProgress: progress * 100}));
+ }
+ )
+ }, [fileTransferService, setError, state.connectedDevice])
+
const clearLogs = useCallback(() => setState(prev => ({...prev, logs: []})), []);
return {
@@ -468,6 +481,7 @@ export const useBleExplorer = () => {
queryDeviceVersion,
queryActivationStatus,
transferSampleFile,
+ transferEmptyTestPackage,
clearLogs,
updateActivationTime: () => {
},
diff --git a/ble/protocol/ProtocolManager.ts b/ble/protocol/ProtocolManager.ts
index 47f3535..16451a1 100644
--- a/ble/protocol/ProtocolManager.ts
+++ b/ble/protocol/ProtocolManager.ts
@@ -28,19 +28,20 @@ export class ProtocolManager {
type: COMMAND_TYPES,
data: Uint8Array,
head: FRAME_HEAD = FRAME_CONSTANTS.HEAD_APP_TO_DEVICE,
- requireFragmentation: boolean = true
+ requireFragmentation: boolean = true,
+ maxDataSize: number = FRAME_CONSTANTS.MAX_DATA_SIZE
): Uint8Array[] {
// Max pages index is 4 bytes, so we can fit up to 65535 pages of data
- const maxDataSize = 0xffff * (FRAME_CONSTANTS.HEADER_SIZE + FRAME_CONSTANTS.MAX_DATA_SIZE + FRAME_CONSTANTS.FOOTER_SIZE);
- if (data.length > maxDataSize) {
- throw new Error(`Data size ${data.length} exceeds max size ${maxDataSize}`);
+ const maxTotalSize = 0xffff * (FRAME_CONSTANTS.HEADER_SIZE + maxDataSize + FRAME_CONSTANTS.FOOTER_SIZE);
+ if (data.length > maxTotalSize) {
+ throw new Error(`Data size ${data.length} exceeds max size ${maxTotalSize}`);
}
- if (data.length <= FRAME_CONSTANTS.MAX_DATA_SIZE || !requireFragmentation) {
- console.debug(`[ProtocolManager] Frame size ${data.length} is less than max size ${FRAME_CONSTANTS.MAX_DATA_SIZE}, not fragmented`);
+ if (data.length <= maxDataSize || !requireFragmentation) {
+ console.debug(`[ProtocolManager] Frame size ${data.length} is less than max size ${maxDataSize}, not fragmented`);
return [this.createSingleFrame(type, data, head)];
} else {
- console.debug(`[ProtocolManager] Frame size ${data.length} is greater than max size ${FRAME_CONSTANTS.MAX_DATA_SIZE}, fragmented`);
- return this.createFragmentedFrames(type, data, head);
+ console.debug(`[ProtocolManager] Frame size ${data.length} is greater than max size ${maxDataSize}, fragmented`);
+ return this.createFragmentedFrames(type, data, head, maxDataSize);
}
}
@@ -62,6 +63,8 @@ export class ProtocolManager {
// dataLen
buffer[offset++] = (data.length >> 8) & 0xff;
buffer[offset++] = data.length & 0xff;
+ const hexHeader = Array.from(buffer.slice(0, offset)).map(b => b.toString(16).padStart(2, '0')).join(' ');
+ console.debug(`chunk length = ${data.length}, buffer 8 header = ${hexHeader}`)
// data
buffer.set(data, offset);
@@ -75,10 +78,9 @@ export class ProtocolManager {
return buffer;
}
- private static createFragmentedFrames(type: COMMAND_TYPES, data: Uint8Array, head: FRAME_HEAD): Uint8Array[] {
+ private static createFragmentedFrames(type: COMMAND_TYPES, data: Uint8Array, head: FRAME_HEAD, maxDataSize: number): Uint8Array[] {
const frames: Uint8Array[] = [];
const totalSize = data.length;
- const maxDataSize = FRAME_CONSTANTS.MAX_DATA_SIZE;
const totalPages = Math.ceil(totalSize / maxDataSize);
for (let i = 0; i < totalPages; i++) {
@@ -112,8 +114,7 @@ export class ProtocolManager {
offset += chunk.length;
buffer[offset] = this.calculateChecksum(buffer.slice(0, offset));
- const verify = this.verifyChecksum(buffer.slice(0, offset), buffer[offset]);
- console.debug(`[ProtocolManager] Verify checksum: ${verify}`);
+
frames.push(buffer);
}
diff --git a/ble/services/BleProtocolService.ts b/ble/services/BleProtocolService.ts
index e3957b7..679e039 100644
--- a/ble/services/BleProtocolService.ts
+++ b/ble/services/BleProtocolService.ts
@@ -148,7 +148,16 @@ export class BleProtocolService {
payload = new Uint8Array(Buffer.from(jsonStr));
}
- const frames = ProtocolManager.createFrame(type, payload);
+ const device = this.client.getConnectedDevice();
+ const mtu = device?.mtu || 23;
+ // MTU - 3 bytes (ATT overhead) - Protocol Header - Protocol Footer
+ const maxPayloadSize = mtu - 3 - FRAME_CONSTANTS.HEADER_SIZE - FRAME_CONSTANTS.FOOTER_SIZE;
+ // Ensure reasonable bounds (at least 1 byte, max constrained by protocol constant)
+ const safeMaxDataSize = Math.max(1, Math.min(maxPayloadSize, FRAME_CONSTANTS.MAX_DATA_SIZE));
+
+ console.debug(`[BleProtocolService] Sending with MTU=${mtu}, maxDataSize=${safeMaxDataSize}`);
+
+ const frames = ProtocolManager.createFrame(type, payload, FRAME_CONSTANTS.HEAD_APP_TO_DEVICE, true, safeMaxDataSize);
const total = frames.length;
console.debug(`Sending ${total} frames`);
for (let i = 0; i < total; i++) {
@@ -160,6 +169,7 @@ export class BleProtocolService {
if (onProgress) {
onProgress((i + 1) / total);
}
+
console.debug("Wrote frame", result);
}
}
diff --git a/ble/services/FileTransferService.ts b/ble/services/FileTransferService.ts
index 9790112..c0568c0 100644
--- a/ble/services/FileTransferService.ts
+++ b/ble/services/FileTransferService.ts
@@ -1,12 +1,13 @@
-import { BleProtocolService } from './BleProtocolService';
-import { COMMAND_TYPES } from '../protocol/Constants';
+import {BleProtocolService} from './BleProtocolService';
+import {COMMAND_TYPES} from '../protocol/Constants';
export class FileTransferService {
private static instance: FileTransferService;
private protocol = BleProtocolService.getInstance();
-
- private constructor() {}
+
+ private constructor() {
+ }
public static getInstance(): FileTransferService {
if (!FileTransferService.instance) {
@@ -22,21 +23,27 @@ export class FileTransferService {
throw new Error(`Failed to load file: ${response.statusText}`);
}
const blob = await response.blob();
-
+
const reader = new FileReader();
const arrayBuffer = await new Promise((resolve, reject) => {
reader.onload = () => resolve(reader.result as ArrayBuffer);
reader.onerror = reject;
reader.readAsArrayBuffer(blob);
});
-
+
await this.protocol.send(deviceId, type, arrayBuffer, onProgress);
} catch (e) {
console.error("File transfer failed", e);
throw e;
}
}
-
+
+ public async transferTestPackage(deviceId: string, onProgress?: (progress: number) => void) {
+ const arrayBuffer = new Uint8Array(512);
+ console.debug(`test package size = ${arrayBuffer.byteLength}`)
+ await this.protocol.send(deviceId, COMMAND_TYPES.TRANSFER_ANI_VIDEO, arrayBuffer, onProgress);
+ }
+
public async transferOta(deviceId: string, filePath: string) {
return this.transferFile(deviceId, filePath, COMMAND_TYPES.OTA_PACKAGE);
}