BLE Communication with SitePoint GPS Devices¶
A comprehensive guide showing how to communicate with SitePoint GPS devices over Bluetooth Low Energy, including RTCM correction data transmission and SQTP protocol handling.
The BLEManager class demonstrates how to develop Bluetooth Low Energy applications for RTK GPS positioning using the Qt Bluetooth API. The implementation covers:
Scanning and connecting to SitePoint GPS devices
Queuing and transmitting RTCM v3 correction data
Receiving GPS position data (LLA) via SQTP protocol
Managing device configuration and mode changes
Battery and device information service integration
Key Qt Classes Used¶
The example demonstrates the use of the following Qt Bluetooth classes:
QBluetoothDeviceDiscoveryAgent - Device discovery
QLowEnergyController - Connection management
QLowEnergyService - Service access
QLowEnergyCharacteristic - Characteristic read/write/notify
QLowEnergyDescriptor - Descriptor configuration
SitePoint Device Architecture¶
SitePoint GPS devices expose a custom BLE service with UUID:
00000100-34ed-12ef-63f4-317792041d17
This service provides two primary characteristics:
UUID |
Purpose |
Properties |
|---|---|---|
|
RTCM transmission |
Write (WriteWithoutResponse) |
|
SQTP protocol |
Read, Write, Notify |
Additionally, the devices support standard BLE services:
Device Information Service (0x180A)
Battery Service (0x180F)
Initializing the BLE Manager¶
The BLEManager constructor sets up the discovery agent, initializes the RTCM transmission queue, and configures the SQTP protocol handlers.
Key initialization steps:
Set global instance pointer for callback access
Configure SQTP frame transmission and parsing callbacks
Initialize pre-allocated RTCM buffer pool (50 messages)
Create transmission timer for FSM pacing
Connect discovery agent signals
Scanning for Devices¶
Device scanning is initiated with startScan(). The method performs several
validation checks before starting discovery.
The scan process:
Validates Bluetooth adapter is available and powered on
Clears any previous discovery results
Starts Low Energy device discovery
Filters results to only SitePoint devices (via onDeviceDiscovered callback)
Each discovered SitePoint device triggers the deviceDiscovered() signal with
the device name and Bluetooth address.
Connecting to a Device¶
Once a device is discovered, connect using its Bluetooth address.
The connection process:
Creates QLowEnergyController for the target device
Connects controller signals (connected, disconnected, errors, etc.)
Initiates connection
Automatically discovers services upon connection
Negotiates MTU (Maximum Transmission Unit)
Configures characteristics for RTCM and SQTP communication
Upon successful connection, the deviceConnected() signal is emitted and
service discovery begins automatically.
Transmitting RTCM Correction Data¶
RTCM v3 correction messages from an NTRIP caster are queued for transmission
using queueRTCMMessage().
The RTCM transmission architecture features:
Pre-allocated buffer pool (50 messages, no heap allocation)
Finite state machine for paced transmission
Automatic fragmentation for messages exceeding MTU
Timer-based pacing matching BLE connection intervals
Prioritization of RTCM over SQTP messages
RTCM Transmission FSM States¶
stateDiagram-v2
[*] --> RTCM_TX_IDLE
RTCM_TX_IDLE --> RTCM_TX_SENDING : Message queued
RTCM_TX_SENDING --> RTCM_TX_WAITING : Fragment sent
RTCM_TX_WAITING --> RTCM_TX_SENDING : Timer expires & more data
RTCM_TX_WAITING --> RTCM_TX_IDLE : Timer expires & queue empty
RTCM_TX_SENDING --> RTCM_TX_IDLE : Transmission complete
The FSM has three states:
RTCM_TX_IDLE - No transmission active, queue empty
RTCM_TX_SENDING - Actively packing and sending messages
RTCM_TX_WAITING - Waiting for next connection interval
State transitions ensure messages are sent at optimal intervals to match the BLE connection timing (typically 7.5ms - 15ms intervals).
Receiving GPS Data¶
GPS position data is received via notifications on the SQTP characteristic (0x0105). The SQTP protocol encapsulates three types of data:
SQTP_ID_SITEPOINT_LLA- Position data (lat/lon/altitude, accuracy)SQTP_ID_SITEPOINT_STATUS- Device status (satellites, corrections, etc.)SQTP_ID_SITEPOINT_LOCAL_BASE_CONFIG- Configuration data
Received data is parsed by the sqtpBleParseSubframe() callback and stored in
global variables for efficient access:
extern SqspLla_t myLla; // GPS position data
extern SqspStatus_t myStatus; // Device status
extern SqspLbConfig_t myConfig; // Configuration
Change detection ensures signals (llaDataChanged(), statusDataChanged(),
configDataChanged()) are only emitted when data actually changes, not on
every 8 Hz update from the device.
Device Mode Changes¶
The GPS device can operate in several modes:
DEVICE_MODE_ROVER- Standard rover (no corrections)DEVICE_MODE_ROVER_NTRIP- Rover with NTRIP corrections via BLEDEVICE_MODE_ROVER_NEAREST- Rover with Tripod network correctionsDEVICE_MODE_FIXED_BASE- Fixed base stationDEVICE_MODE_FIXED_BASE_BROADCAST- Fixed base with broadcast
Mode changes are sent using sendModeChangeCommand():
bleManager->sendModeChangeCommand(DEVICE_MODE_ROVER_NTRIP);
MTU Negotiation and Fragmentation¶
Upon connection, the BLE stack negotiates an MTU (Maximum Transmission Unit). The usable payload size is calculated with platform-specific limits in mind.
MTU Calculation¶
BLEManager uses a single-source-of-truth design for payload limits via the
m_maxPayload member variable, calculated when MTU changes:
void BLEManager::onMtuChanged(int mtu) {
m_currentMtu = mtu;
// Platform-aware calculation (Android: 512 byte cap, Others: MTU-3)
m_maxPayload = qMin(mtu - 3, MAX_BLE_GATT_ATTR_LEN);
}
Platform-Specific Behavior¶
Different platforms enforce BLE GATT attribute limits differently:
Platform |
MTU Negotiated |
Max Payload |
Notes |
|---|---|---|---|
Android |
517 bytes |
512 bytes |
Strict BLE GATT spec enforcement (crashes if exceeded) |
Windows |
517 bytes |
514 bytes (MTU-3) |
Lenient enforcement, allows full MTU-3 |
iOS/macOS |
517 bytes |
514 bytes (MTU-3) |
Version-dependent, generally lenient |
Automatic Fragmentation¶
RTCM messages exceeding m_maxPayload are automatically fragmented across
multiple BLE packets using sentOffset tracking:
Large messages split transparently across multiple transmission timer ticks
Example: 514-byte message on Android → 512 bytes (tick 1) + 2 bytes (tick 2)
No reassembly needed at device (RTCM v3 is byte-stream protocol)
Fragmentation counter tracked in
m_messagesFragmented
Benefits¶
Consistency: Same payload limit used for validation, packing, and transmission
Safety: Android 512-byte limit prevents crashes
Efficiency: Windows/iOS utilize full MTU-3 when possible
Simplicity: Single calculation point, no redundant logic
The getCurrentMtu() method provides the current negotiated MTU value.
Complete Usage Example¶
// Create BLE manager
BLEManager *bleManager = new BLEManager(this);
// Connect signals
connect(bleManager, &BLEManager::deviceDiscovered,
[](const QString &name, const QString &address) {
qDebug() << "Found device:" << name << "at" << address;
});
connect(bleManager, &BLEManager::deviceConnected, []() {
qDebug() << "Device connected successfully";
});
connect(bleManager, &BLEManager::llaDataChanged, [bleManager]() {
SqspLla_t lla = bleManager->getLlaData();
qDebug() << "Position:" << lla.latitude << "," << lla.longitude;
});
// Start scanning
bleManager->startScan();
// Later, connect to discovered device
bleManager->connectToDevice("12:34:56:78:9A:BC");
// Queue RTCM correction message (from NTRIP caster)
QByteArray rtcmMessage = /* received from NTRIP */;
bleManager->queueRTCMMessage(rtcmMessage);
// Change device mode
bleManager->sendModeChangeCommand(DEVICE_MODE_ROVER_NTRIP);
Performance Considerations¶
High-Frequency Data Access¶
GPS data arrives at 8-10 Hz. For optimal performance when accessing position data frequently (e.g., map updates), use direct extern access instead of function calls:
extern SqspLla_t myLla; // Declared in BLEManager.cpp
// Direct access (fast, no function call overhead)
double lat = myLla.latitude;
double lon = myLla.longitude;
RTCM Queue Sizing¶
The RTCM queue is pre-allocated with 50 message slots. For typical NTRIP correction streams (MSM4/MSM7 messages at 1 Hz), this provides ample buffering. If messages are dropped (check statistics), consider reducing the correction stream data rate or message types.
No Heap Allocation During Transmission¶
The RTCM transmission system is designed for real-time performance:
Buffer pool pre-allocated at construction
Fixed-size send buffer (2048 bytes)
No dynamic allocation during message transmission
API Reference¶
For detailed API documentation, see:
-
class BLEManager : public QObject
Manages Bluetooth Low Energy communication with SitePoint GPS devices.
The BLEManager class provides comprehensive BLE functionality for communicating with SitePoint GPS devices. It handles:
Device discovery and connection management
RTCM v3 correction data transmission with queuing and FSM
SQTP (SitePoint Transport Protocol) message handling
SQSP (SitePoint Protocol) data reception (LLA, Status, Config)
Battery and Device Information Service support
MTU negotiation and automatic fragmentation
Usage¶
Basic workflow:
Create BLEManager instance
Call startScan() to discover nearby SitePoint devices
Connect to desired device using connectToDevice()
Queue RTCM messages using queueRTCMMessage()
Monitor GPS data via llaDataChanged(), statusDataChanged(), configDataChanged() signals
Characteristics¶
The BLEManager works with two primary BLE characteristics on the SitePoint service:
0x0102 (RTCM): Write-only characteristic for sending RTK correction data
0x0105 (SQTP): Read/Write/Notify characteristic for bidirectional protocol communication
RTCM Transmission Architecture¶
RTCM messages are queued and transmitted via a finite state machine (FSM) that:
Uses a pre-allocated 50-message buffer pool (no heap allocation)
Paces transmission to match BLE connection intervals
Automatically fragments messages exceeding MTU limits
Prioritizes RTCM over SQTP (queues SQTP when RTCM active)
Global Data Structures¶
The following global variables (extern) provide fast access to device data:
myLla: GPS position data (latitude, longitude, altitude, accuracy)
myStatus: Device status (GPS state, satellites, corrections, aidingBins)
myConfig: Device configuration (base station position, mode settings)
These are updated automatically when notifications arrive on the 0x0105 characteristic. Change detection is implemented to emit signals only when data actually changes.
See also
QBluetoothDeviceDiscoveryAgent, QLowEnergyController, QLowEnergyService
Note
The RTCM characteristic uses WriteWithoutResponse for maximum throughput. Messages are sent and forgotten - retransmission is not supported as stale corrections are useless for RTK positioning.
Private Helper Methods
Reads all characteristics of a service (legacy, unused)
RTCM Queue Management Methods
Initializes the RTCM buffer pool
Public Functions
-
explicit BLEManager(QObject *parent = nullptr)
Constructs a BLEManager instance.
[blemanager-constructor]
Initializes the BLE discovery agent, RTCM buffer pool, transmission timer, and SQTP protocol handlers. The manager is ready to scan for devices after construction.
- Parameters:
parent – Parent QObject for memory management
- Q_INVOKABLE void startScan ()
Starts scanning for nearby BLE devices.
[blemanager-constructor]
Initiates Bluetooth Low Energy device discovery using the Low Energy method. Only devices advertising the SitePoint service UUID will be reported via the deviceDiscovered() signal.
[blemanager-startscan]
See also
Note
Requires Bluetooth to be enabled on the host system. Will emit errorOccurred() if Bluetooth is unavailable or powered off.
- Q_INVOKABLE void stopScan ()
Stops the ongoing BLE device scan.
[blemanager-startscan]
Halts the device discovery process if currently active. Safe to call even if no scan is in progress.
See also
- Q_INVOKABLE void connectToDevice (const QString &deviceAddress)
Connects to a discovered SitePoint device.
[blemanager-connect]
Establishes a BLE connection to the specified device and initiates service discovery. Upon successful connection:
deviceConnected() signal is emitted
Services are discovered automatically
RTCM and SQTP characteristics are configured
MTU is negotiated
Notifications are enabled on the SQTP characteristic
Note
The device must have been discovered via startScan() first.
- Parameters:
deviceAddress – Bluetooth address of the device (from deviceDiscovered signal)
- Q_INVOKABLE void disconnectFromDevice ()
Disconnects from the currently connected device.
Terminates the BLE connection, clears all queues, stops transmission timer, and resets all device data (myLla, myStatus, myConfig) to zero. Emits deviceDisconnected() and sqtpDisconnected() signals.
See also
- Q_INVOKABLE void readCharacteristic (const QString &characteristicUuid)
Legacy characteristic read function (deprecated)
[blemanager-connect]
- Deprecated:
This function is no longer used. Characteristic reading is now handled automatically during service discovery.
- Parameters:
characteristicUuid – UUID of characteristic to read (unused)
- Q_INVOKABLE SqspLla_t getLlaData ()
Returns the current GPS position data.
Provides direct access to the global myLla structure. Data is updated automatically when notifications arrive from the device.
See also
Note
For high-frequency access (8-10 Hz), consider using extern myLla directly to avoid function call overhead.
- Returns:
SqspLla_t structure containing latitude, longitude, altitude, and accuracy
-
bool sendRTCMData(const QByteArray &data)
Sends RTCM data immediately (low-level method)
Low-level method that writes RTCM data directly to the 0x0102 characteristic using WriteWithoutResponse. Does not use the queue or FSM.
See also
Note
Message must fit within MTU-3 bytes or transmission will fail.
Warning
Most applications should use queueRTCMMessage() instead, which provides queuing, pacing, and fragmentation support.
- Parameters:
data – RTCM message bytes to send
- Returns:
true if sent successfully, false on error
-
bool queueRTCMMessage(const QByteArray &data)
Queues an RTCM message for transmission (recommended method)
[blemanager-queuertcm]
Adds an RTCM message to the transmission queue. The FSM will automatically transmit messages at appropriate intervals matching the BLE connection timing. Messages larger than MTU will be automatically fragmented.
Queue capacity: 50 messages (pre-allocated, no heap allocation)
See also
Note
This is the recommended method for RTCM transmission. The FSM handles:
Proper pacing to match BLE connection intervals
Automatic fragmentation for large messages
Buffer management and overflow protection
- Parameters:
data – RTCM message bytes to queue
- Returns:
true if queued successfully, false if queue full or device not ready
-
SqtpStatus_t sendSQTPMessage(const QByteArray &data)
Sends an SQTP message on the 0x0105 characteristic.
Sends an SQTP protocol message to the device. If RTCM transmission is active, the message is queued in a single-slot pending buffer and sent when RTCM idle.
See also
Note
Used internally by SQTP protocol handlers. Applications typically don’t call this directly - use sendModeChangeCommand() or sendConfigData() instead.
- Parameters:
data – SQTP frame bytes to send
- Returns:
SQTP_STATUS_SUCCESS on success, error code on failure
-
int getCurrentMtu() const
Returns the current negotiated MTU value.
The MTU (Maximum Transmission Unit) determines the maximum packet size. Usable payload is MTU-3 bytes (3 bytes for BLE ATT header).
Note
Not all platforms expose MTU (e.g., Linux returns -1). Default MTU of 23 bytes is assumed if platform doesn’t report.
- Returns:
Current MTU in bytes, or 0 if not connected/unknown
-
SqtpStatus_t sendModeChangeCommand(DeviceMode mode)
Sends a device mode change command.
Changes the operating mode of the GPS device by sending an SQTP configuration message with the appropriate mode bits set.
Supported modes:
DEVICE_MODE_ROVER: Standard rover mode (no corrections)
DEVICE_MODE_ROVER_NTRIP: Rover with NTRIP corrections via BLE
DEVICE_MODE_ROVER_NEAREST: Rover with nearest base corrections (Tripod network)
DEVICE_MODE_FIXED_BASE: Fixed base station mode
DEVICE_MODE_FIXED_BASE_BROADCAST: Fixed base with RTK broadcast
See also
DeviceMode, sendConfigData()
- Parameters:
mode – Desired device mode (ROVER, ROVER_NTRIP, ROVER_NEAREST, FIXED_BASE, etc.)
- Returns:
SQTP_STATUS_SUCCESS on success, error code on failure
- Q_INVOKABLE bool sendConfigData (double longitude, double latitude, double height, double hMSL, double hAcc, double vAcc, int duration)
Sends custom configuration data to the device.
Sends a complete configuration message to the device including base station position, accuracy estimates, and survey duration. Uses the current mode settings from myConfig.mode.
See also
Note
Typically used in Fixed Base mode to set the known base station position.
- Parameters:
longitude – Base station longitude in degrees
latitude – Base station latitude in degrees
height – Base station ellipsoidal height in meters
hMSL – Base station height above mean sea level in meters
hAcc – Horizontal accuracy in meters
vAcc – Vertical accuracy in meters
duration – Survey duration in seconds
- Returns:
true on success, false on failure
- Q_INVOKABLE int getBatteryLevel () const
Returns the current battery level.
Battery level is read from the Battery Service characteristic 0x2A19. Updates automatically via notifications if the device supports them.
- Returns:
Battery percentage (0-100), or -1 if unknown
- Q_INVOKABLE QString getBatteryPowerState () const
Returns the battery power state as a formatted string.
Provides detailed information about battery presence, wired/wireless power connection, and charging state. Read from Battery Power State characteristic 0x2A1A.
See also
- Returns:
Human-readable power state (e.g., “Battery: Present | Wired: Connected | Charge: Charging”)
- Q_INVOKABLE QString getBatteryLevelState () const
Returns the battery level state.
Categorical battery level indication from Battery Level State characteristic 0x2A1B.
See also
- Returns:
“Unknown”, “Critical”, “Low”, or “Good”
Public Members
-
bool m_sqtpActive = false
True when SQTP protocol is actively connected.
Signals
-
void deviceDiscovered(const QString &deviceName, const QString &deviceAddress)
Emitted when a SitePoint device is discovered during scanning.
This signal is emitted for each SitePoint device found during BLE scanning. Only devices advertising the SitePoint service UUID (00000100-34ed-12ef-63f4-317792041d17) are reported.
See also
Note
May be emitted multiple times for the same device as more details are discovered.
- Parameters:
deviceName – Advertised name of the device
deviceAddress – Bluetooth address of the device (use with connectToDevice())
-
void deviceConnected()
Emitted when BLE connection to device is established.
Indicates successful connection. Service discovery begins automatically after this signal is emitted.
See also
-
void deviceDisconnected()
Emitted when BLE connection to device is lost.
Emitted when connection is lost (either intentionally via disconnectFromDevice() or due to connection failure). All queues are cleared and device data is reset.
See also
-
void errorOccurred(const QString &errorString)
Emitted when a BLE error occurs.
Reports BLE-related errors such as Bluetooth unavailable, connection failure, or characteristic access errors.
- Parameters:
errorString – Human-readable description of the error
-
void inputReceived(const QString &input)
Legacy signal (unused)
- Deprecated:
This signal is not currently used in the implementation.
- Parameters:
input – Input data (unused)
-
void serviceDiscovered(const QString &serviceUuid)
Emitted when a BLE service is discovered on the device.
Emitted during service discovery for each service found on the device.
- Parameters:
serviceUuid – UUID of the discovered service
-
void characteristicDiscovered(const QString &characteristicUuid)
Emitted when a characteristic is discovered.
- Deprecated:
This signal is not currently used in the implementation.
- Parameters:
characteristicUuid – UUID of the discovered characteristic
-
void characteristicValueRead(const QString &characteristicUuid, const QByteArray &value)
Emitted when a characteristic value is read.
- Deprecated:
This signal is not currently used in the implementation.
- Parameters:
characteristicUuid – UUID of the characteristic
value – Raw bytes read from the characteristic
-
void llaDataChanged()
Emitted when GPS position data (LLA) changes.
Indicates that the global myLla structure has been updated with new position data from the device. Change detection ensures this signal is only emitted when data actually changes (not on every 8 Hz update).
Access updated data via:
getLlaData() method
Direct extern access to myLla global variable
See also
-
void statusDataChanged()
Emitted when device status data changes.
Indicates that the global myStatus structure has been updated with new device status information (GPS state, satellites, corrections, aidingBins).
Access updated data via direct extern access to myStatus global variable.
See also
-
void configDataChanged()
Emitted when device configuration data changes.
Indicates that the global myConfig structure has been updated with new configuration information (mode settings, base position, etc.).
Access updated data via direct extern access to myConfig global variable.
See also
-
void mtuChanged(int newMtu)
Emitted when MTU value changes.
Emitted when the BLE MTU (Maximum Transmission Unit) is negotiated or changes. Usable payload size is newMtu - 3 bytes.
See also
- Parameters:
newMtu – New MTU value in bytes
-
void batteryLevelChanged(int level)
Emitted when battery level changes.
Emitted when battery level is read or updated via notification.
See also
- Parameters:
level – New battery level (0-100 percent)
-
void sqtpConnected()
Emitted when SQTP protocol connection is established.
Indicates that the first valid SQTP subframe has been received from the device. After this signal, the device is ready for full bidirectional communication.
See also
-
void sqtpDisconnected()
Emitted when SQTP protocol connection is lost.
Indicates SQTP connection has been lost (either due to user disconnect, BLE connection loss, or device reporting disconnected state).
See also
Public Static Functions
-
static BLEManager *getInstance()
Returns the global BLEManager instance.
Provides access to the BLEManager from C callbacks that don’t have direct object access (e.g., SQTP protocol handlers).
Note
Only one BLEManager instance should exist per application.
- Returns:
Pointer to the singleton BLEManager instance