Theory of Operation¶
This chapter explains the underlying theory and operation of the qt-test application and SitePoint GPS positioning system.
RTK GPS Positioning¶
Real-Time Kinematic (RTK) positioning achieves centimeter-level accuracy by using correction data from a reference station.
Standard GPS vs RTK:
Standard GPS: 2-10 meter accuracy
RTK GPS: 1-5 centimeter accuracy
How RTK Works:
Base station (or network) measures GPS errors at known location
Corrections transmitted via NTRIP/RTCM to rover
Rover applies corrections to achieve high accuracy
Correction age must be < 30 seconds for optimal performance
Data Flow Architecture¶
The application implements a multi-layer data flow:
graph TB
subgraph corrections ["RTCM Correction Path"]
NTRIP[NTRIP Caster]
NM[NTRIPManager]
BLE1[BLEManager]
GPS1[GPS Device]
NTRIP -->|"RTCM v3<br/>(1 Hz)"| NM
NM -->|"Queue RTCM"| BLE1
BLE1 -->|"BLE 0x0102"| GPS1
end
subgraph data ["GPS Data Path"]
GPS2[SitePoint GPS Device]
BLE2[BLEManager]
DM[DataManager]
QML[QML UI]
GPS2 -->|"SQSP/SQTP<br/>(8-10 Hz)"| BLE2
BLE2 -->|"Parse SQTP"| DM
DM -->|"Format for UI"| QML
end
GPS1 -.->|"Same Device"| GPS2
style NTRIP fill:#e1f5ff
style GPS2 fill:#e1f5ff
style NM fill:#fff4e1
style BLE1 fill:#fff4e1
style BLE2 fill:#fff4e1
style DM fill:#fff4e1
style GPS1 fill:#e1ffe1
style QML fill:#e1ffe1
Key Points:
NTRIP corrections queued in RTCM buffer (50 message capacity)
BLE FSM paces transmission to match connection interval
GPS data (8-10 Hz) uses change detection to prevent UI thrashing
DataManager formats raw data for QML display
BLE Communication Model¶
Bluetooth Low Energy communication follows a client-server model:
Device Roles:
Peripheral (Server): SitePoint GPS device
Central (Client): qt-test application
Communication Patterns:
Write Without Response (0x0102): RTCM data to device
Notify (0x0105): GPS data from device
Request/Response (0x0105): Commands and configuration
Connection Parameters:
Connection interval: 7.5-15 ms (negotiated)
MTU: 23-527 bytes (negotiated, typical 244)
Supervision timeout: 6 seconds
Slave latency: 0 (no skipped events)
See BLE Communication with SitePoint GPS Devices for complete BLE implementation details.
SQTP Protocol Details¶
SQTP provides reliable framing over BLE’s unreliable write operations.
Frame Assembly:
Application calls
sqtpManagerAddDirect()with subframe ID and dataSQTP library packs header, subframe ID, length, payload
CRC-16 calculated and appended
Frame passed to
sqtpBleTransmitFrame()callbackBLEManager writes frame to characteristic 0x0105
Frame Parsing:
BLE notification received on characteristic 0x0105
Data passed to
sqtpBleParseSubframe()callbackSQTP library validates CRC, extracts subframe ID
Payload routed based on subframe ID
Parsed data stored in global structures
Correction Data Flow¶
RTCM correction data follows a carefully managed path:
Queue Management:
Pre-allocated pool: 50 × 2048 byte buffers
FIFO queuing with overflow detection
Oldest message dropped if queue full
Transmission FSM:
IDLE: Queue empty, no activity
SENDING: Packing and transmitting fragments
WAITING: Pacing delay between transmissions
Fragmentation:
Messages split into MTU-sized chunks
Fragment count =
ceil(messageSize / (MTU - 3))All fragments must be sent before returning to IDLE
Change Detection Optimization¶
GPS data arrives at 8-10 Hz but may not change every update. Change detection prevents unnecessary UI updates:
// In BLEManager::sqtpBleParseSubframe()
static SqspLla_t previousLla;
if (memcmp(&myLla, &previousLla, sizeof(SqspLla_t)) != 0) {
emit llaDataChanged(); // Only emit if data changed
previousLla = myLla;
}
Benefits:
Reduces UI update load by ~80%
Prevents TextField editing interruptions
Maintains responsive UI at high data rates
Device Modes¶
SitePoint devices support multiple operational modes:
Mode |
Description |
|---|---|
DEVICE_MODE_ROVER |
Standard rover, no corrections, ~2m accuracy |
DEVICE_MODE_ROVER_NTRIP |
Rover with NTRIP corrections via BLE, ~2cm accuracy |
DEVICE_MODE_ROVER_NEAREST |
Rover with Tripod network corrections, ~2cm accuracy |
DEVICE_MODE_FIXED_BASE |
Fixed base station, broadcasts corrections |
DEVICE_MODE_FIXED_BASE_BROADCAST |
Fixed base with wider broadcast range |
Mode Selection Considerations:
Use ROVER_NTRIP when you have internet and NTRIP account
Use ROVER_NEAREST for Tripod network coverage
Use FIXED_BASE to provide corrections to other rovers
Survey duration affects base station accuracy (longer = better)
For more details, see Device Configuration.
Real-Time Performance Architecture¶
The application is architected for real-time GPS data processing at 8-10 Hz update rates with minimal latency. This section explains the performance optimizations and design decisions.
The 8-10 Hz Challenge¶
GPS data arrives from the BLE device at 8-10 Hz (every 100-125 ms). At this rate:
120-150 ms budget per update cycle
UI must remain responsive (60 FPS = 16ms per frame)
Data processing must not block UI rendering
Property bindings must not create excessive overhead
Traditional Qt approach would use signals/slots extensively, but this creates overhead:
Operation |
Latency |
Annual CPU Time (8 Hz) |
|---|---|---|
Qt signal emission |
~500ns |
126 million signals/year |
Qt slot execution |
~500ns |
126 million calls/year |
Property notification |
~1000ns |
252 million notifications/year |
With 15+ properties per update, this would be 1.89 billion signal emissions per year of continuous operation.
Global Extern Variables¶
The application uses global variables for GPS data to eliminate signal routing overhead:
// Declared in BLEManager.cpp
SqspLla_t myLla;
SqspStatus_t myStatus;
SqspLbConfig_t myConfig;
// Accessed via extern in other managers
extern SqspLla_t myLla;
extern SqspStatus_t myStatus;
extern SqspLbConfig_t myConfig;
Data Flow with Extern:
graph TB
GPS["GPS Device<br/>(8-10 Hz)"]
BLE["BLEManager::sqtpBleParseSubframe()"]
WRITE["myLla.latitude = newValue<br/><i>~5ns, single memory write</i>"]
CHECK["if (memcmp(&myLla, &previousLla, ...))<br/>emit llaDataChanged()"]
MAP["MapManager::onLlaDataChanged()"]
READ["return myLla.lat<br/><i>~5ns, single memory read</i>"]
GPS -->|"BLE Notify"| BLE
BLE -->|"Direct write"| WRITE
WRITE -->|"Change detection"| CHECK
CHECK -->|"Signal emission<br/>(only if changed)"| MAP
MAP -->|"Direct read"| READ
style GPS fill:#e1f5ff
style BLE fill:#fff4e1
style WRITE fill:#ffe1e1
style CHECK fill:#ffe1f5
style MAP fill:#fff4e1
style READ fill:#e1ffe1
Performance Benefits:
No function call overhead: Direct memory access
No copy operations: Managers read from shared memory
Single source of truth: All managers see consistent data
Cache-friendly: Data co-located in memory
When Extern Is Used:
MapManager: Always uses extern for maximum speed
DataManager: Uses extern with formatting and caching
BLEManager: Owns the globals, provides getter functions for encapsulation
Batched Properties Pattern¶
DataManager uses batched properties to reduce Qt signal overhead:
Traditional Approach (high overhead):
// 15 separate properties = 15 signals per update
Q_PROPERTY(QString latitude ...)
Q_PROPERTY(QString longitude ...)
Q_PROPERTY(QString height ...)
Q_PROPERTY(QString hAcc ...)
// ... 11 more properties ...
void onUpdate() {
emit latitudeChanged(); // 500ns
emit longitudeChanged(); // 500ns
emit heightChanged(); // 500ns
// ... 12 more emissions = ~7500ns overhead
}
Batched Approach (low overhead):
// Single property with map = 1 signal per update
Q_PROPERTY(QVariantMap llaData ...)
void onUpdate() {
// Process all fields once
m_llaData["latitude"] = format(myLla.lat);
m_llaData["longitude"] = format(myLla.lon);
// ... all fields ...
emit dataChanged(); // Single 500ns emission
}
Performance Comparison at 8 Hz:
Approach |
Signals/Update |
Signals/Second |
Annual Emissions |
|---|---|---|---|
Traditional (15 props) |
15 |
120 |
3.78 billion |
Batched (1 prop) |
1 |
8 |
252 million |
Reduction |
93% |
93% |
93% |
Minimalist MapManager¶
MapManager is the ultimate performance optimization - zero processing:
// No member variables (except counter)
// No caching
// No formatting
// Just direct passthrough
double MapManager::lat() const {
return myLla.lat; // Single memory read, ~5ns
}
void MapManager::onLlaDataChanged() {
m_centerUpdateCount++; // Increment counter
emit locationChanged(); // Signal QML to re-read
}
Why This Works:
The QML Map component re-reads coordinates at 60 FPS (every 16ms) for rendering, but GPS only updates at 8-10 Hz (every 100-125ms). The property binding is evaluated 60 times/second, but:
Each coordinate read: ~5ns (direct memory access)
Per frame overhead: 2 coordinates × 5ns = 10ns per frame
Map rendering time: ~16ms per frame = 16,000,000ns per frame
Overhead percentage: 10ns / 16,000,000ns = 0.00006% of frame time
Change Detection Optimization¶
GPS data arrives at 8-10 Hz, but values may not change every update. Change detection prevents unnecessary signal emissions:
void BLEManager::sqtpBleParseSubframe(...) {
static SqspLla_t previousLla;
// Parse new data into myLla global
myLla.latitude = newLatitude;
// ... update all fields ...
// Only emit signal if data actually changed
if (memcmp(&myLla, &previousLla, sizeof(SqspLla_t)) != 0) {
emit llaDataChanged(); // Typical: 1-2 Hz (not 8-10 Hz)
previousLla = myLla;
}
}
Measured Impact:
GPS updates: 8-10 Hz (800-1000 ms intervals)
Actual changes: 1-2 Hz (position changes every 500-1000ms when stationary)
Signal reduction: 80-90% when device is stationary
Prevents: TextField editing interruptions in QML UI
C++ vs QML Split Rationale¶
Why C++ for Data Processing:
Performance: Native code vs JavaScript VM
Type safety: Compile-time checking vs runtime errors
BLE/Network APIs: Qt Bluetooth and QTcpSocket are C++ APIs
Memory control: Direct memory access, no GC pauses
Why QML for UI:
Declarative: UI structure obvious from code
Animations: Built-in animation framework
Responsive: Automatic property binding
Rapid iteration: No recompilation for UI tweaks
Data Bridge:
graph TB
subgraph cpp ["C++ Layer (Real-Time)"]
GPS["GPS Device<br/>(8-10 Hz)"]
BLE["BLEManager"]
EXT["extern myLla"]
MAPM["MapManager"]
SIG["emit locationChanged()"]
GPS --> BLE
BLE --> EXT
EXT --> MAPM
MAPM --> SIG
end
subgraph qml ["QML Layer (UI)"]
MAP["Map Display<br/>(60 FPS)"]
PROP["Property binding"]
READ["MapManager.lat"]
RENDER["UI renders"]
RENDER --> PROP
PROP --> READ
READ --> MAP
end
SIG -.->|"Signal triggers<br/>QML re-read"| RENDER
style GPS fill:#e1f5ff
style BLE fill:#fff4e1
style EXT fill:#ffe1e1
style MAPM fill:#fff4e1
style SIG fill:#ffe1f5
style MAP fill:#e1ffe1
style READ fill:#e1ffe1
style PROP fill:#e1ffe1
style RENDER fill:#e1ffe1
Performance Result:
GPS processing: < 1μs per update (C++)
UI rendering: ~16ms per frame (QML, GPU accelerated)
Decoupled: GPS processing never blocks UI
Smooth: 60 FPS maintained even at 10 Hz GPS updates
Benchmark Summary¶
Measured performance on typical desktop hardware (Intel Core i5):
Operation |
Latency |
Throughput |
|---|---|---|
BLE receive + parse |
~50μs |
20,000 updates/sec |
SQTP frame parsing |
~20μs |
50,000 frames/sec |
Extern data access |
~5ns |
200 million reads/sec |
Batched property update |
~10μs |
100,000 updates/sec |
Qt signal emission |
~500ns |
2 million signals/sec |
QML Map rendering |
~16ms |
60 FPS |
Bottleneck Analysis:
GPS device: 100-125ms between updates (8-10 Hz) - System limit
BLE communication: ~50μs processing - 0.05% of budget
Data access: ~5ns - 0.000005% of budget
Signal routing: ~500ns - 0.0005% of budget
Map rendering: ~16ms per frame - UI thread, not data path
Conclusion: Application performance is entirely GPS-limited. All software optimizations ensure we never become the bottleneck.