16 KiB
Custom NMEA Sentences Reference
Overview
This document describes the custom NMEA-format sentences (GNPOS and GNDEV) output by the H11hw GNSS RTK device. These proprietary sentences provide comprehensive positioning data and device information in a standardized NMEA format.
NMEA Format Structure:
$<sentence>*<checksum>\r\n
$- Start delimiter<sentence>- Sentence identifier and data fields (comma-separated)*- Checksum delimiter<checksum>- Two-digit hexadecimal XOR checksum\r\n- End delimiter (CR+LF)
1. GNPOS - Position Data Sentence
Description
The GNPOS sentence provides comprehensive real-time positioning information including coordinates, accuracy metrics, satellite status, and system information.
Output Frequency
- Default: Every 800ms (1.25 Hz)
- Configurable: Via
AT+BT_OUT=SETcommand
Format
$GNPOS,<lat>,<lon>,<alt>,<altCorr>,<status>,<hdop>,<hrms>,<vrms>,<satUsed>,<satView>,<speed>,<heading>,<battV>,<battPct>,<ntripFlag>,<rtcmSize>,<age>,<timestamp>,<tiltAngle>*<checksum>\r\n
Field Definitions
| Field | Index | Type | Unit | Description | Example |
|---|---|---|---|---|---|
| Sentence ID | 0 | String | - | Fixed identifier | GNPOS |
| lat | 1 | Float | degrees | Latitude (signed, 9 decimals) | 31.140518542 |
| lon | 2 | Float | degrees | Longitude (signed, 9 decimals) | 121.284018564 |
| alt | 3 | Float | meters | Altitude above sea level (3 decimals) | 45.123 |
| altCorr | 4 | Float | meters | Corrected altitude (3 decimals) | 45.456 |
| status | 5 | Integer | - | Positioning status (see table below) | 4 |
| hdop | 6 | Float | - | Horizontal Dilution of Precision (2 decimals) | 0.85 |
| hrms | 7 | Float | meters | Horizontal RMS error (3 decimals) | 0.012 |
| vrms | 8 | Float | meters | Vertical RMS error (3 decimals) | 0.018 |
| satUsed | 9 | Integer | - | Number of satellites used in solution | 18 |
| satView | 10 | Integer | - | Number of satellites visible | 24 |
| speed | 11 | Float | km/h | Ground speed (3 decimals) | 12.345 |
| heading | 12 | Float | degrees | Heading/course over ground (2 decimals) | 135.67 |
| battV | 13 | Float | volts | Battery voltage (2 decimals) | 4.15 |
| battPct | 14 | Integer | % | Battery percentage (0-100) | 85 |
| ntripFlag | 15 | Integer | - | NTRIP connection status (0=disconnected, 1=connected) | 1 |
| rtcmSize | 16 | Integer | bytes | RTCM data size received | 1024 |
| age | 17 | Float | seconds | Differential correction age (1 decimal) | 1.2 |
| timestamp | 18 | Integer | seconds | System timestamp (Unix time) | 1714953600 |
| tiltAngle | 19 | Float | degrees | Device tilt angle (1 decimal) | 2.5 |
Positioning Status Values
| Value | Status | Description |
|---|---|---|
| 0 | No Fix | No valid position |
| 1 | Single Point | Autonomous GPS/GNSS positioning |
| 2 | DGPS | Differential GPS (SBAS corrected) |
| 4 | RTK Fixed | RTK fixed solution (cm-level accuracy) |
| 5 | RTK Float | RTK float solution (dm-level accuracy) |
Example Output
$GNPOS,31.140518542,121.284018564,45.123,45.456,4,0.85,0.012,0.018,18,24,12.345,135.67,4.15,85,1,1024,1.2,1714953600,2.5*5A\r\n
Parsing Example (Python)
def parse_gnpos(sentence):
# Remove $, checksum, and whitespace
data = sentence.strip().split('*')[0].lstrip('$')
fields = data.split(',')
if fields[0] != 'GNPOS' or len(fields) != 20:
return None
return {
'latitude': float(fields[1]),
'longitude': float(fields[2]),
'altitude': float(fields[3]),
'altitude_corrected': float(fields[4]),
'status': int(fields[5]),
'hdop': float(fields[6]),
'hrms': float(fields[7]),
'vrms': float(fields[8]),
'satellites_used': int(fields[9]),
'satellites_visible': int(fields[10]),
'speed_kmh': float(fields[11]),
'heading': float(fields[12]),
'battery_voltage': float(fields[13]),
'battery_percent': int(fields[14]),
'ntrip_connected': bool(int(fields[15])),
'rtcm_size': int(fields[16]),
'correction_age': float(fields[17]),
'timestamp': int(fields[18]),
'tilt_angle': float(fields[19])
}
Parsing Example (C)
typedef struct {
double latitude;
double longitude;
float altitude;
float altitude_corrected;
int status;
float hdop;
float hrms;
float vrms;
int satellites_used;
int satellites_visible;
float speed_kmh;
float heading;
float battery_voltage;
int battery_percent;
int ntrip_connected;
int rtcm_size;
float correction_age;
long long timestamp;
float tilt_angle;
} gnpos_data_t;
int parse_gnpos(const char* sentence, gnpos_data_t* data) {
if (strncmp(sentence, "$GNPOS,", 7) != 0) {
return -1;
}
int result = sscanf(sentence,
"$GNPOS,%lf,%lf,%f,%f,%d,%f,%f,%f,%d,%d,%f,%f,%f,%d,%d,%d,%f,%lld,%f*",
&data->latitude,
&data->longitude,
&data->altitude,
&data->altitude_corrected,
&data->status,
&data->hdop,
&data->hrms,
&data->vrms,
&data->satellites_used,
&data->satellites_visible,
&data->speed_kmh,
&data->heading,
&data->battery_voltage,
&data->battery_percent,
&data->ntrip_connected,
&data->rtcm_size,
&data->correction_age,
&data->timestamp,
&data->tilt_angle
);
return (result == 19) ? 0 : -1;
}
2. GNDEV - Device Information Sentence
Description
The GNDEV sentence provides device identification and cellular module information. This sentence is useful for device management, inventory tracking, and remote diagnostics.
Output Frequency
- Bluetooth disconnected: Every 5 seconds
- Bluetooth connected (first 10 seconds): Every 800ms (fast sync)
- Bluetooth connected (after 10 seconds): Every 5 seconds
Format
$GNDEV,<sn>,<pcb_version>,<fw_version>,<imei>,<imsi>,<iccid>*<checksum>\r\n
Field Definitions
| Field | Index | Type | Length | Description | Example |
|---|---|---|---|---|---|
| Sentence ID | 0 | String | - | Fixed identifier | GNDEV |
| sn | 1 | String | Variable | Device serial number (unique ID) | H11-20240507-001 |
| pcb_version | 2 | String | Variable | PCB hardware version | V1.2 |
| fw_version | 3 | String | Variable | Firmware version (x.x.x format) | 1.2.37 |
| imei | 4 | String | 15 | 4G module IMEI (International Mobile Equipment Identity) | 866123456789012 |
| imsi | 5 | String | 15 | SIM card IMSI (International Mobile Subscriber Identity) | 460012345678901 |
| iccid | 6 | String | 20 | SIM card ICCID (Integrated Circuit Card ID) | 89860123456789012345 |
Example Output
$GNDEV,H11-20240507-001,V1.2,1.2.37,866123456789012,460012345678901,89860123456789012345*3F\r\n
Parsing Example (Python)
def parse_gndev(sentence):
# Remove $, checksum, and whitespace
data = sentence.strip().split('*')[0].lstrip('$')
fields = data.split(',')
if fields[0] != 'GNDEV' or len(fields) != 7:
return None
return {
'serial_number': fields[1],
'pcb_version': fields[2],
'firmware_version': fields[3],
'imei': fields[4],
'imsi': fields[5],
'iccid': fields[6]
}
Parsing Example (C)
typedef struct {
char serial_number[32];
char pcb_version[16];
char firmware_version[16];
char imei[16];
char imsi[16];
char iccid[32];
} gndev_data_t;
int parse_gndev(const char* sentence, gndev_data_t* data) {
if (strncmp(sentence, "$GNDEV,", 7) != 0) {
return -1;
}
int result = sscanf(sentence,
"$GNDEV,%31[^,],%15[^,],%15[^,],%15[^,],%15[^,],%31[^*]*",
data->serial_number,
data->pcb_version,
data->firmware_version,
data->imei,
data->imsi,
data->iccid
);
return (result == 6) ? 0 : -1;
}
3. Checksum Calculation
Algorithm
The checksum is calculated as the XOR of all characters between $ and * (exclusive).
Example (C)
uint8_t calculate_nmea_checksum(const char* sentence) {
uint8_t checksum = 0;
const char* ptr = sentence;
// Skip '$' if present
if (*ptr == '$') ptr++;
// XOR all characters until '*' or end
while (*ptr && *ptr != '*') {
checksum ^= *ptr;
ptr++;
}
return checksum;
}
// Verify checksum
int verify_nmea_checksum(const char* sentence) {
const char* asterisk = strchr(sentence, '*');
if (!asterisk) return 0;
uint8_t calculated = calculate_nmea_checksum(sentence);
uint8_t received = 0;
sscanf(asterisk + 1, "%02X", &received);
return (calculated == received);
}
Example (Python)
def calculate_nmea_checksum(sentence):
# Remove $ and everything after *
data = sentence.lstrip('$').split('*')[0]
checksum = 0
for char in data:
checksum ^= ord(char)
return checksum
def verify_nmea_checksum(sentence):
if '*' not in sentence:
return False
data, checksum_str = sentence.split('*')
calculated = calculate_nmea_checksum(data)
received = int(checksum_str[:2], 16)
return calculated == received
4. Integration Guide
Enabling Custom Sentences
Use the AT+BT_OUT=SET command to enable GNPOS and GNDEV output:
AT+BT_OUT=SET,1,0,1,1,0,0,0,0,0,0\r\n
This enables:
- Custom mode (
type=1) - GNPOS sentence (
gnpos=1) - GNDEV sentence (
gndev=1) - Disables JSON and standard NMEA sentences
Complete Data Stream Example
$GNDEV,H11-20240507-001,V1.2,1.2.37,866123456789012,460012345678901,89860123456789012345*3F\r\n
$GNPOS,31.140518542,121.284018564,45.123,45.456,4,0.85,0.012,0.018,18,24,12.345,135.67,4.15,85,1,1024,1.2,1714953600,2.5*5A\r\n
$GNPOS,31.140518543,121.284018565,45.124,45.457,4,0.85,0.012,0.018,18,24,12.346,135.68,4.15,85,1,1024,1.2,1714953601,2.5*5B\r\n
$GNPOS,31.140518544,121.284018566,45.125,45.458,4,0.85,0.012,0.018,18,24,12.347,135.69,4.15,85,1,1024,1.2,1714953602,2.5*5C\r\n
$GNDEV,H11-20240507-001,V1.2,1.2.37,866123456789012,460012345678901,89860123456789012345*3F\r\n
Bluetooth Receiver Implementation
import serial
import time
class H11RTKReceiver:
def __init__(self, port, baudrate=115200):
self.serial = serial.Serial(port, baudrate, timeout=1)
self.gnpos_callback = None
self.gndev_callback = None
def set_gnpos_callback(self, callback):
self.gnpos_callback = callback
def set_gndev_callback(self, callback):
self.gndev_callback = callback
def read_loop(self):
buffer = ""
while True:
data = self.serial.read(256).decode('utf-8', errors='ignore')
buffer += data
while '\n' in buffer:
line, buffer = buffer.split('\n', 1)
line = line.strip()
if line.startswith('$GNPOS,'):
if verify_nmea_checksum(line):
gnpos_data = parse_gnpos(line)
if self.gnpos_callback and gnpos_data:
self.gnpos_callback(gnpos_data)
elif line.startswith('$GNDEV,'):
if verify_nmea_checksum(line):
gndev_data = parse_gndev(line)
if self.gndev_callback and gndev_data:
self.gndev_callback(gndev_data)
# Usage example
def on_position_update(data):
print(f"Position: {data['latitude']:.9f}, {data['longitude']:.9f}")
print(f"Status: {data['status']}, Satellites: {data['satellites_used']}")
print(f"Accuracy: H={data['hrms']:.3f}m, V={data['vrms']:.3f}m")
def on_device_info(data):
print(f"Device: {data['serial_number']}")
print(f"Firmware: {data['firmware_version']}")
print(f"IMEI: {data['imei']}")
receiver = H11RTKReceiver('/dev/rfcomm0')
receiver.set_gnpos_callback(on_position_update)
receiver.set_gndev_callback(on_device_info)
receiver.read_loop()
5. Data Quality Indicators
RTK Solution Quality
| Status | HRMS Range | VRMS Range | Typical Accuracy |
|---|---|---|---|
| RTK Fixed (4) | < 0.02m | < 0.03m | 1-2 cm horizontal, 2-3 cm vertical |
| RTK Float (5) | 0.1-0.5m | 0.2-1.0m | 10-50 cm horizontal, 20-100 cm vertical |
| DGPS (2) | 0.5-2.0m | 1.0-3.0m | 0.5-2 m horizontal, 1-3 m vertical |
| Single (1) | 2.0-10.0m | 3.0-15.0m | 2-10 m horizontal, 3-15 m vertical |
HDOP Quality Assessment
| HDOP Value | Quality | Description |
|---|---|---|
| < 1.0 | Excellent | Ideal satellite geometry |
| 1.0 - 2.0 | Good | Acceptable for RTK |
| 2.0 - 5.0 | Moderate | Usable but degraded |
| > 5.0 | Poor | Unreliable positioning |
Correction Age Guidelines
| Age (seconds) | RTK Quality | Recommendation |
|---|---|---|
| < 3.0 | Excellent | Optimal RTK performance |
| 3.0 - 10.0 | Good | Acceptable for most applications |
| 10.0 - 30.0 | Degraded | Consider reconnecting |
| > 30.0 | Poor | RTK solution unreliable |
6. Troubleshooting
No GNPOS/GNDEV Output
Possible Causes:
- Bluetooth output not configured
- Custom mode disabled
- Bluetooth connection lost
Solutions:
AT+BT_OUT=GET\r\n # Check current configuration
AT+BT_OUT=SET,1,0,1,1,0,0,0,0,0,0\r\n # Enable custom sentences
Invalid Checksum
Possible Causes:
- Data corruption during transmission
- Bluetooth interference
- Buffer overflow
Solutions:
- Verify checksum calculation algorithm
- Check Bluetooth signal strength
- Increase receive buffer size
Incorrect Data Values
Possible Causes:
- GNSS not initialized
- No satellite fix
- Antenna disconnected
Solutions:
- Check
statusfield (should be > 0) - Verify
satViewandsatUsed(should be > 4) - Check antenna connection
7. Best Practices
Data Processing
- Always verify checksums before parsing data
- Check positioning status before using coordinates
- Monitor correction age for RTK applications
- Validate satellite count (minimum 4 for 3D fix)
- Check HDOP values for solution quality
Performance Optimization
- Buffer management: Use circular buffers for continuous data streams
- Parsing efficiency: Pre-compile regex patterns or use fixed-format parsing
- Callback design: Keep callbacks lightweight, defer heavy processing
- Error handling: Implement timeout and retry mechanisms
Application Design
- Position filtering: Apply Kalman filtering for smooth trajectories
- Status monitoring: Track RTK solution quality over time
- Battery management: Monitor voltage and percentage for low-power warnings
- Connection health: Track NTRIP status and correction age
Appendix A: Complete Message Examples
RTK Fixed Solution (High Quality)
$GNPOS,31.140518542,121.284018564,45.123,45.456,4,0.85,0.012,0.018,18,24,0.000,0.00,4.15,85,1,1024,1.2,1714953600,0.5*XX\r\n
- Status: 4 (RTK Fixed)
- HRMS: 0.012m (1.2cm horizontal accuracy)
- VRMS: 0.018m (1.8cm vertical accuracy)
- 18 satellites used, 24 visible
- NTRIP connected, 1.2s correction age
RTK Float Solution (Medium Quality)
$GNPOS,31.140518542,121.284018564,45.123,45.456,5,1.20,0.250,0.450,15,22,5.234,45.30,4.10,80,1,1024,3.5,1714953600,1.2*XX\r\n
- Status: 5 (RTK Float)
- HRMS: 0.250m (25cm horizontal accuracy)
- VRMS: 0.450m (45cm vertical accuracy)
- 15 satellites used, 22 visible
- NTRIP connected, 3.5s correction age
Single Point Solution (Low Quality)
$GNPOS,31.140518542,121.284018564,45.123,45.456,1,2.50,3.500,5.200,8,15,12.345,135.67,3.95,65,0,0,0.0,1714953600,2.5*XX\r\n
- Status: 1 (Single Point)
- HRMS: 3.500m (3.5m horizontal accuracy)
- VRMS: 5.200m (5.2m vertical accuracy)
- 8 satellites used, 15 visible
- NTRIP disconnected
Document Version: 1.0
Firmware Version: 1.2.37
Last Updated: 2026-05-07