# 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:** ``` $*\r\n ``` - `$` - Start delimiter - `` - Sentence identifier and data fields (comma-separated) - `*` - Checksum delimiter - `` - 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,,,,,,,,,,,,,,,,,,,*\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) ```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) ```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,,,,,,*\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) ```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) ```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) ```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) ```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 ```python 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