Categories
Tools

FS2EFB data export toolbar

Get latest version

Base functionality copied from Blanik tablet script. Shortly, it’s a toolbar which sends aircraft data to the mobile app. This app then shares data with navigation app, like XCSoar or SkyDemon. Android app supports GPS spoofing, which can be used common nav apps like Google Maps (developer mode should be enabled on the smartphone).

Limitations of current version:
– toolbar can’t be closed while data export is active
– while minimised, toolbar icon disappears only when placed in top left corner of the screen
– iOS app does not work correctly when data export frequency is lower than 5Hz
– some apps require to set data export frequency as 1Hz (these use XPlane data exchange protocol)
-both apps in beta stage and may behave unexpectedly – crash, freeze, stay at background and consume power. You may delete the app while it not used. Report issues if you will find any.

To configure NMEA data export toolbar you have to install the app on your mobile device (separate apps for Android and iOS).

1. Install FS2EFB app:

Android
Follow this link https://play.google.com/apps/testing/com.touchingcloud.msfs2nmea or scan QR code which is on the navigation tablet screen in MSFS. Install the app, blue app icon with glider image will appear on the desktop.
iOS
Follow this link to join testing group https://testflight.apple.com/join/YgDUXEmr
Install TestFlight app from App Store (this is kind of “app store for beta apps”). Open TestFlight, click on the  FS2EFB tab, select INSTALL. Blue app icon with glider image will appear on the desktop.

2. Ensure that your mobile device is connected to the same router as PC/Xbox (either WiFi or LAN), and data exchange between clients is not blocked by firewall (usually don’t if you haven’t set your network as Public).

Send mobile device IP to the server

1. Launch Android/iOS app. Once loaded, it will try yo sync your local IP with server. If you see Sync Success in the log, everything went well. If not, fix connection issues and press SYNC IP.

Configure MSFS nav tablet

1. Time to  configure MSFS part. Open NMEA data export toolbar

2. Press the SYNC IP button

3. If it went well, you will see your local IP and correct port in the field. If not, try to fix connection issues, or type IP by hands using keyboard. Then press START button

4.Starting send to x.x.x.x will appear in log if everything okay, START button became red.5. Back to the FS2EFB app. Incoming data should be indicated in the log as RX lines. If not – something wrong with your network, try to change ports and start from beginning, or change router settings to prevent data exchange block by firewall.

 

 

Screenshot
CONFIGURE NAV APP
XCSoar (Android/iOS)

1.In the app, select TCP Client mode, TCP port 8880. press START button, Web Server OK and TCP Server OK should appear in log. If not, some of the ports are busy – change 5555 to 5556 for example, or 8880 to 8881. Press SYNC IP again, then START.

2. Open XCSoar and select FLY.3. Tap twice the screen, choose CONFIG4. Then DEVICES5. First, disable built in sensors. If you will not do it, your device GPS and barometer sensors data will conflict with simulator data. Select first line and tap DISABLE

