Skip to main content

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

  1. Always store the device local name (AEROXXXXXX) in your app's persistent storage
  2. Cache the resolved address during the current session for performance
  3. Re-scan when needed if the cached address becomes invalid
  4. 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');
      }
    }
  }
}