expo-ble模块测试demo 联调BLE模块
This commit is contained in:
@@ -1,18 +1,13 @@
|
||||
import React from 'react';
|
||||
import {Alert, StyleSheet, Button, Switch, ScrollView} from 'react-native';
|
||||
import {StyleSheet, Button} from 'react-native';
|
||||
import {ThemedText} from '@/components/themed-text';
|
||||
import {ThemedView} from '@/components/themed-view';
|
||||
import {useBleExplorer} from '@/hooks/useBleExplorer';
|
||||
import {useBlePeripheral} from '@/hooks/useBlePeripheral';
|
||||
import {BLE_UUIDS, PROTOCOL_VERSION} from '@/ble';
|
||||
import {BLE_UUIDS, PROTOCOL_VERSION, useBleExplorer} from '@/ble';
|
||||
import ParallaxScrollView from '@/components/parallax-scroll-view';
|
||||
import {IconSymbol} from '@/components/ui/icon-symbol';
|
||||
|
||||
|
||||
export default function TabTwoScreen() {
|
||||
const [deviceMode, setDeviceMode] = React.useState<'central' | 'peripheral'>('central');
|
||||
|
||||
|
||||
const {
|
||||
isScanning,
|
||||
isConnected,
|
||||
@@ -35,24 +30,8 @@ export default function TabTwoScreen() {
|
||||
updateActivationTime,
|
||||
sendIdentityCheck,
|
||||
transferSampleFile,
|
||||
clearLogs,
|
||||
} = useBleExplorer();
|
||||
|
||||
const {
|
||||
isAdvertising,
|
||||
connectedCentralCount,
|
||||
logs: peripheralLogs,
|
||||
deviceInfo: peripheralDeviceInfo,
|
||||
loading: peripheralLoading,
|
||||
error: peripheralError,
|
||||
startAdvertising,
|
||||
stopAdvertising,
|
||||
getCharacteristicReadValue,
|
||||
updateDeviceInfo,
|
||||
resetDeviceInfo,
|
||||
clearLogs: clearPeripheralLogs,
|
||||
} = useBlePeripheral();
|
||||
|
||||
|
||||
return (
|
||||
<ParallaxScrollView
|
||||
@@ -69,240 +48,126 @@ export default function TabTwoScreen() {
|
||||
<ThemedText type="title">BLE Explorer</ThemedText>
|
||||
</ThemedView>
|
||||
|
||||
{/* Device Mode Selection */}
|
||||
{/* Connection Status */}
|
||||
<ThemedView style={styles.section}>
|
||||
<ThemedText type="subtitle">Device Mode</ThemedText>
|
||||
<ThemedView style={styles.modeSwitchContainer}>
|
||||
<ThemedText style={styles.modeLabel}>Central</ThemedText>
|
||||
<Switch
|
||||
value={deviceMode === 'peripheral'}
|
||||
onValueChange={async (value) => {
|
||||
if (value) {
|
||||
// Switching to peripheral mode - stop central operations first
|
||||
console.log('Switching to peripheral mode - stopping central operations');
|
||||
if (isScanning) {
|
||||
await stopScan();
|
||||
// Add small delay to ensure cleanup
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
if (isConnected) {
|
||||
await disconnectDevice();
|
||||
// Add small delay to ensure cleanup
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
} else {
|
||||
// Switching to central mode - stop peripheral operations first
|
||||
console.log('Switching to central mode - stopping peripheral operations');
|
||||
if (isAdvertising) {
|
||||
await stopAdvertising();
|
||||
// Add small delay to ensure cleanup
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
}
|
||||
setDeviceMode(value ? 'peripheral' : 'central');
|
||||
}}
|
||||
disabled={isScanning || isConnected || isAdvertising || peripheralLoading.advertising}
|
||||
/>
|
||||
<ThemedText style={styles.modeLabel}>Peripheral</ThemedText>
|
||||
</ThemedView>
|
||||
<ThemedText style={styles.modeDescription}>
|
||||
{deviceMode === 'central'
|
||||
? 'Central mode: Scan for and connect to BLE peripherals'
|
||||
: `Peripheral mode: Act as BLE receiver`}
|
||||
</ThemedText>
|
||||
<ThemedText type="subtitle">Connection Status</ThemedText>
|
||||
<ThemedText>Scanning: {isScanning ? 'Yes' : 'No'}</ThemedText>
|
||||
<ThemedText>Connected: {isConnected ? 'Yes' : 'No'}</ThemedText>
|
||||
<ThemedText>Device: {connectedDevice?.name || 'None'}</ThemedText>
|
||||
{error && <ThemedText style={styles.errorText}>Error: {error}</ThemedText>}
|
||||
</ThemedView>
|
||||
|
||||
{deviceMode === 'central' ? (
|
||||
<>
|
||||
{/* Connection Status */}
|
||||
<ThemedView style={styles.section}>
|
||||
<ThemedText type="subtitle">Connection Status</ThemedText>
|
||||
<ThemedText>Scanning: {isScanning ? 'Yes' : 'No'}</ThemedText>
|
||||
<ThemedText>Connected: {isConnected ? 'Yes' : 'No'}</ThemedText>
|
||||
<ThemedText>Device: {connectedDevice?.name || 'None'}</ThemedText>
|
||||
{error && <ThemedText style={styles.errorText}>Error: {error}</ThemedText>}
|
||||
</ThemedView>
|
||||
|
||||
{/* Discovered Devices */}
|
||||
<ThemedView style={styles.section}>
|
||||
<ThemedText type="subtitle">Discovered Devices</ThemedText>
|
||||
<ThemedText style={styles.deviceCount}>Total devices
|
||||
found: {discoveredDevices.length}</ThemedText>
|
||||
{discoveredDevices.length === 0 ? (
|
||||
<ThemedText>No devices discovered yet. Start scanning to find devices.</ThemedText>
|
||||
) : (
|
||||
<ThemedView style={{gap: 8}}>
|
||||
{discoveredDevices.map((item) => (
|
||||
<ThemedView
|
||||
key={item.id}
|
||||
style={[styles.deviceItem, item.connected && styles.connectedDevice]}
|
||||
lightColor="#eee"
|
||||
darkColor="#2a2a2a"
|
||||
>
|
||||
<ThemedView style={styles.deviceInfo} lightColor="transparent"
|
||||
darkColor="transparent">
|
||||
<ThemedText style={item.connected && styles.connectedDeviceText}>
|
||||
{item.name || 'Unknown Device'}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.deviceId}>{item.id}</ThemedText>
|
||||
{item.serviceUUIDs && item.serviceUUIDs.length > 0 && (
|
||||
<ThemedText style={styles.serviceUuids}>
|
||||
Services: {item.serviceUUIDs.join(', ')}
|
||||
</ThemedText>
|
||||
)}
|
||||
{item.connected && (
|
||||
<ThemedText style={styles.connectionStatus}>Connected</ThemedText>
|
||||
)}
|
||||
</ThemedView>
|
||||
<Button
|
||||
title={loading.connecting ? 'Connecting...' : (item.connected ? 'Connected' : 'Connect')}
|
||||
onPress={() => connectToDevice(item)}
|
||||
disabled={isConnected || loading.connecting || item.connected}
|
||||
/>
|
||||
</ThemedView>
|
||||
))}
|
||||
</ThemedView>
|
||||
)}
|
||||
</ThemedView>
|
||||
|
||||
{/* Device Info */}
|
||||
<ThemedView style={styles.section}>
|
||||
<ThemedText type="subtitle">Device Information</ThemedText>
|
||||
<ThemedText>Activated: {isActivated ? 'Yes' : 'No'}</ThemedText>
|
||||
<ThemedText>Version: {version || 'Unknown'}</ThemedText>
|
||||
{deviceInfo && (
|
||||
<>
|
||||
<ThemedText>Name: {deviceInfo.devname}</ThemedText>
|
||||
<ThemedText>Total Space: {deviceInfo.allspace} KB</ThemedText>
|
||||
<ThemedText>Free Space: {deviceInfo.freespace} KB</ThemedText>
|
||||
<ThemedText>Brand: {deviceInfo.brand}</ThemedText>
|
||||
</>
|
||||
)}
|
||||
</ThemedView>
|
||||
|
||||
{/* Transfer Status */}
|
||||
<ThemedView style={styles.section}>
|
||||
<ThemedText type="subtitle">File Transfer</ThemedText>
|
||||
<ThemedText>Transferring: {isTransferring ? 'Yes' : 'No'}</ThemedText>
|
||||
<ThemedText>Progress: {transferProgress}%</ThemedText>
|
||||
</ThemedView>
|
||||
|
||||
{/* Control Buttons */}
|
||||
<ThemedView style={styles.section}>
|
||||
<ThemedText type="subtitle">Controls</ThemedText>
|
||||
<ThemedView style={styles.buttonRow}>
|
||||
<Button title={isScanning ? 'Stop Scan' : 'Start Scan'}
|
||||
onPress={isScanning ? stopScan : startScan}/>
|
||||
<Button
|
||||
title="Disconnect"
|
||||
onPress={disconnectDevice}
|
||||
disabled={!isConnected}
|
||||
/>
|
||||
</ThemedView>
|
||||
|
||||
<ThemedView style={styles.buttonRow}>
|
||||
<Button
|
||||
title={loading.querying ? 'Querying...' : 'Query Activation'}
|
||||
onPress={queryActivationStatus}
|
||||
disabled={!isConnected || loading.querying}
|
||||
/>
|
||||
<Button title={loading.querying ? 'Querying...' : 'Query Version'}
|
||||
onPress={queryDeviceVersion} disabled={!isConnected || loading.querying}/>
|
||||
</ThemedView>
|
||||
|
||||
<ThemedView style={styles.buttonRow}>
|
||||
<Button
|
||||
title={loading.querying ? 'Querying...' : 'Device Info'}
|
||||
onPress={requestDeviceInfo}
|
||||
disabled={!isConnected || loading.querying}
|
||||
/>
|
||||
<Button title="Update Time" onPress={updateActivationTime} disabled={!isConnected}/>
|
||||
</ThemedView>
|
||||
|
||||
<ThemedView style={styles.buttonRow}>
|
||||
<Button title="Identity Check (Valid)" onPress={() => sendIdentityCheck()}
|
||||
disabled={!isConnected}/>
|
||||
<Button title="Identity Check (Invalid)" onPress={() => sendIdentityCheck()}
|
||||
disabled={!isConnected}/>
|
||||
</ThemedView>
|
||||
|
||||
<ThemedView style={styles.buttonRow}>
|
||||
<Button
|
||||
title={loading.transferring ? 'Transferring...' : 'Transfer File'}
|
||||
onPress={transferSampleFile}
|
||||
disabled={!isConnected || loading.transferring}
|
||||
/>
|
||||
</ThemedView>
|
||||
</ThemedView>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{/* Peripheral Status */}
|
||||
<ThemedView style={styles.section}>
|
||||
<ThemedText type="subtitle">Peripheral Status</ThemedText>
|
||||
<ThemedText>Advertising: {isAdvertising ? 'Yes' : 'No'}</ThemedText>
|
||||
<ThemedText>Connected Centrals: {connectedCentralCount}</ThemedText>
|
||||
{peripheralError && <ThemedText style={styles.errorText}>Error: {peripheralError}</ThemedText>}
|
||||
</ThemedView>
|
||||
|
||||
{/* Device Information */}
|
||||
<ThemedView style={styles.section}>
|
||||
<ThemedText type="subtitle">Device Information</ThemedText>
|
||||
<ThemedText>Activated: {peripheralDeviceInfo.activated ? 'Yes' : 'No'}</ThemedText>
|
||||
<ThemedText>Version: {peripheralDeviceInfo.version || 'Unknown'}</ThemedText>
|
||||
<ThemedText>Name: {peripheralDeviceInfo.devname || 'Unknown Device'}</ThemedText>
|
||||
<ThemedText>Total Space: {peripheralDeviceInfo.allspace || 0} KB</ThemedText>
|
||||
<ThemedText>Free Space: {peripheralDeviceInfo.freespace || 0} KB</ThemedText>
|
||||
<ThemedText>Brand: {peripheralDeviceInfo.brand || 'Unknown'}</ThemedText>
|
||||
</ThemedView>
|
||||
|
||||
{/* Peripheral Controls */}
|
||||
<ThemedView style={styles.section}>
|
||||
<ThemedText type="subtitle">Peripheral Controls</ThemedText>
|
||||
<ThemedView style={styles.buttonRow}>
|
||||
<Button
|
||||
title={peripheralLoading.advertising ? 'Starting...' : 'Start Advertising'}
|
||||
onPress={startAdvertising}
|
||||
disabled={isAdvertising || peripheralLoading.advertising}
|
||||
/>
|
||||
<Button title="Stop Advertising" onPress={stopAdvertising} disabled={!isAdvertising}/>
|
||||
</ThemedView>
|
||||
|
||||
<ThemedView style={styles.buttonRow}>
|
||||
<Button
|
||||
title="Test Read Response"
|
||||
onPress={() => getCharacteristicReadValue(BLE_UUIDS.READ_CHARACTERISTIC)}
|
||||
disabled={!isAdvertising}
|
||||
/>
|
||||
<Button title="Reset Device Info" onPress={resetDeviceInfo}/>
|
||||
</ThemedView>
|
||||
|
||||
<ThemedView style={styles.buttonRow}>
|
||||
<Button title="Toggle Activation"
|
||||
onPress={() => updateDeviceInfo({activated: !(peripheralDeviceInfo.activated ?? false)})}/>
|
||||
<Button title="Update Free Space"
|
||||
onPress={() => updateDeviceInfo({freespace: Math.floor(Math.random() * 1024)})}/>
|
||||
</ThemedView>
|
||||
</ThemedView>
|
||||
|
||||
{/* Peripheral Logs */}
|
||||
<ThemedView style={styles.section}>
|
||||
<ThemedView style={styles.logHeader}>
|
||||
<ThemedText type="subtitle">Peripheral Logs</ThemedText>
|
||||
<Button title="Clear" onPress={clearPeripheralLogs}/>
|
||||
</ThemedView>
|
||||
<ThemedView style={styles.logContainer} lightColor="#eee" darkColor="#2a2a2a">
|
||||
<ScrollView nestedScrollEnabled={false} showsVerticalScrollIndicator={true}>
|
||||
{peripheralLogs.map((log, index) => (
|
||||
<ThemedText key={index} style={styles.logText}>
|
||||
{log}
|
||||
{/* Discovered Devices */}
|
||||
<ThemedView style={styles.section}>
|
||||
<ThemedText type="subtitle">Discovered Devices</ThemedText>
|
||||
<ThemedText style={styles.deviceCount}>Total devices
|
||||
found: {discoveredDevices.length}</ThemedText>
|
||||
{discoveredDevices.length === 0 ? (
|
||||
<ThemedText>No devices discovered yet. Start scanning to find devices.</ThemedText>
|
||||
) : (
|
||||
<ThemedView style={{gap: 8}}>
|
||||
{discoveredDevices.map((item) => (
|
||||
<ThemedView
|
||||
key={item.id}
|
||||
style={[styles.deviceItem, item.connected && styles.connectedDevice]}
|
||||
lightColor="#eee"
|
||||
darkColor="#2a2a2a"
|
||||
>
|
||||
<ThemedView style={styles.deviceInfo} lightColor="transparent"
|
||||
darkColor="transparent">
|
||||
<ThemedText style={item.connected && styles.connectedDeviceText}>
|
||||
{item.name || 'Unknown Device'}
|
||||
</ThemedText>
|
||||
))}
|
||||
</ScrollView>
|
||||
</ThemedView>
|
||||
<ThemedText style={styles.deviceId}>{item.id}</ThemedText>
|
||||
{item.serviceUUIDs && item.serviceUUIDs.length > 0 && (
|
||||
<ThemedText style={styles.serviceUuids}>
|
||||
Services: {item.serviceUUIDs.join(', ')}
|
||||
</ThemedText>
|
||||
)}
|
||||
{item.connected && (
|
||||
<ThemedText style={styles.connectionStatus}>Connected</ThemedText>
|
||||
)}
|
||||
</ThemedView>
|
||||
<Button
|
||||
title={loading.connecting ? 'Connecting...' : (item.connected ? 'Connected' : 'Connect')}
|
||||
onPress={() => connectToDevice(item)}
|
||||
disabled={isConnected || loading.connecting || item.connected}
|
||||
/>
|
||||
</ThemedView>
|
||||
))}
|
||||
</ThemedView>
|
||||
</>
|
||||
)}
|
||||
)}
|
||||
</ThemedView>
|
||||
|
||||
{/* Device Info */}
|
||||
<ThemedView style={styles.section}>
|
||||
<ThemedText type="subtitle">Device Information</ThemedText>
|
||||
<ThemedText>Activated: {isActivated ? 'Yes' : 'No'}</ThemedText>
|
||||
<ThemedText>Version: {version || 'Unknown'}</ThemedText>
|
||||
{deviceInfo && (
|
||||
<>
|
||||
<ThemedText>Name: {deviceInfo.devname}</ThemedText>
|
||||
<ThemedText>Total Space: {deviceInfo.allspace} KB</ThemedText>
|
||||
<ThemedText>Free Space: {deviceInfo.freespace} KB</ThemedText>
|
||||
<ThemedText>Brand: {deviceInfo.brand}</ThemedText>
|
||||
</>
|
||||
)}
|
||||
</ThemedView>
|
||||
|
||||
{/* Transfer Status */}
|
||||
<ThemedView style={styles.section}>
|
||||
<ThemedText type="subtitle">File Transfer</ThemedText>
|
||||
<ThemedText>Transferring: {isTransferring ? 'Yes' : 'No'}</ThemedText>
|
||||
<ThemedText>Progress: {transferProgress}%</ThemedText>
|
||||
</ThemedView>
|
||||
|
||||
{/* Control Buttons */}
|
||||
<ThemedView style={styles.section}>
|
||||
<ThemedText type="subtitle">Controls</ThemedText>
|
||||
<ThemedView style={styles.buttonRow}>
|
||||
<Button title={isScanning ? 'Stop Scan' : 'Start Scan'}
|
||||
onPress={isScanning ? stopScan : startScan}/>
|
||||
<Button
|
||||
title="Disconnect"
|
||||
onPress={disconnectDevice}
|
||||
disabled={!isConnected}
|
||||
/>
|
||||
</ThemedView>
|
||||
|
||||
<ThemedView style={styles.buttonRow}>
|
||||
<Button
|
||||
title={loading.querying ? 'Querying...' : 'Query Activation'}
|
||||
onPress={queryActivationStatus}
|
||||
disabled={!isConnected || loading.querying}
|
||||
/>
|
||||
<Button title={loading.querying ? 'Querying...' : 'Query Version'}
|
||||
onPress={queryDeviceVersion} disabled={!isConnected || loading.querying}/>
|
||||
</ThemedView>
|
||||
|
||||
<ThemedView style={styles.buttonRow}>
|
||||
<Button
|
||||
title={loading.querying ? 'Querying...' : 'Device Info'}
|
||||
onPress={requestDeviceInfo}
|
||||
disabled={!isConnected || loading.querying}
|
||||
/>
|
||||
<Button title="Update Time" onPress={updateActivationTime} disabled={!isConnected}/>
|
||||
</ThemedView>
|
||||
|
||||
<ThemedView style={styles.buttonRow}>
|
||||
<Button title="Identity Check (Valid)" onPress={() => sendIdentityCheck()}
|
||||
disabled={!isConnected}/>
|
||||
<Button title="Identity Check (Invalid)" onPress={() => sendIdentityCheck()}
|
||||
disabled={!isConnected}/>
|
||||
</ThemedView>
|
||||
|
||||
<ThemedView style={styles.buttonRow}>
|
||||
<Button
|
||||
title={loading.transferring ? 'Transferring...' : 'Transfer File'}
|
||||
onPress={transferSampleFile}
|
||||
disabled={!isConnected || loading.transferring}
|
||||
/>
|
||||
</ThemedView>
|
||||
</ThemedView>
|
||||
|
||||
{/* Protocol Info - Available in both modes */}
|
||||
<ThemedView style={styles.section}>
|
||||
|
||||
Reference in New Issue
Block a user