Audience: iOS engineers building a custom app that connects to a Columbus EX-1 RTK GNSS receiver over Bluetooth Low Energy
Platform: iOS 15+ recommended (CoreBluetooth)
Receiver: Columbus EX-1 (u-blox ZED-F9P inside)
Status: Field-proven integration notes for GATT connect, NMEA download, RTCM uplink, and Rover/Base role configuration
This document is a neutral third-party integration guide. It describes the BLE interface and the UBX/NMEA/RTCM traffic needed to use EX-1 from your own iOS app. Official Columbus and u-blox manuals remain the primary references for product policy and chip protocol details. This guide explains what to do; implement the CoreBluetooth and protocol details in your own code.
Table of contents
- What you can build
- Architecture overview
- Critical: which BLE name to use
- GATT profile
- iOS project setup
- Minimal path: scan → connect → NMEA
- Implementing the BLE client
- Parsing the notify stream
- NMEA position parsing
- RTK Rover: write RTCM3 corrections
- UBX command framing
- Role configuration: Rover / Base
- Permanent save (BBR / Flash)
- Recommended transaction rules
- Accuracy estimates (optional)
- Pitfalls checklist
1. What you can build
| Level | Capability |
|---|---|
| A | Discover EX-1, connect, receive NMEA, show lat/lon/fix |
| B | Inject RTCM3 corrections over BLE (RTK Rover) |
| C | Configure EX-1 as Rover or Base via UBX (Survey-In / Fixed), then optionally save |
You do not send Columbus CONFIG.TXT over BLE. That file is for microSD boot import. Live configuration on BLE uses u-blox UBX binary messages written to the same characteristic that carries NMEA.
2. Architecture overview
iOS App (Central) ←── Notify FFF1: NMEA (+ UBX/RTCM) ── EX-1 NTRIP BLE
─── Write FFF1: RTCM3 and/or UBX ───► Service FFF0 / Char FFF1
- iPhone / iPad = BLE Central (
CBCentralManager) - EX-1 = BLE Peripheral
- Downlink (receiver → app): primarily NMEA 0183 text; may be interleaved with UBX (sync
B5 62) and RTCM3 (preambleD3) when Base/raw modes are active - Uplink (app → receiver): RTCM3 binary for corrections, and/or UBX binary for configuration
Baud rate of the internal UART behind the BLE bridge is not something your app sets. Use ATT MTU / maximumWriteValueLength for write chunking.
3. Critical: which BLE name to use
EX-1 advertises two Bluetooth interfaces:
| Advertised name | Use for your app? | Notes |
|---|---|---|
EX-1 NTRIP BLE |
Yes — always use this | Supports NMEA download and RTCM3 / UBX write (full RTK workflow) |
EX-1 BLE (no NTRIP) |
No (for RTK / config) | Nearby name for other uses; do not rely on it for RTCM uplink or role writes |
Matching rule: connect only when the advertised / peripheral name has prefix EX-1 NTRIP BLE.
Do not identify EX-1 only by service UUID FFF0. Several Columbus models share FFF0 / FFF1. Always combine:
- Name prefix
EX-1 NTRIP BLE - Then discover service
FFF0and characteristicFFF1
4. GATT profile
| Item | Value |
|---|---|
| Service UUID | 0000FFF0-0000-1000-8000-00805F9B34FB (16-bit FFF0) |
| Characteristic UUID | 0000FFF1-0000-1000-8000-00805F9B34FB (16-bit FFF1) |
| Notify / Indicate | Required for GNSS stream |
| Write | Same characteristic; supports write and/or writeWithoutResponse |
Documented Columbus behavior for this family: one characteristic for both directions (UART-over-GATT style).
Connection sequence:
- Scan peripherals (do not filter by service UUID alone).
- Keep only names with prefix
EX-1 NTRIP BLE. - Connect; discover service
FFF0, then characteristicFFF1. - Verify notify or indicate is available; enable notifications.
- Start buffering and parsing notification data.
- Use a connection timeout around 20 seconds.
5. iOS project setup
Info.plist
- Set
NSBluetoothAlwaysUsageDescriptionto a clear user-facing reason (connect to a Columbus EX-1 GNSS receiver). - Optionally add
UIBackgroundModes→bluetooth-centralif you need brief background streaming or reconnect.
Capabilities
- Enable Background Modes → Uses Bluetooth LE accessories only if you need background central reconnect / notify.
- Background scanning is restricted on iOS; plan for foreground discovery and optional restore identifier if you need reconnect after kill/relaunch.
Entitlements / privacy
No special Apple entitlement beyond the standard Bluetooth usage description is required for central mode. Test on a physical iPhone; Simulator is not useful for real EX-1 hardware.
6. Minimal path: scan → connect → NMEA
Goal: show a live fix outdoors.
- Power on EX-1 with clear sky view.
- Scan and select
EX-1 NTRIP BLE. - Subscribe to
FFF1notifications. - Buffer bytes into lines ending with CR/LF.
- Parse GGA / RMC sentences (talker may be
GN,GP,GL, etc.). - Display latitude, longitude, altitude, fix quality.
Factory Rover profile (Mode=ROVER, RawData=OFF) typically emits RMC / GGA / GSA / GSV. Example address forms: $GNGGA, $GNRMC.
7. Implementing the BLE client
Build a small CoreBluetooth central that:
- Creates
CBCentralManagerand waits for.poweredOn. - Scans without relying on service UUID filters for discovery (name filter is primary).
- Connects the chosen peripheral and discovers
FFF0/FFF1. - Enables notify on
FFF1and forwards each update to your parsers. - Exposes a write path that chunks payloads with
maximumWriteValueLength(for:)(floor around 20 bytes is a safe minimum if the stack reports a tiny value).
Write type guidance: for RTCM, prefer write with response when the characteristic supports it (flow control). For short UBX commands, write without response is often acceptable if with-response is unavailable. Always fall back to whichever write property exists.
Cancel an in-flight connect after ~20 seconds if the link never reaches connected + notifying.
8. Parsing the notify stream
Notifications are not guaranteed to be one NMEA sentence per callback. A single callback may contain a partial sentence, multiple sentences, or NMEA mixed with UBX and/or RTCM3.
Practical demux strategy
- Position-only app: keep printable ASCII plus CR/LF; replace other bytes with a line break so binary traffic does not corrupt UTF-8 line assembly; emit complete lines that start with
$. - Base / UBX ACK / RTCM verification: keep a raw byte buffer and run parallel framers:
- NMEA: starts with
$, ends with\r\n, checksum after* - UBX: sync
B5 62, then class, id, little-endian length, payload, CK_A, CK_B - RTCM3: preamble
D3, 10-bit length, payload, 24-bit CRC
- NMEA: starts with
Never force an entire notification through UTF-8 decoding without filtering.
9. NMEA position parsing
Coordinate format
NMEA lat/lon fields are ddmm.mmmm / dddmm.mmmm, not decimal degrees. Convert: degrees = floor(value / 100), minutes = remainder, decimal = degrees + minutes / 60; negate for S/W.
GGA fields (common)
| Index | Meaning |
|---|---|
| 1 | UTC time hhmmss.ss |
| 2–3 | Latitude + N/S |
| 4–5 | Longitude + E/W |
| 6 | Fix quality (0 invalid, 1 GPS, 2 DGPS, 4 RTK fixed, 5 RTK float, …) |
| 7 | Satellites |
| 8 | HDOP |
| 9 | Altitude (often orthometric / MSL — check your product notes) |
| 11 | Geoid separation |
RMC
Use status field A (active). Provides speed (knots) and course.
Talker IDs
Parse by the last three characters of the address field (GGA, RMC, …), not by assuming $GN only.
10. RTK Rover: write RTCM3 corrections
Once notify is active on EX-1 NTRIP BLE:
- Connect your app to an NTRIP caster (TCP) as a client.
- Decode the HTTP response body to a pure RTCM3 byte stream (if the caster uses HTTP chunked encoding, strip chunk framing first).
- Write those bytes to
FFF1, preserving packet order and chunking to the platform writable length.
Write tips
| Topic | Recommendation |
|---|---|
| Characteristic | Same FFF1 |
| Write type for RTCM | Prefer with-response when available |
| Chunk size | Use maximumWriteValueLength — never assume 20 forever |
| Ordering | Preserve RTCM packet order |
| During Survey-In | Do not inject Rover corrections into a Base that is still surveying |
How you know RTK is working
Watch GGA fix quality: 4 = RTK Fixed, 5 = RTK Float, 1 / 2 = single / DGPS. Also watch age of differential in GGA when present.
11. UBX command framing
EX-1 configuration over BLE uses u-blox UBX packets:
B5 62 | class | id | length_le (2) | payload | CK_A | CK_B
Checksum covers class through end of payload (not sync bytes, not checksum bytes): running 8-bit sums CK_A and CK_B as defined in the u-blox interface description.
ACK is class/id 05 01 with a 2-byte payload naming the acknowledged class/id. NAK is 05 00 with the same shape.
Important: every CFG-MSG ACK carries payload 06 01. You cannot tell which message was acknowledged if you fire several at once. Send one UBX command at a time; wait for its ACK/NAK (or timeout) before the next.
Suggested ACK timeout: 10–15 seconds on BLE; one bounded retry on timeout only.
12. Role configuration: Rover / Base
EX-1 ships with Columbus high-level modes (Mode=ROVER / Mode=BASE in CONFIG.TXT). Over BLE you implement the equivalent with:
UBX-CFG-TMODE3(class0x06, id0x71) — Disabled / Survey-In / FixedUBX-CFG-MSG(06 01) — enable or disable RTCM output message rates- Optionally
UBX-CFG-CFG— permanent save (see next section)
Official Base RTCM set (MSM4)
Enable (do not also enable MSM7):
| RTCM type | Purpose |
|---|---|
| 1005 | Stationary antenna reference |
| 1074 | GPS MSM4 |
| 1084 | GLONASS MSM4 |
| 1094 | Galileo MSM4 |
| 1124 | BeiDou MSM4 |
| 1230 | GLONASS code-phase biases |
Never mix MSM4 (…74) with MSM7 (…77) on the same constellation. Explicitly disable 1077 / 1087 / 1097 / 1127 if the unit may have been configured in u-center with MSM7.
Multi-port CFG-MSG payload layout
Payload after the RTCM class (0xF5) and subtype id: six rate bytes for I2C, UART1, UART2, USB, SPI, and reserved.
Field experience for Base enable rates: I2C=0, UART1=1, UART2=1, USB=1, SPI=0, reserved=0. Rover / disable rates: all zeros.
RTCM subtype ids commonly used with CFG-MSG: 1005→0x05, 1074→0x4A, 1084→0x54, 1094→0x5E, 1124→0x7C, 1230→0xE6; MSM7 disable ids: 1077→0x4D, 1087→0x57, 1097→0x61, 1127→0x7F. Build frames with a correct UBX checksum; verify against u-blox docs or a known-good hex dump on your firmware.
Rover temporary apply (recommended order)
CFG-TMODE3Disabled → wait ACK for06 71- Optionally poll
CFG-TMODE3and verify mode is disabled - Disable MSM7 messages (rate 0) — serial ACK each
- Disable Base RTCM messages 1005/1074/1084/1094/1124/1230 — serial ACK each
- Start NTRIP client and write RTCM uplink
Base temporary apply (recommended order)
- Disable MSM7 (rate 0)
- Disable then enable the official MSM4 Base set (rate profile above)
- Set TMODE3:
- Fixed: lat/lon + ellipsoidal height (not MSL) + fixed accuracy metadata
- Survey-In: minimum duration + accuracy limit; poll
NAV-SVINuntil valid
- Wait until RTCM 1005 appears in the notify stream before treating Base as ready for caster upload
- Only then start your NTRIP server upload of Base RTCM
TMODE3 notes
- Fixed mode uses LLA encoding with high-precision residual fields as defined by u-blox; height must be ellipsoidal meters.
- Survey-In needs minimum duration (seconds) and accuracy limit (encode per Interface Description; apps often store the user value in millimeters and scale for the payload).
- Disabled mode clears TMODE so the unit behaves as a normal Rover receiver.
- Poll
NAV-SVINwhile surveying: duration, mean accuracy,valid, andactiveflags indicate progress.
Two-step UX (strongly recommended)
- Apply temporarily — write TMODE3 + CFG-MSG; verify; do not save yet.
- Save permanently — only after verification, on user confirmation.
Temporary settings can be lost on power cycle. That is intentional for lab testing.
13. Permanent save (BBR / Flash)
Legacy field integrations often save with UBX-CFG-CFG using a non-zero save mask and device mask BBR + Flash (commonly device mask 0x03). On modern F9 HPG protocol versions this behaves as save-all of the current configuration, not a selective TMODE/RTCM-only save.
Warnings you should surface to your users:
- Do not auto-save.
- Do not save before temporary apply verification.
- ACK means the command was accepted — still verify after a power cycle if persistence is a product requirement.
Selective CFG-VALSET persistence is more precise long-term, but the BLE-bridged UART port must be known and some ports (notably UART2 in u-blox docs) restrict configuration writes. Validate on your hardware before shipping.
14. Recommended transaction rules
| Rule | Why |
|---|---|
| One UBX command in flight | CFG-MSG ACKs are indistinguishable |
| Serial CFG-MSG list | Prevents false “all done” |
| Pause RTCM uplink during role transactions | Avoid mixed Base/Rover traffic |
| Pause NAV-PVT polls during role transactions | Same shared notify pipe |
Name gate: EX-1 NTRIP BLE only |
Wrong interface has no reliable RTCM write |
| Wait for 1005 before Base “ready” | Reference not initialized until then |
| No Rover RTCM during Survey-In | Contaminates base solution |
A full Base apply (many CFG-MSG + TMODE3) over BLE commonly takes 10–20 seconds. Show progress (command i of n).
15. Accuracy estimates (optional)
NMEA GST can provide sigma values, but on EX-1 a robust approach is polling UBX-NAV-PVT. When the payload is long enough, horizontal / vertical accuracy estimates are at offsets 40 and 44 (unsigned millimeters). Poll at ~1 Hz only when you need UI accuracy — stop polling while applying role commands.
16. Pitfalls checklist
- Connecting to
EX-1 BLEinstead ofEX-1 NTRIP BLE. - Assuming
FFF0alone means EX-1. - Treating every notify as UTF-8 text.
- Writing RTCM without removing HTTP chunked framing.
- Parallel CFG-MSG writes.
- Mixing MSM4 and MSM7.
- Using MSL height in TMODE3 Fixed (needs ellipsoidal height).
- Declaring Base ready before RTCM 1005 exists.
- Hard-coding ATT payload size to 20 forever.
- Calling permanent save without telling the user it is save-all.
- Expecting Simulator + real BLE hardware to work.
- Changing UART baud from the app “just in case” — do not.
End of guide.
