Columbus EX-1 — iOS BLE Developer Guide

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

  1. What you can build
  2. Architecture overview
  3. Critical: which BLE name to use
  4. GATT profile
  5. iOS project setup
  6. Minimal path: scan → connect → NMEA
  7. Implementing the BLE client
  8. Parsing the notify stream
  9. NMEA position parsing
  10. RTK Rover: write RTCM3 corrections
  11. UBX command framing
  12. Role configuration: Rover / Base
  13. Permanent save (BBR / Flash)
  14. Recommended transaction rules
  15. Accuracy estimates (optional)
  16. 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 (preamble D3) 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:

  1. Name prefix EX-1 NTRIP BLE
  2. Then discover service FFF0 and characteristic FFF1

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:

  1. Scan peripherals (do not filter by service UUID alone).
  2. Keep only names with prefix EX-1 NTRIP BLE.
  3. Connect; discover service FFF0, then characteristic FFF1.
  4. Verify notify or indicate is available; enable notifications.
  5. Start buffering and parsing notification data.
  6. Use a connection timeout around 20 seconds.

5. iOS project setup

Info.plist

  • Set NSBluetoothAlwaysUsageDescription to a clear user-facing reason (connect to a Columbus EX-1 GNSS receiver).
  • Optionally add UIBackgroundModesbluetooth-central if 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.

  1. Power on EX-1 with clear sky view.
  2. Scan and select EX-1 NTRIP BLE.
  3. Subscribe to FFF1 notifications.
  4. Buffer bytes into lines ending with CR/LF.
  5. Parse GGA / RMC sentences (talker may be GN, GP, GL, etc.).
  6. 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:

  1. Creates CBCentralManager and waits for .poweredOn.
  2. Scans without relying on service UUID filters for discovery (name filter is primary).
  3. Connects the chosen peripheral and discovers FFF0 / FFF1.
  4. Enables notify on FFF1 and forwards each update to your parsers.
  5. 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

  1. 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 $.
  2. 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

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:

  1. Connect your app to an NTRIP caster (TCP) as a client.
  2. Decode the HTTP response body to a pure RTCM3 byte stream (if the caster uses HTTP chunked encoding, strip chunk framing first).
  3. 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:

  1. UBX-CFG-TMODE3 (class 0x06, id 0x71) — Disabled / Survey-In / Fixed
  2. UBX-CFG-MSG (06 01) — enable or disable RTCM output message rates
  3. 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.

  1. CFG-TMODE3 Disabled → wait ACK for 06 71
  2. Optionally poll CFG-TMODE3 and verify mode is disabled
  3. Disable MSM7 messages (rate 0) — serial ACK each
  4. Disable Base RTCM messages 1005/1074/1084/1094/1124/1230 — serial ACK each
  5. Start NTRIP client and write RTCM uplink
  1. Disable MSM7 (rate 0)
  2. Disable then enable the official MSM4 Base set (rate profile above)
  3. Set TMODE3:
    • Fixed: lat/lon + ellipsoidal height (not MSL) + fixed accuracy metadata
    • Survey-In: minimum duration + accuracy limit; poll NAV-SVIN until valid
  4. Wait until RTCM 1005 appears in the notify stream before treating Base as ready for caster upload
  5. 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-SVIN while surveying: duration, mean accuracy, valid, and active flags indicate progress.
  1. Apply temporarily — write TMODE3 + CFG-MSG; verify; do not save yet.
  2. 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.


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

  1. Connecting to EX-1 BLE instead of EX-1 NTRIP BLE.
  2. Assuming FFF0 alone means EX-1.
  3. Treating every notify as UTF-8 text.
  4. Writing RTCM without removing HTTP chunked framing.
  5. Parallel CFG-MSG writes.
  6. Mixing MSM4 and MSM7.
  7. Using MSL height in TMODE3 Fixed (needs ellipsoidal height).
  8. Declaring Base ready before RTCM 1005 exists.
  9. Hard-coding ATT payload size to 20 forever.
  10. Calling permanent save without telling the user it is save-all.
  11. Expecting Simulator + real BLE hardware to work.
  12. Changing UART baud from the app “just in case” — do not.

End of guide.

Leave a Reply