forked from yudi_xiao/expo-ble-app-demo
expo-ble模块测试demo 联调BLE模块 take 8 新增空数据包测试
This commit is contained in:
parent
b612335d95
commit
675036f07c
|
|
@ -30,6 +30,7 @@ export default function TabTwoScreen() {
|
||||||
updateActivationTime,
|
updateActivationTime,
|
||||||
sendIdentityCheck,
|
sendIdentityCheck,
|
||||||
transferSampleFile,
|
transferSampleFile,
|
||||||
|
transferEmptyTestPackage,
|
||||||
} = useBleExplorer();
|
} = useBleExplorer();
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -166,6 +167,11 @@ export default function TabTwoScreen() {
|
||||||
onPress={transferSampleFile}
|
onPress={transferSampleFile}
|
||||||
disabled={!isConnected || loading.transferring}
|
disabled={!isConnected || loading.transferring}
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
title={loading.transferring ? 'Transferring...' : 'Transfer Test'}
|
||||||
|
onPress={transferEmptyTestPackage}
|
||||||
|
disabled={!isConnected || loading.transferring}
|
||||||
|
/>
|
||||||
</ThemedView>
|
</ThemedView>
|
||||||
</ThemedView>
|
</ThemedView>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,9 +95,11 @@ export class BleClient {
|
||||||
autoConnect: false,
|
autoConnect: false,
|
||||||
requestMTU: BLE_UUIDS.REQUEST_MTU
|
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);
|
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);
|
console.log("Connected to device with MTU = ", device.mtu);
|
||||||
this.connectedDevice = await device.discoverAllServicesAndCharacteristics();
|
this.connectedDevice = await device.discoverAllServicesAndCharacteristics();
|
||||||
|
|
|
||||||
|
|
@ -456,6 +456,19 @@ export const useBleExplorer = () => {
|
||||||
}
|
}
|
||||||
}, [state.connectedDevice, fileTransferService, convertToANI, setError]);
|
}, [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: []})), []);
|
const clearLogs = useCallback(() => setState(prev => ({...prev, logs: []})), []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -468,6 +481,7 @@ export const useBleExplorer = () => {
|
||||||
queryDeviceVersion,
|
queryDeviceVersion,
|
||||||
queryActivationStatus,
|
queryActivationStatus,
|
||||||
transferSampleFile,
|
transferSampleFile,
|
||||||
|
transferEmptyTestPackage,
|
||||||
clearLogs,
|
clearLogs,
|
||||||
updateActivationTime: () => {
|
updateActivationTime: () => {
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -28,19 +28,20 @@ export class ProtocolManager {
|
||||||
type: COMMAND_TYPES,
|
type: COMMAND_TYPES,
|
||||||
data: Uint8Array,
|
data: Uint8Array,
|
||||||
head: FRAME_HEAD = FRAME_CONSTANTS.HEAD_APP_TO_DEVICE,
|
head: FRAME_HEAD = FRAME_CONSTANTS.HEAD_APP_TO_DEVICE,
|
||||||
requireFragmentation: boolean = true
|
requireFragmentation: boolean = true,
|
||||||
|
maxDataSize: number = FRAME_CONSTANTS.MAX_DATA_SIZE
|
||||||
): Uint8Array[] {
|
): Uint8Array[] {
|
||||||
// Max pages index is 4 bytes, so we can fit up to 65535 pages of data
|
// 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);
|
const maxTotalSize = 0xffff * (FRAME_CONSTANTS.HEADER_SIZE + maxDataSize + FRAME_CONSTANTS.FOOTER_SIZE);
|
||||||
if (data.length > maxDataSize) {
|
if (data.length > maxTotalSize) {
|
||||||
throw new Error(`Data size ${data.length} exceeds max size ${maxDataSize}`);
|
throw new Error(`Data size ${data.length} exceeds max size ${maxTotalSize}`);
|
||||||
}
|
}
|
||||||
if (data.length <= FRAME_CONSTANTS.MAX_DATA_SIZE || !requireFragmentation) {
|
if (data.length <= maxDataSize || !requireFragmentation) {
|
||||||
console.debug(`[ProtocolManager] Frame size ${data.length} is less than max size ${FRAME_CONSTANTS.MAX_DATA_SIZE}, not fragmented`);
|
console.debug(`[ProtocolManager] Frame size ${data.length} is less than max size ${maxDataSize}, not fragmented`);
|
||||||
return [this.createSingleFrame(type, data, head)];
|
return [this.createSingleFrame(type, data, head)];
|
||||||
} else {
|
} else {
|
||||||
console.debug(`[ProtocolManager] Frame size ${data.length} is greater than max size ${FRAME_CONSTANTS.MAX_DATA_SIZE}, fragmented`);
|
console.debug(`[ProtocolManager] Frame size ${data.length} is greater than max size ${maxDataSize}, fragmented`);
|
||||||
return this.createFragmentedFrames(type, data, head);
|
return this.createFragmentedFrames(type, data, head, maxDataSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,6 +63,8 @@ export class ProtocolManager {
|
||||||
// dataLen
|
// dataLen
|
||||||
buffer[offset++] = (data.length >> 8) & 0xff;
|
buffer[offset++] = (data.length >> 8) & 0xff;
|
||||||
buffer[offset++] = data.length & 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
|
// data
|
||||||
buffer.set(data, offset);
|
buffer.set(data, offset);
|
||||||
|
|
@ -75,10 +78,9 @@ export class ProtocolManager {
|
||||||
return buffer;
|
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 frames: Uint8Array[] = [];
|
||||||
const totalSize = data.length;
|
const totalSize = data.length;
|
||||||
const maxDataSize = FRAME_CONSTANTS.MAX_DATA_SIZE;
|
|
||||||
const totalPages = Math.ceil(totalSize / maxDataSize);
|
const totalPages = Math.ceil(totalSize / maxDataSize);
|
||||||
|
|
||||||
for (let i = 0; i < totalPages; i++) {
|
for (let i = 0; i < totalPages; i++) {
|
||||||
|
|
@ -112,8 +114,7 @@ export class ProtocolManager {
|
||||||
offset += chunk.length;
|
offset += chunk.length;
|
||||||
|
|
||||||
buffer[offset] = this.calculateChecksum(buffer.slice(0, offset));
|
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);
|
frames.push(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,16 @@ export class BleProtocolService {
|
||||||
payload = new Uint8Array(Buffer.from(jsonStr));
|
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;
|
const total = frames.length;
|
||||||
console.debug(`Sending ${total} frames`);
|
console.debug(`Sending ${total} frames`);
|
||||||
for (let i = 0; i < total; i++) {
|
for (let i = 0; i < total; i++) {
|
||||||
|
|
@ -160,6 +169,7 @@ export class BleProtocolService {
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
onProgress((i + 1) / total);
|
onProgress((i + 1) / total);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug("Wrote frame", result);
|
console.debug("Wrote frame", result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import { BleProtocolService } from './BleProtocolService';
|
import {BleProtocolService} from './BleProtocolService';
|
||||||
import { COMMAND_TYPES } from '../protocol/Constants';
|
import {COMMAND_TYPES} from '../protocol/Constants';
|
||||||
|
|
||||||
|
|
||||||
export class FileTransferService {
|
export class FileTransferService {
|
||||||
private static instance: FileTransferService;
|
private static instance: FileTransferService;
|
||||||
private protocol = BleProtocolService.getInstance();
|
private protocol = BleProtocolService.getInstance();
|
||||||
|
|
||||||
private constructor() {}
|
private constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
public static getInstance(): FileTransferService {
|
public static getInstance(): FileTransferService {
|
||||||
if (!FileTransferService.instance) {
|
if (!FileTransferService.instance) {
|
||||||
|
|
@ -22,21 +23,27 @@ export class FileTransferService {
|
||||||
throw new Error(`Failed to load file: ${response.statusText}`);
|
throw new Error(`Failed to load file: ${response.statusText}`);
|
||||||
}
|
}
|
||||||
const blob = await response.blob();
|
const blob = await response.blob();
|
||||||
|
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
const arrayBuffer = await new Promise<ArrayBuffer>((resolve, reject) => {
|
const arrayBuffer = await new Promise<ArrayBuffer>((resolve, reject) => {
|
||||||
reader.onload = () => resolve(reader.result as ArrayBuffer);
|
reader.onload = () => resolve(reader.result as ArrayBuffer);
|
||||||
reader.onerror = reject;
|
reader.onerror = reject;
|
||||||
reader.readAsArrayBuffer(blob);
|
reader.readAsArrayBuffer(blob);
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.protocol.send(deviceId, type, arrayBuffer, onProgress);
|
await this.protocol.send(deviceId, type, arrayBuffer, onProgress);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("File transfer failed", e);
|
console.error("File transfer failed", e);
|
||||||
throw 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) {
|
public async transferOta(deviceId: string, filePath: string) {
|
||||||
return this.transferFile(deviceId, filePath, COMMAND_TYPES.OTA_PACKAGE);
|
return this.transferFile(deviceId, filePath, COMMAND_TYPES.OTA_PACKAGE);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue