Workflow
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);
});