Skip to main content

New Page

BLE Service Overview

The SafeSky app broadcasts NMEA messages over a standardized BLE GATT service:

  • Service UUID: FFE0
  • Characteristic UUID (Notify/Write): FFE1
  • Direction: Notify only (BLE receiver listens for data from SafeSky)

No pairing required. You can connect and subscribe directly to receive data.


BLE Advertising & Device Identification

To support automatic reconnection workflows, especially on iOS (where BLE MAC addresses are randomized), the app advertises a unique and stable local name:

SafeSkyNMEA-<5charID>
  • Example: SafeSkyNMEA-A1B2C
  • You may use this name to cache and re-identify the device on future scans
  • If your use case doesn't require reconnection, simply scan by service UUID as usual

NMEA Message Format

The SafeSky app emits standard NMEA 0183 sentences over BLE. These messages are streamed in real time and may be split across multiple packets depending on MTU.

Currently, the following sentence types are emitted:

Supported NMEA Sentences

Type Description
$GPRMC Position, time, course, speed
$GPGGA GPS fix data, time, altitude
$PFLAA Relative traffic position (FLARM-compatible)

NMEA Sentence Formats

$PFLAA,<AlarmLevel>,<RelativeNorth>,<RelativeEast>,<RelativeVertical>,<IDType>,<ID>,<Track>,<TurnRate>,<GroundSpeed>,<ClimbRate>,<AcftType>
$GPGGA,<Time>,<Latitude>,<NS>,<Longitude>,<EW>,<FixQuality>,<NumSV>,<HDOP>,<Altitude>,<M>,<Separation>,<M>,<DGPSAge>,<DGPSRef>
$GPRMC,<Time>,<Status>,<Latitude>,<NS>,<Longitude>,<EW>,<Speed>,<Course>,<Date>,<MagVar>,<MagVarDir>,<Mode>

Reference: FLARM NMEA sentence documentation (ICD 7.19)

Example Sentences

$GPRMC,091946,A,4827.3033,N,00414.6889,W,0.0,94.2,160725,,*30
$GPGGA,091946,4827.3033,N,00414.6889,W,,,,7.5,M,,M,,*54
$PFLAA,0,1113,738,492,1,syDEMO-TRAFFIC,90,0,45,0,8*2F
$PFLAA,0,29376,50485,11960,1,4CADE5!RYR4AK,212,,213,0.0,9,0,1*1E

Buffering and Sentence Reassembly

NMEA sentences may arrive in chunks due to BLE MTU limitations. Always implement buffering logic to reconstruct complete messages.

Buffering Example

let buffer = '';

function onDataReceived(chunk: string) {
  buffer += chunk;

  // Match complete NMEA sentences
  const regex = /\$(?:[^$*]+)\*[0-9A-Fa-f]{2}/g;
  const matches = buffer.match(regex) || [];

  matches.forEach(sentence => {
    handleNMEASentence(sentence);
  });

  const lastIndex = buffer.lastIndexOf(matches[matches.length - 1] || '');
  buffer = lastIndex >= 0 ? buffer.slice(lastIndex + matches[matches.length - 1]?.length || 0) : buffer;
}

BLE Connection Flow (Typical)

await bluetoothLE.initialize();

await bluetoothLE.startScan({
  services: ['FFE0']
});

const device = findDeviceByNamePrefix('SafeSkyNMEA-'); // Optional: reconnect using name
await bluetoothLE.connect({ address: device.address });
await bluetoothLE.discover({ address: device.address });

const subscription = bluetoothLE.subscribe({
  address: device.address,
  service: 'FFE0',
  characteristic: 'FFE1'
}).subscribe(data => {
  const chunk = bluetoothLE.bytesToString(
    bluetoothLE.encodedStringToBytes(data.value)
  );
  onDataReceived(chunk);
});