Platform Considerations
Handle BLE differences on iOS and Android: UUIDs, MTU, permissions, and IDs.
Bluetooth Implementation Notes
General BLE Considerations
- On iOS, UUIDs may appear with a
0x
prefix in some contexts. Do not include this prefix when scanning or subscribing. - On Android, short-form UUIDs like
FFE0
are internally represented as full 128-bit UUIDs (e.g.0000ffe0-0000-1000-8000-00805f9b34fb
), but you can still pass the short form in scan or connect calls. - On Android, updating the MTU (Maximum Transmission Unit) to 500 bytes is recommended for better performance, especially when using the SafeSky proprietary protocol.
MTU Configuration Example
// Set MTU for better performance (Android only)
if (platform.is('android')) {
try {
const mtuResponse = await bluetoothLE.mtu({
address: deviceAddress,
mtu: 500
});
console.log(`MTU set to: ${mtuResponse.mtu}`);
} catch (error) {
console.warn('Failed to set MTU:', error);
// Continue anyway - not critical for NMEA reception
}
}
Cross-Platform Device Identification Challenge
The Problem
- Android exposes the actual MAC address of Bluetooth devices (e.g.,
12:34:56:78:90:AB
) - iOS generates random UUIDs for privacy protection (e.g.,
XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
)
This creates a significant challenge for cross-platform applications that need to reliably identify and reconnect to the same device.
The Solution
Use the device's advertised local name (AEROXXXXXX
) as the primary identifier instead of the Bluetooth address:
// Store devices by local name instead of address
private deviceCache = new Map<string, BluetoothAddress>();
async resolveDeviceAddress(localName: string): Promise<string> {
// Check cache first
if (this.deviceCache.has(localName)) {
return this.deviceCache.get(localName);
}
// Scan for device by name
const devices = await this.scanForDevices();
const device = devices.find(d =>
d.name === localName ||
d.advertisement?.localName === localName
);
if (device) {
this.deviceCache.set(localName, device.address);
return device.address;
}
throw new Error(`Device ${localName} not found`);
}
// Usage in your app
const deviceName = 'AERO123456';
try {
const address = await resolveDeviceAddress(deviceName);
await connectToDevice(address);
} catch (error) {
console.error('Failed to find device:', error);
}
Implementation Strategy
- Always store the device local name (
AEROXXXXXX
) in your app's persistent storage - Cache the resolved address during the current session for performance
- Re-scan when needed if the cached address becomes invalid
- Handle connection failures gracefully by clearing cache and re-scanning
Platform-Specific Connection Handling
iOS Considerations
- Background BLE operations are limited
- Connection state restoration is available but requires proper implementation
- Scanning while the app is backgrounded requires specific permissions
Android Considerations
- Location permissions are required for BLE scanning
- Different permission models for Android 12+ (API 31+)
- Better support for background BLE operations
- MTU negotiation is more reliable
// Android permission handling example
private async handleAndroidPermissions() {
if (this.platform.is('android')) {
const apiLevel = parseInt(device.sdkVersion, 10);
if (apiLevel >= 31) {
// Android 12+ permissions
const btScanGranted = await this.requestPermission('BLUETOOTH_SCAN');
const btConnectGranted = await this.requestPermission('BLUETOOTH_CONNECT');
if (!btScanGranted || !btConnectGranted) {
throw new Error('Bluetooth permissions required');
}
} else {
// Android <12 permissions
const locationGranted = await this.requestPermission('ACCESS_COARSE_LOCATION');
if (!locationGranted) {
throw new Error('Location permission required for BLE scanning');
}
}
}
}