expo-ble模块测试demo 联调BLE模块 take 10 新增log 打印ani文件前后512字节

This commit is contained in:
Yudi Xiao 2025-12-11 16:42:41 +08:00
parent f816165779
commit 83cdf722ac
8 changed files with 32 additions and 29 deletions

View File

@ -16,7 +16,6 @@ export default function TabTwoScreen() {
version,
isActivated,
transferProgress,
isTransferring,
discoveredDevices,
loading,
error,
@ -118,7 +117,8 @@ export default function TabTwoScreen() {
{/* Transfer Status */}
<ThemedView style={styles.section}>
<ThemedText type="subtitle">File Transfer</ThemedText>
<ThemedText>Transferring: {isTransferring ? 'Yes' : 'No'}</ThemedText>
<ThemedText>Converting: {loading.converting ? 'Yes' : 'No'}</ThemedText>
<ThemedText>Transferring: {loading.transferring ? 'Yes' : 'No'}</ThemedText>
<ThemedText>Progress: {transferProgress}%</ThemedText>
</ThemedView>

View File

@ -96,7 +96,7 @@ export class BleClient {
requestMTU: 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 ${device.mtu} not supported, requesting default to ${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));

View File

@ -6,9 +6,9 @@ import {
BleClient,
BleDevice,
BleError,
BleProtocolService, COMMAND_TYPES,
BleProtocolService, APP_COMMAND_TYPES,
ConnectionState,
DeviceInfo,
DeviceInfo, COMMAND_TYPES,
} from '../index';
import {DeviceInfoService} from '../services/DeviceInfoService'
import {FileTransferService} from "../services/FileTransferService";
@ -25,11 +25,11 @@ interface BleState {
version: string;
isActivated: boolean;
transferProgress: number;
isTransferring: boolean;
discoveredDevices: BleDevice[];
loading: {
connecting: boolean;
querying: boolean;
converting: boolean;
transferring: boolean;
};
error: string | null;
@ -50,11 +50,11 @@ export const useBleExplorer = () => {
version: '',
isActivated: false,
transferProgress: 0,
isTransferring: false,
discoveredDevices: [],
loading: {
connecting: false,
querying: false,
converting: false,
transferring: false,
},
error: null,
@ -334,7 +334,7 @@ export const useBleExplorer = () => {
};
const convertToANI = useCallback(async (tempDir: Directory, media: ImagePicker.ImagePickerAsset): Promise<File> => {
const tempFile = new File(tempDir, `${media.fileName}.ani`)
const tempFile = new File(tempDir, `${media.fileName?.split('.')[0]}.ani`)
const formData = new FormData();
formData.append('file', {
uri: media.uri,
@ -370,7 +370,9 @@ export const useBleExplorer = () => {
if (media.type === 'video') {
let tempFile: File;
console.debug(`Converting video: ${media.fileName || 'video'}...`);
setState(prev => ({...prev, loading: {...prev.loading, converting: true}}));
tempFile = await convertToANI(tempDir, media)
setState(prev => ({...prev, loading: {...prev.loading, converting: false}}));
console.log(`Transferring converted file to device...`);
await fileTransferService.transferFile(
state.connectedDevice.id,

View File

@ -2,7 +2,6 @@ export const PROTOCOL_VERSION = "1.0.0";
export const BLE_UUIDS = {
SERVICE: '000002c4-0000-1000-8000-00805f9b34fb',
SERVICE_SHORT: 'ae00',
BROADCAST_CHARACTERISTIC: "000002c1-0000-1000-8000-00805f9b34fb",
WRITE_CHARACTERISTIC: '000002c5-0000-1000-8000-00805f9b34fb',
READ_CHARACTERISTIC: '000002c6-0000-1000-8000-00805f9b34fb',
@ -11,13 +10,13 @@ export const BLE_UUIDS = {
export const FRAME_CONSTANTS = {
HEAD_DEVICE_TO_APP: 0xb0,
// HEAD_APP_TO_DEVICE: 0xc7,
HEAD_APP_TO_DEVICE: 0xb1,
MAX_DATA_SIZE: 496,
HEADER_SIZE: 8,
FOOTER_SIZE: 1,
FRAME_INTERVAL: 35,
FRAME_INTERVAL: 35, // package transfer idle interval in ms, set 35 ms for ble device have enough time to process data
} as const;
export type FRAME_HEAD = typeof FRAME_CONSTANTS.HEAD_DEVICE_TO_APP | typeof FRAME_CONSTANTS.HEAD_APP_TO_DEVICE;
export const COMMAND_TYPES = {
@ -32,7 +31,8 @@ export const COMMAND_TYPES = {
DEVICE_INFO_SETTINGS: 0x0d,
DEVICE_IDENTITY_CHECK: 0x0e,
} as const;
export type COMMAND_TYPES = typeof COMMAND_TYPES[keyof typeof COMMAND_TYPES];
export type APP_COMMAND_TYPES = typeof COMMAND_TYPES[keyof typeof COMMAND_TYPES];
export const RESPONSE_TYPES = {
ACTIVATION_STATUS: 0x01,

View File

@ -1,5 +1,5 @@
import {ProtocolFrame} from './types';
import {FRAME_CONSTANTS, COMMAND_TYPES, FRAME_HEAD} from './Constants';
import {FRAME_CONSTANTS, APP_COMMAND_TYPES, FRAME_HEAD} from './Constants';
export class ProtocolManager {
@ -13,9 +13,7 @@ export class ProtocolManager {
for (let i = 0; i < frameData.length; i++) {
sum += frameData[i];
}
const checksumV1 = (0 - sum) & 0xff
const checksum = (~sum + 1) & 0xff
console.debug(`[ProtocolManager] Checksum V1 calculated: 0 - ${sum} = ${checksumV1.toString(16).padStart(2, '0')}`);
console.debug(`[ProtocolManager] Checksum calculated: 0 - ${sum} = ${checksum.toString(16).padStart(2, '0')}`);
return checksum;
}
@ -25,10 +23,9 @@ export class ProtocolManager {
}
static createFrame(
type: COMMAND_TYPES,
type: APP_COMMAND_TYPES,
data: Uint8Array,
head: FRAME_HEAD = FRAME_CONSTANTS.HEAD_APP_TO_DEVICE,
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
@ -36,7 +33,7 @@ export class ProtocolManager {
if (data.length > maxTotalSize) {
throw new Error(`Data size ${data.length} exceeds max size ${maxTotalSize}`);
}
if (data.length <= maxDataSize || !requireFragmentation) {
if (data.length <= maxDataSize) {
console.debug(`[ProtocolManager] Frame size ${data.length} is less than max size ${maxDataSize}, not fragmented`);
return [this.createSingleFrame(type, data, head)];
} else {
@ -45,7 +42,7 @@ export class ProtocolManager {
}
}
private static createSingleFrame(type: COMMAND_TYPES, data: Uint8Array, head: FRAME_HEAD): Uint8Array {
private static createSingleFrame(type: APP_COMMAND_TYPES, data: Uint8Array, head: FRAME_HEAD): Uint8Array {
const buffer = new Uint8Array(FRAME_CONSTANTS.HEADER_SIZE + data.length + FRAME_CONSTANTS.FOOTER_SIZE);
let offset = 0;
@ -78,7 +75,7 @@ export class ProtocolManager {
return buffer;
}
private static createFragmentedFrames(type: COMMAND_TYPES, data: Uint8Array, head: FRAME_HEAD, maxDataSize: number): Uint8Array[] {
private static createFragmentedFrames(type: APP_COMMAND_TYPES, data: Uint8Array, head: FRAME_HEAD, maxDataSize: number): Uint8Array[] {
const frames: Uint8Array[] = [];
const totalSize = data.length;
const totalPages = Math.ceil(totalSize / maxDataSize);

View File

@ -1,6 +1,6 @@
import {BleClient} from '../core/BleClient';
import {ProtocolManager} from '../protocol/ProtocolManager';
import {BLE_UUIDS, COMMAND_TYPES, FRAME_CONSTANTS} from '../protocol/Constants';
import {BLE_UUIDS, APP_COMMAND_TYPES, FRAME_CONSTANTS} from '../protocol/Constants';
import {ProtocolFrame} from '../protocol/types';
import {Buffer} from 'buffer';
import {Subscription} from 'react-native-ble-plx';
@ -139,13 +139,16 @@ export class BleProtocolService {
}
}
public async send(deviceId: string, type: COMMAND_TYPES, data: object | ArrayBuffer | Uint8Array, onProgress?: (progress: number) => void): Promise<void> {
public async send(deviceId: string, type: APP_COMMAND_TYPES, data: object | ArrayBuffer | Uint8Array, onProgress?: (progress: number) => void): Promise<void> {
let payload: Uint8Array;
if (data instanceof ArrayBuffer) {
console.debug("[BleProtocolService] Sending ArrayBuffer");
payload = new Uint8Array(data);
} else if (data instanceof Uint8Array) {
console.debug("[BleProtocolService] Sending Uint8Array");
payload = data;
} else {
console.debug("[BleProtocolService] Sending JSON payload");
const jsonStr = JSON.stringify(data);
payload = new Uint8Array(Buffer.from(jsonStr));
}
@ -158,8 +161,10 @@ export class BleProtocolService {
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 rawPayloadHex = payload.reduce((acc, val) => acc + val.toString(16).padStart(2, '0') + ' ', '');
const formattedRawPayloadHex = rawPayloadHex.substring(0, 512) + "\n......\n" + rawPayloadHex.substring(rawPayloadHex.length - 512);
console.debug(`[BleProtocolService] Sending payload size=${payload.byteLength}, raw payload hex=\n${formattedRawPayloadHex}`);
const frames = ProtocolManager.createFrame(type, payload, FRAME_CONSTANTS.HEAD_APP_TO_DEVICE, safeMaxDataSize);
const total = frames.length;
console.debug(`Sending ${total} frames`);
for (let i = 0; i < total; i++) {
@ -171,8 +176,7 @@ export class BleProtocolService {
if (onProgress) {
onProgress((i + 1) / total);
}
console.debug("Wrote frame", result);
// console.debug("Wrote frame", result);
}
}
}

View File

@ -1,5 +1,5 @@
import {BleProtocolService} from './BleProtocolService';
import {COMMAND_TYPES, RESPONSE_TYPES} from '../protocol/Constants';
import {APP_COMMAND_TYPES, COMMAND_TYPES, RESPONSE_TYPES} from '../protocol/Constants';
import {DeviceInfo, ActivationStatus} from '../protocol/types';
import {Buffer} from 'buffer';

View File

@ -1,5 +1,5 @@
import {BleProtocolService} from './BleProtocolService';
import {COMMAND_TYPES} from '../protocol/Constants';
import {APP_COMMAND_TYPES, COMMAND_TYPES} from '../protocol/Constants';
export class FileTransferService {
@ -16,7 +16,7 @@ export class FileTransferService {
return FileTransferService.instance;
}
public async transferFile(deviceId: string, filePath: string, type: COMMAND_TYPES, onProgress?: (progress: number) => void): Promise<void> {
public async transferFile(deviceId: string, filePath: string, type: APP_COMMAND_TYPES, onProgress?: (progress: number) => void): Promise<void> {
try {
const response = await fetch(filePath);
if (!response.ok) {