6. Then select 2nd device and tab EDIT7. On the device page, set values like on the example picture.
– If your FS2EFB app installed on different device than XCSoar, IP address should match local IP of the device with app installed (you can find it at the top of the black screen
– If you change TCP server port, set new value in TCP Port field.
Once finished, press OK

8. In the devices list, you can see such data comes from the device #2: GPS fix, Baro, Airspeed, Vario. Press CLOSE.9. Validate XCSoar values with simulator data – altitude, airspeed should match.10. Configure XCSoar for your needs – track indication, thermalling assist, download map for your area. You can find detailed manual on the official website https://www.xcsoar.org/

ForeFlight (iOS)

1.In the app, select UDP Server mode, IP 127.0.0.1 (if ForeFlight launched on same device, otherwise local IP of that device), UDP port 49002, ensure XGPS and XATT sentences are enabled. Press START button.

2. Open ForeFlight, tap MENU and select DEVICES

3. MSFS connection should be presented. Tap it to open connection settings.

If it missing, back to NMEA export app and restart the server. Check log for error messages.4. Enable connection, return to devices list

5. MSFS should appear as Connected now

6. Return to map, your simulation location should be received correctly. ForeFlight official tutorial

SkyDemon (Android/iOS)

1.In the app, select UDP Server mode, IP 127.0.0.1 (if SkyDemon launched on same device, otherwise local IP of that device), UDP port 49002, ensure XGPS and XATT sentences are enabled. Press START button.

2. Open SkyDemon, tap Settings gear icon and select Connectivity

3. Enable Flight Simulator option4. Tap Go Flying, select Use X-Plane (as we use X-Plane data sentence)

5. Return to map, your simulation location should be received correctly. SkyDemon official tutorial

Spoiler

1.In the app, select UDP Server mode, IP 127.0.0.1 (if Garmin Pilot launched on same device, otherwise local IP of that device), UDP port 49002, ensure XGPS and XATT sentences are enabled. Press START button.

2. Open Garmin Pilot, tap Settings gear icon

3. Enable Flight Simulation tab, enable Use Flight Simulator Data option4. Return to map, aircraft data should be updated

Garmin Pilot manual

LX North (Android/iOS)

1.In the app, select UDP Server mode, IP 127.0.0.1 (if LX North launched on same device, otherwise local IP of that device), UDP port 49002, ensure XGPS and XATT sentences are enabled. Press START button.

2. Open LX North, tap Library icon and check all MSFS options in the list

3. Return to map, your simulation location should be received correctly.

OzRunways (Android/iOS)

1.In the app, select UDP Server mode, IP 127.0.0.1 (if OzRunways launched on same device, otherwise local IP of that device), UDP port 49002, ensure XGPS and XATT sentences are enabled. Press START button.

2. Open OzRunways, tap Settings gear icon and select SimulatorMode tab. Enable Simulator Mode and Remember selection on startup options

3. Return to map, your simulation location should be received correctly. OzRunways manual

Sky-Map (iOS)

1.In the app, select UDP Server mode, IP 127.0.0.1 (if Sky-Map launched on same device, otherwise local IP of that device), UDP port 49002, ensure XGPS and XATT sentences are enabled. Press START button.

2. Open Sky-Map, tap Menu then Setup

3. Select Wireless Interface Setup option4. Enable connection to device option , select X-Plane device (as we use X-Plane data sentence)

5. Return to map, your simulation location should be received correctly.

Garmin Pilot (Android/iOS)

1.In the app, select UDP Server mode, IP 127.0.0.1 (if Garmin Pilot launched on same device, otherwise local IP of that device), UDP port 49002, ensure XGPS and XATT sentences are enabled. Press START button.

2. Open Garmin Pilot, tap Settings gear icon

3. Select Flight Simulation mode, enable option Use Flight Simulator Data
4. Return to the map, ensure that your location and aircraft data displayed correctly
Garmin Pilot official tutorial

Stratus Insight (iOS)

1.In the app, select UDP Server mode, IP 127.0.0.1 (if Stratus Insight launched on same device, otherwise local IP of that device), UDP port 49002, ensure XGPS and XATT sentences are enabled. Press START button.

2. Open Stratus Insight, tap Settings icon and enable Listen to UDP 49002

3. Return to map, your simulation location should be received correctly. Stratus Insight official tutorial

WingX (Android/iOS)

1.In the app, select UDP Server mode, IP 127.0.0.1 (if SkyDemon launched on same device, otherwise local IP of that device), UDP port 49002, ensure XGPS and XATT sentences are enabled. Press START button.

2. Open WingX, tap Settings gear icon and enable Use X-plane/MS option

5. Return to map, your simulation location should be received correctly. WingX official tutorial

 

 


FS2EFB — Technical Description

System Architecture Overview

FS2EFB is a three-component bridge that extracts real-time telemetry from Microsoft Flight Simulator (MSFS) and delivers it to Electronic Flight Bag (EFB) apps on smartphones and tablets. The data flow is unidirectional:

MSFS (PC)
  └─ In-Game Panel (JavaScript, Coherent GT engine)
       │
       └── HTTP GET requests ──► Smartphone Bridge App (Android or iOS)
                                    │
                                    ├── NMEA 0183 sentences ──► EFB App (TCP or UDP)
                                    ├── GDL90 binary packets ──► EFB App (UDP port 4000)
                                    └── X-Plane DATA* binary ──► EFB App (same transport)

The smartphone acts as a protocol translator: it receives flight data via HTTP, converts it into the formats that EFB apps expect (NMEA, GDL90, or X-Plane binary), and delivers it over the appropriate transport.


Part 1: MSFS In-Game Panel

How Aircraft Data Is Extracted

The panel runs as a Web Component inside MSFS’s Coherent GT browser engine. Coherent GT exposes a global API for reading simulation variables synchronously. Each call takes a variable name and a unit string. The panel reads approximately 20 SimVars every tick:

Position & Navigation: Latitude and longitude (degrees, 6 decimal places), GPS ground track (degrees), magnetic heading (degrees), magnetic variation (degrees).

Altitudes: GPS altitude (meters, geometric) and barometric/indicated altitude (meters).

Speeds: True airspeed, indicated airspeed, and GPS ground speed — all in meters per second.

Vertical rates: Total energy variometer, netto variometer, and vertical speed — all in m/s.

Attitude: Pitch and bank angles in degrees.

Environment: Ambient wind direction (degrees) and wind velocity (km/h).

Time: Local hour, minute, second (2 decimal places), day, month, year (mod 100) — assembled into UTC time (HHMMSS.ss) and date (DDMMYY) strings.

Update Loop and Timing

The main loop is driven by requestAnimationFrame, which fires at the Coherent GT refresh rate (typically 30-60 fps). Two independent delta-time accumulators control data dispatch:

  1. Ownship accumulator: Compared against 1 / frequency (default 5 Hz = 200ms interval). When exceeded, ownship telemetry is sent and the accumulator resets.
  2. Traffic accumulator: Compared against the configured traffic polling interval (default 4 seconds). When exceeded and the previous traffic batch is fully drained, a new traffic fetch is triggered.

This accumulator-based approach decouples the data send rate from the frame rate, providing consistent output regardless of MSFS rendering performance.

How Data Is Sent to the Smartphone

All communication from the panel to the bridge app uses asynchronous HTTP GET requests via XMLHttpRequest.

Ownship request format:

GET http://<ip>:<port>/?user=1&UTCtime=HHMMSS.ss&UTCdate=DDMMYY&TAS=...&IAS=...&GRspeed=...&TE=...&NETTO=...&GPStrack=...&MAGNhead=...&VS=...&lat=...&long=...&GPSalt=...&BAROalt=...&magVar=...&windDir=...&windSpd=...&pitch=...&bank=...&w=...&h=...

All values are SI units (meters, m/s, degrees). The w and h parameters convey the panel’s pixel dimensions for the screen-share feature. Request timeout is 1000ms; errors are silently ignored since the periodic nature of the loop provides automatic retry.

Traffic request format:

GET http://<ip>:<port>/?traffic=1&id=...&name=...&lat=...&long=...&alt=...&vspeed=...&airborne=...&heading=...&speed=...&callsign=...

String values are URI-encoded. Timeout is 1500ms.

IP Address Discovery and Synchronization

The panel needs to know the smartphone’s IP address and port. Two mechanisms exist:

  1. Local persistence: The MSFS SDK provides a key-value storage API. The IP:port string is saved on every keypress of the on-screen numpad and restored when the panel loads. A fallback DOM event listener handles cases where the storage API isn’t immediately available.
  2. Cloud sync: An HTTP GET to an external web server retrieves the IP:port. The smartphone can push its address to this same server via its own SYNC button. The panel validates the response (must contain both : and ., must not contain “Error”) before applying.

Traffic Data Collection and Queuing

Traffic collection is a two-phase process designed to avoid frame-rate drops:

Phase 1 — Bulk fetch: The panel calls an asynchronous Coherent API to request the full list of AI and multiplayer aircraft. This returns a promise. When it resolves, each aircraft is filtered by haversine distance against the configured range (default 250 NM). Qualifying aircraft are pushed onto a queue as structured objects.

Phase 2 — One-per-frame dispatch: Each frame, exactly one aircraft is shifted from the front of the queue and transmitted. Before sending, the panel computes ground speed from position history (a map keyed by aircraft ID storing previous lat/lon/timestamp). If previous data exists, speed is calculated as distance-over-time using haversine. The airborne flag is dynamically adjusted: below 1 m/s forces ground state, above 10 m/s forces airborne. The history map is capped at 500 entries with a 5-minute expiry.

Skip-frame mechanism: After every ownship send, traffic dispatch is suppressed for the current frame and the next frame. This prevents ownship and traffic data from competing for network bandwidth in the same frame.

Configuration Feedback Loop

The HTTP response from the bridge app carries a JSON object with the current configuration:

{
  "webPort": 5555,
  "tcpPort": 49002,
  "transportMode": "UDP_SERVER",
  "dataFrequency": 5,
  "trafficRange": 250,
  "trafficFrequency": 4,
  "nmea": {
    "GPGGA": true, "GPRMC": true, "LXWP0": true,
    "XGPS": true, "XATT": true,
    "XTRAFFIC_GND": false, "XTRAFFIC_AIR": false,
    "GDL90_GND": false, "GDL90_AIR": false,
    "PFLAA_GND": false, "PFLAA_AIR": false
  }
}

The panel applies the received frequency, range, and traffic-enable settings, keeping itself synchronized with the user’s choices on the smartphone. If the response is larger than ~100 bytes and passes magic-byte checks (JPEG: FF D8, PNG: 89 50), it is treated as a screen-share image frame instead.

Network Requirements and Difficulties

Same local network requirement: The MSFS PC and the smartphone must be on the same local network (WiFi). The panel sends HTTP requests directly to the smartphone’s IP address. If the devices are on different subnets or VLANs, communication will fail silently (the panel’s error handler is intentionally empty to avoid log flooding at 5+ Hz).

Firewall considerations: Windows Firewall may block inbound connections to the MSFS panel (if the EFB app needed to connect back), but since the panel is the initiator of HTTP requests, the typical issue is the smartphone’s firewall blocking incoming connections on port 5555. On Android, the app’s own firewall rules usually allow this. On iOS, the app uses Network.framework which handles local network permissions via the iOS local network privacy prompt.

Router AP isolation: Some consumer routers enable “AP isolation” or “client isolation” which prevents WiFi devices from communicating with each other. This silently breaks the bridge. The workaround is to use the smartphone’s mobile hotspot instead.

Hotspot mode: When the smartphone acts as a WiFi hotspot and the PC connects to it, the network topology is simpler (no router in between) and generally more reliable. The Android app provides a WiFi/Hotspot mode toggle that adjusts the displayed IP accordingly.

Port conflicts: Port 5555 is also used by Android Debug Bridge (ADB). If ADB over WiFi is enabled, the bridge app’s web server may fail to bind. The port is configurable to avoid conflicts.


Part 2: Smartphone Bridge Apps

Web Server (Receiving MSFS Data)

Both Android and iOS apps run a TCP server on the configurable web port (default 5555). When an HTTP GET request arrives:

  1. CORS preflight (OPTIONS) requests receive a 204 No Content with permissive CORS headers.
  2. The query string is parsed into key-value pairs.
  3. A traffic=1 parameter routes to traffic processing; otherwise it’s ownship data.
  4. The HTTP response is either JSON configuration (described above) or, if screen sharing is active on Android, a Base64-encoded JPEG image.

Every received request resets a 30-minute inactivity timer. If no data arrives for 30 minutes, the service auto-stops to conserve battery.

Output Transport Modes

Transport modes are named from the EFB app’s perspective, not the phone’s:

ModePhone’s RoleHow It Works
TCP ClientPhone hosts a TCP server on the output port.EFB connects as a TCP client. Multiple EFB connections are accepted and stored in a thread-safe list. Data is broadcast to all connected clients.
TCP ServerPhone connects out as a TCP client.The EFB hosts a TCP server. The phone lazily connects to the target IP:port when data needs to be sent. A 2-second cooldown prevents rapid reconnection attempts.
UDP ClientPhone binds a UDP socket on the output port.The EFB sends a discovery/registration UDP packet to the phone. The phone records the sender’s address and replies with data to that endpoint.
UDP ServerPhone creates an unbound UDP socket.The phone sends UDP datagrams to the configured target IP and output port. No handshake required — fire and forget.

GDL90 always uses a separate dedicated UDP socket on port 4000, regardless of the NMEA transport mode. This matches the behavior of real ADS-B receivers (like Stratux) that EFB apps expect.

Data Flow Summary

HTTP Request (port 5555)
  │
  ├── Parse query parameters
  │
  ├── Ownship data?
  │     ├── Generate enabled NMEA sentences ──► Send via selected transport
  │     ├── Generate X-Plane DATA* packets ──► Send via selected transport (if RPOS enabled)
  │     ├── Generate GDL90 ownship report ──► Send via dedicated UDP:4000
  │     ├── Generate GDL90 AHRS report ──► Send via dedicated UDP:4000
  │     └── Update GPS mock location (Android only, if enabled)
  │
  └── Traffic data?
        ├── Generate XTRAFFIC sentence ──► Send via selected transport
        ├── Generate PFLAA sentence ──► Send via selected transport
        └── Generate GDL90 traffic report ──► Send via dedicated UDP:4000

Part 3: Output Protocols in Detail

NMEA 0183 Sentences

All standard NMEA sentences follow the format: $<TALKER><SENTENCE>,<fields>*<XX>\r\n where <XX> is the XOR checksum of all bytes between $ and * (exclusive), formatted as two uppercase hex digits.

Coordinate encoding: Decimal degrees are converted to DDmm.mmmm format (degrees + minutes with 4 decimal places). Latitude uses 2-digit degrees (DDmm.mmmm), longitude uses 3-digit degrees (DDDmm.mmmm). Hemisphere is indicated by N/S or E/W suffix.

$GPGGA — GPS Fix Data

$GPGGA,<HHMMSS.ss>,<lat>,<N/S>,<lon>,<E/W>,1,08,0.9,<alt_m>,M,0.0,M,,*XX\r\n
  • Fix quality: hardcoded to 1 (GPS fix)
  • Satellites: hardcoded to 08
  • HDOP: hardcoded to 0.9
  • Altitude: GPS altitude in meters MSL
  • Geoid separation: hardcoded to 0.0

$GPRMC — Recommended Minimum

$GPRMC,<HHMMSS.ss>,A,<lat>,<N/S>,<lon>,<E/W>,<speed_kts>,<track>,<DDMMYY>,<magVar>,<E/W>,A*XX\r\n
  • Status: always “A” (active)
  • Speed: ground speed converted from m/s to knots (x 1.94384)
  • Mode indicator: always “A” (autonomous)

$LXWP0 — LXNAV Variometer

$LXWP0,Y,<ias_kmh>,<baro_alt_m>,<vario_ms>,,,,,,<heading>,<wind_dir>,<wind_spd>*XX\r\n
  • IAS converted from m/s to km/h (x 3.6)
  • Vario: total energy in m/s
  • Five empty fields between vario and heading

XGPS — ForeFlight/SkyDemon GPS (Proprietary)

XGPSFS,<lon>,<lat>,<alt_m>,<track>,<groundspeed_ms>\r\n
  • No $ prefix, no checksum — raw proprietary format
  • Note: longitude comes before latitude
  • Units: meters and m/s (no conversion)

XATT — ForeFlight/SkyDemon Attitude (Proprietary)

XATTFS,<heading>,<-pitch>,<-roll>,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0\r\n
  • No $ prefix, no checksum
  • Pitch and roll are sign-negated (MSFS convention to EFB convention)
  • Nine trailing zero-value placeholder fields

XTRAFFIC — Custom MSFS Traffic (Proprietary)

XTRAFFIC<name>,<id>,<lat>,<lon>,<alt>,<vspeed>,<airborne>,<heading>,<speed>,<callsign>\r\n
  • No $ prefix, no checksum
  • Aircraft name is embedded directly in the sentence identifier

$PFLAA — FLARM Traffic

$PFLAA,0,<relNorth>,<relEast>,<relVert>,2,<hexID>,<track>,,<speed>,<vspeed>,8*XX\r\n
  • Relative distances use flat-Earth approximation: ~111,320 m/degree latitude; longitude scaled by cos(ownship latitude)
  • Traffic ID is hashed to a 6-character uppercase hex string (lower 24 bits of hash)
  • Aircraft type: hardcoded to 8 (powered aircraft)

X-Plane Binary DATA* Packets

When the RPOS mode is enabled, three separate 41-byte binary packets are sent in the standard X-Plane UDP DATA format:

Each packet structure:

Bytes 0-4:   ASCII "DATA*" (0x44 0x41 0x54 0x41 0x2A)
Bytes 5-8:   Group index (32-bit little-endian integer)
Bytes 9-40:  Eight 32-bit little-endian IEEE 754 floats

Group 20 (Position): latitude (deg), longitude (deg), altitude MSL (feet), altitude AGL (feet). Four unused zeros.

Group 3 (Speeds): IAS (knots), EAS (= IAS), TAS (knots), ground speed (knots). All converted from m/s (x 1.94384).

Group 17 (Attitude): pitch (deg, negated), roll (deg, negated), true heading (deg), magnetic heading (deg). Four unused zeros.

Additionally, a proprietary compact 41-byte RPOS packet format exists that interleaves longitude bytes across non-contiguous positions for a specific EFB app compatibility. The longitude float32 is split: its MSB is placed at byte 0 (serving as a frame-sync byte), while the lower 3 bytes are placed at bytes 38-40.

GDL90 Binary Protocol

GDL90 is the FAA standard for ADS-B data exchange, used by hardware receivers like Stratux and ForeFlight Sentry. The bridge emulates this protocol over UDP port 4000.

Frame Structure

Every GDL90 message is wrapped in a frame:

[0x7E] [message payload with byte stuffing] [CRC-16 LSB] [CRC-16 MSB] [0x7E]
  • Flag byte: 0x7E marks frame boundaries
  • Byte stuffing: Within the frame (excluding flag bytes), 0x7E is escaped as 0x7D 0x5E, and 0x7D is escaped as 0x7D 0x5D
  • CRC-16: Polynomial 0x1021 (CRC-CCITT), initial value 0x0000. Computed over the unescaped message payload. Appended LSB-first, and the CRC bytes themselves are subject to byte stuffing.

Heartbeat (Message ID 0x00)

7-byte payload: [0x00, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00]

  • Status byte 1: 0x81 = initialized + GPS valid
  • Sent every 1 second (required by the GDL90 specification)

Ownship Report (Message ID 0x0A) and Traffic Report (Message ID 0x14)

29 bytes total (1 byte message ID + 28 bytes payload). Both share the same structure:

Offset  Size  Field                    Encoding
──────  ────  ─────                    ────────
0       1     Message ID               0x0A (ownship) or 0x14 (traffic)
1       1     Alert/Address Type       Always 0x00
2-4     3     Participant Address      3-byte hash of ID string (ownship uses 0xAAAAAA)
5-7     3     Latitude                 24-bit signed: degrees / 180 * 2^23
8-10    3     Longitude                Same encoding as latitude
11-12   2     Altitude + Misc          Upper 12 bits: (alt_feet + 1000) / 25, clamped [0, 0xFFE]
                                       Lower 4 bits (misc nibble):
                                         bit 3 = airborne (1=yes, 0=ground)
                                         bit 0 = true track angle (always 1)
                                       → 0x09 for airborne, 0x01 for ground
13      1     NIC/NACp                 Fixed 0xB0
14-15   2     Horizontal Speed         12-bit unsigned scalar in knots, clamped [0, 4094]
              + Vertical Velocity      Byte 14: speed[11..4] (upper 8 bits)
                                       Byte 15 upper nibble: speed[3..0]
                                       Byte 15 lower nibble: vv[11..8]
16      1     Vertical Velocity cont.  vv[7..0] (lower 8 bits)
                                       12-bit two's complement, 64 fpm per LSB
                                       Clamped [-2048, 2047]
17      1     Track/Heading            0-360° mapped to 0-255 (heading * 256 / 360)
18      1     Emitter/Emergency        Upper nibble = category (1 = light aircraft)
                                       Lower nibble = emergency (0 = none) → 0x10
19-26   8     Callsign                 8 ASCII characters, space-padded

Unit conversions applied:

  • Altitude: meters to feet (x 3.28084)
  • Speed: m/s to knots (x 1.94384)
  • Vertical speed: m/s to ft/min (x 196.8504), then divided by 64 for the 64-fpm LSB

Note on speed encoding: The horizontal speed and vertical velocity share byte 15 — the upper nibble contains the 4 least significant bits of speed, while the lower nibble contains the 4 most significant bits of the 12-bit vertical velocity value. This packing is specified by the GDL90 standard.

AHRS/Attitude Report (Message ID 0x4C — ForeFlight Proprietary)

13-byte payload (1 byte message ID + 12 bytes data):

Offset  Size  Field      Encoding
──────  ────  ─────      ────────
0       1     Msg ID     0x4C
1-2     2     Pitch      Signed 16-bit big-endian, tenths of a degree
3-4     2     Roll       Signed 16-bit big-endian, tenths of a degree
5-6     2     Heading    Unsigned 16-bit big-endian, tenths of a degree
7-12    6     Reserved   All zeros

Part 4: Presets System

Both Android and iOS apps include a preset system that configures the bridge for specific EFB applications. Presets are stored in a bundled CSV file with 18 columns:

EFB name, TCP_CLIENT, TCP_SERVER, UDP_CLIENT, UDP_SERVER, tcpPort, GPGGA, GPRMC, LXWP0, XGPS, XATT, Rpos, XTRAFFICGnd, XTRAFFICAir, Gdl90Gnd, Gdl90Air, PflaaGnd, PflaaAir

Value semantics for each integer:

ValueMeaning
0Force OFF and lock the control (disabled, nearly invisible at 5% opacity)
1Leave the control enabled but don’t change its current state (user’s choice)
2Force ON — check the checkbox or select the transport mode

This three-state system allows presets to enforce required settings while leaving optional ones to the user.

Preset categories in the bundled data:

  • XGPS/XATT-based aviation EFBs (ForeFlight, SkyDemon, Garmin Pilot, FlyQ EFB, AirMate, FltplnGO): UDP Server, port 49002, XGPS + XATT enabled, XTRAFFIC for traffic
  • GDL90-based aviation EFBs (WingX, OzRunways, Stratus Insight, Sky-Map, EasyVFR, iFly GPS): UDP Server, port 49002, XGPS + XATT plus GDL90 traffic
  • Soaring/glider EFBs (XCSoar, LK8000): TCP Client or UDP Server, port 4353, standard NMEA (GPGGA + GPRMC + LXWP0) plus PFLAA traffic
  • Binary RPOS EFBs (Air Navigation Pro, AvPlan EFB): UDP Server, port 49002, only RPOS binary output
  • Marine EFBs (Aqua Map, iNavX, iSailor, SeaNav, i-Boating): Various TCP modes, custom ports, only GPGGA + GPRMC

Selecting “EFB Preset” (the first entry) restores all controls to their unlocked state.


Part 5: Android-Exclusive Features

GPS Spoofing (Mock Location Provider)

The Android app can inject simulated GPS coordinates into the system location service, making all apps on the device (including EFB apps that read GPS) believe the phone is at the simulated aircraft’s position.

How it works:

  1. Permission flow: The app checks for ACCESS_FINE_LOCATION runtime permission, then probes for mock location capability by attempting to add a test provider to the system’s Location Manager. This requires the app to be set as the “Mock Location App” in Android Developer Options.
  2. Prominent disclosure: Before enabling, a dialog explains that the app will replace the device’s real GPS with simulated flight data, with an explicit consent mechanism.
  3. Location injection: On each ownship HTTP request, if spoofing is enabled, the service creates a mock Location object with the simulated latitude, longitude, altitude, speed, bearing, and a synthetic accuracy value. This is pushed to the system’s Location Manager as a test provider update. All Android apps reading GPS will receive this simulated position.
  4. Service lifecycle: The foreground service declares LOCATION as a foreground service type when spoofing is active. On stop, the mock provider is removed, restoring real GPS.

Screen Sharing (MediaProjection)

The Android app can capture the device’s screen and transmit it back to the MSFS in-game panel, enabling VR users to see their EFB app without removing the headset.

How it works:

  1. Permission flow: A disclosure dialog explains screen capture. On acceptance, the system’s screen capture intent is launched via startActivityForResult.
  2. Capture pipeline: When granted, the result code and intent data are passed to the service. The service creates a MediaProjection, an ImageReader (for receiving frames), and a VirtualDisplay that renders the screen content into the ImageReader’s surface.
  3. Frame serving: Captured frames are stored as a volatile Bitmap reference behind a synchronized lock. When the MSFS panel sends an HTTP request with width/height parameters (indicating it wants a screen image), the latest bitmap is resized to fit the requested dimensions while maintaining aspect ratio, compressed as JPEG at quality 60, Base64-encoded, and returned as a data:image/jpeg;base64,... string in the HTTP response body.
  4. Throttling: Image frames are sent at most every 500ms (or 100ms during active drag gestures) to balance quality and bandwidth.
  5. Panel-side rendering: The MSFS panel detects image responses by checking magic bytes and displays them using a double-buffered foreground/background image pair for smooth transitions.

Remote Touch Simulation (Accessibility Service)

The Android app can simulate touch gestures on the device screen, dispatched remotely from the MSFS in-game panel. This enables VR users to interact with the EFB app without taking off the headset.

How it works:

  1. Touch event flow: The MSFS panel captures mouse clicks on the screen-share image and sends the coordinates as HTTP query parameters (percentage-based X/Y and gesture type). The bridge service broadcasts these as a namespaced intent.
  2. Accessibility Service: A separate Android Accessibility Service receives the broadcast intents and uses the Accessibility API’s gesture dispatch mechanism to simulate touches.
  3. Coordinate conversion: Incoming coordinates are percentages (0-100%) of the screen. The service queries real display metrics and converts to absolute pixel coordinates.
  4. Four gesture types:
    • “tap”: Creates a stroke path at the coordinates with a 50ms duration and dispatches immediately.
    • “down”: Begins a continued/drag gesture using the accessibility continued-stroke API with a 100ms segment and the “willContinue” flag.
    • “drag”: Continues the stroke at updated coordinates with another 100ms segment.
    • “up”: Ends the continued stroke with “willContinue” set to false.
  5. Safety timeout: A 2-second timer auto-releases orphaned drag gestures if no follow-up event arrives.
  6. Permission: Requires the user to manually enable the Accessibility Service in Android Settings. The app provides a direct link to the correct settings page.

Thread Safety (Android)

The Android service uses several mechanisms for thread safety across its web server thread, network I/O threads, and the main thread:

  • Volatile fields: All shared state (running flag, timestamps, transport mode, ownship coordinates, UDP peer address, bitmap reference, generation counter) is declared volatile for visibility guarantees.
  • CopyOnWriteArrayList: TCP client connections are stored in a thread-safe list that allows concurrent iteration and modification without explicit locking.
  • Synchronized locks: The screen capture bitmap is guarded by a dedicated lock object; old bitmaps are recycled within the lock.
  • Generation counter: An integer counter is incremented on every socket restart. Each background thread captures the current generation when it starts and exits silently if the generation changes, preventing stale threads from producing confusing error messages during reconfiguration.

Part 6: iOS-Specific Details

Background Execution

iOS aggressively suspends apps that are not actively producing audio or using location services. The bridge app plays a silent audio loop using AVAudioEngine: a 1-second buffer of zeros at 44,100 Hz, looped continuously on a player node. The audio session is configured with the .playback category, which prevents iOS from suspending the app when the screen locks.

Audio Variometer

The iOS app includes an audio variometer that produces climb/sink tones based on the aircraft’s vertical speed:

Three zones based on climb rate (m/s):

ZoneRangeAudio Behavior
Dead-0.5 to +0.5 m/sSilence
SinkBelow -0.5 m/sContinuous solid tone, pitch decreasing with sink rate. At -0.5 m/s: ~440 Hz; at -5.0 m/s: ~220 Hz
LiftAbove +0.5 m/sPulsed/beeping tone. Pitch and beep rate increase with climb rate. At +0.5 m/s: ~440 Hz, slow beeps (~0.37s interval); at +10 m/s: ~1067 Hz, rapid beeps (0.1s interval)

The implementation uses a 440 Hz sine wave buffer played through an AVAudioUnitVarispeed node. Changing the varispeed rate shifts both pitch and playback speed proportionally. Pulsing is achieved by a repeating timer that toggles the player volume between the master level and zero. To avoid audio jitter, the timer is only recreated if the interval changes by more than 0.05 seconds.

Networking

The iOS app uses Apple’s Network.framework (NWListener, NWConnection) exclusively — no third-party libraries. Network I/O runs on a private concurrent dispatch queue, while client list mutations are serialized on a dedicated serial queue to prevent race conditions.

Settings Persistence

All user settings use SwiftUI’s @AppStorage (backed by UserDefaults), providing automatic persistence without explicit save/load logic. The bridge manager is an ObservableObject whose published properties drive the SwiftUI view updates on the main thread.

FS2EFB data export toolbar v0.6 (FS20)

  • navigation apps presets
  • better EFB support (artificial horizon, AI traffic)

Android app version required: 0.20 iOS app version required: 1.20

FS2EFB data export toolbar v0.5 (FS20)

traffic export, available sentences
* XTRAFF (X-Plane format, SkyDemon ForeFlight Garmin Pilot etc.)
* GDL90 (Stratus Insight)
* PFLAA (XCSoar, LK8000 – LXNAV data type)

Android app version required: 0.19
iOS app version required: 1.19

FS2EFB data export toolbar v0.4 (FS20)

* screen share of selected app or whole screen
* screen tap simulation (optional, require special accessibility permission)
* frequency slider moved from toolbat to the app
* minor UI changes
* default values set for SkyDemon/Foreflight compatibility

FS2EFB data export toolbar v0.2 (FS20)

– minimized GPS icon appears only when cursor is nearby (only works when placed in top left corner due to toolbar system limitations)
– QR images scalable
– frequency and IP stored in used data and restored on toolbar restart

FS2EFB data export toolbar v0.1 (FS20)

base functionality copied from Blanik tablet script. Shortly, it’s a toolbar which sends aircraft data to the mobile app. This app then shares data with navigation app, like XCSoar or SkyDemon. Android app supports GPS spoofing, which can be used common nav apps like Google Maps (developer mode should be enabled on the smartphone).

Have some questions?

Your email address will not be published.

Email and website fields are optional.

The maximum upload file size: 10 MB. You can upload: image. Drop files here