#!/usr/bin/env python3 """ Capture raw RTCM message 208 to binary file for manual analysis. """ import base64 import socket import time from datetime import datetime # NTRIP configuration CASTER_HOST = "truertk.pointonenav.com" CASTER_PORT = 2101 MOUNTPOINT = "AUTO" USERNAME = "9t7fwfbm57" PASSWORD = "96m7bec9g8" LAT = 36.1140884 LON = -97.0880663 ALT = 390.0 def build_gga(lat: float, lon: float, alt: float) -> bytes: """Build NMEA GGA sentence.""" now_utc = datetime.utcnow().strftime("%H%M%S") lat_hemi = "N" if lat >= 0 else "S" lat_abs = abs(lat) lat_deg = int(lat_abs) lat_min = (lat_abs - lat_deg) * 60.0 lat_str = f"{lat_deg:02d}{lat_min:07.4f}" lon_hemi = "E" if lon >= 0 else "W" lon_abs = abs(lon) lon_deg = int(lon_abs) lon_min = (lon_abs - lon_deg) * 60.0 lon_str = f"{lon_deg:03d}{lon_min:07.4f}" fields = ["GPGGA", now_utc, lat_str, lat_hemi, lon_str, lon_hemi, "1", "12", "1.0", f"{alt:.1f}", "M", "", "M", "", ""] core = ",".join(fields) checksum = 0 for char in core: checksum ^= ord(char) return f"${core}*{checksum:02X}\r\n".encode("ascii") def make_ntrip_request(host: str, port: int, mount: str, user: str, password: str) -> bytes: """Create NTRIP v2 HTTP request.""" auth = base64.b64encode(f"{user}:{password}".encode("utf-8")).decode("ascii") req = ( f"GET /{mount} HTTP/1.1\r\n" f"Host: {host}:{port}\r\n" f"Ntrip-Version: Ntrip/2.0\r\n" f"User-Agent: RTCM-Capture/1.0\r\n" f"Connection: close\r\n" f"Authorization: Basic {auth}\r\n\r\n" ) return req.encode("ascii") def main(): timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") output_file = f"rtcm_208_raw_{timestamp}.bin" index_file = f"rtcm_208_index_{timestamp}.txt" print(f"RTCM Message 208 Raw Capture") print(f"=" * 80) print(f"Caster: {CASTER_HOST}:{CASTER_PORT}/{MOUNTPOINT}") print(f"Output: {output_file}") print(f"Index: {index_file}") print(f"Capturing for 60 seconds or 20 messages, whichever comes first...") print(f"=" * 80) print() last_gga_time = 0 start_time = time.monotonic() msg_208_count = 0 total_messages = 0 try: # Connect print("Connecting...") sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(30) sock.connect((CASTER_HOST, CASTER_PORT)) sock.sendall(make_ntrip_request(CASTER_HOST, CASTER_PORT, MOUNTPOINT, USERNAME, PASSWORD)) # Read headers header = b"" while b"\r\n\r\n" not in header: chunk = sock.recv(1) if not chunk: print("ERROR: Connection closed") return header += chunk if "200 OK" not in header.decode("iso-8859-1", errors="replace"): print("ERROR: Connection failed") return print("Connected!\n") # Open output files with open(output_file, 'wb') as binfile, open(index_file, 'w') as idxfile: idxfile.write(f"# RTCM 208 Message Index\n") idxfile.write(f"# Captured: {datetime.now().isoformat()}\n") idxfile.write(f"# Format: message_number, offset, length, timestamp\n") idxfile.write(f"#\n") file_offset = 0 # Receive loop while time.monotonic() - start_time < 60 and msg_208_count < 20: now = time.monotonic() # Send GGA every 10 seconds if now - last_gga_time >= 10: sock.sendall(build_gga(LAT, LON, ALT)) elapsed = int(now - start_time) print(f"[{elapsed:3d}s] Total: {total_messages}, Msg 208: {msg_208_count}/20") last_gga_time = now # Receive data data = sock.recv(4096) if not data: print("Connection closed") break # Parse messages i = 0 while i < len(data): if data[i] == 0xD3 and i + 2 < len(data): length = ((data[i+1] & 0x03) << 8) | data[i+2] msg_total_len = 3 + length + 3 if i + msg_total_len <= len(data) and length >= 3: msg_type = (data[i+3] << 4) | (data[i+4] >> 4) total_messages += 1 if msg_type == 208: msg_208_count += 1 msg_data = data[i+3:i+3+length] # Just the payload # Write to binary file binfile.write(msg_data) binfile.flush() # Write to index ts = datetime.now().isoformat() idxfile.write(f"{msg_208_count},{file_offset},{length},{ts}\n") idxfile.flush() print(f" ✓ Captured message 208 #{msg_208_count} - {length} bytes at offset {file_offset}") file_offset += length i += msg_total_len continue i += 1 except KeyboardInterrupt: print("\n\nInterrupted by user") except Exception as e: print(f"\nERROR: {e}") import traceback traceback.print_exc() finally: if 'sock' in locals(): try: sock.close() except: pass print(f"\n{'=' * 80}") print(f"CAPTURE COMPLETE") print(f"{'=' * 80}") print(f"Messages captured: {msg_208_count}") print(f"Output file: {output_file} ({file_offset} bytes)") print(f"Index file: {index_file}") print() print("To view the binary file:") print(f" hexdump -C {output_file}") print(f" xxd {output_file}") print() print("To extract a specific message (e.g., message #1):") print(" 1. Look up offset and length in index file") print(" 2. Use: dd if={output_file} bs=1 skip= count= | hexdump -C") if __name__ == "__main__": main()