Files
maglink-console/Custom_NMEA_Sentences.md
brentperteet 5703c05c1d Initial commit
2026-06-24 11:12:44 -05:00

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=SET command

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:

  1. Bluetooth output not configured
  2. Custom mode disabled
  3. 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:

  1. Data corruption during transmission
  2. Bluetooth interference
  3. Buffer overflow

Solutions:

  • Verify checksum calculation algorithm
  • Check Bluetooth signal strength
  • Increase receive buffer size

Incorrect Data Values

Possible Causes:

  1. GNSS not initialized
  2. No satellite fix
  3. Antenna disconnected

Solutions:

  • Check status field (should be > 0)
  • Verify satView and satUsed (should be > 4)
  • Check antenna connection

7. Best Practices

Data Processing

  1. Always verify checksums before parsing data
  2. Check positioning status before using coordinates
  3. Monitor correction age for RTK applications
  4. Validate satellite count (minimum 4 for 3D fix)
  5. Check HDOP values for solution quality

Performance Optimization

  1. Buffer management: Use circular buffers for continuous data streams
  2. Parsing efficiency: Pre-compile regex patterns or use fixed-format parsing
  3. Callback design: Keep callbacks lightweight, defer heavy processing
  4. Error handling: Implement timeout and retry mechanisms

Application Design

  1. Position filtering: Apply Kalman filtering for smooth trajectories
  2. Status monitoring: Track RTK solution quality over time
  3. Battery management: Monitor voltage and percentage for low-power warnings
  4. 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