Files
venus/axiom-nmea/debug/analyze_structure.py
dev 9756538f16 Initial commit: Venus OS boat addons monorepo
Organizes 11 projects for Cerbo GX/Venus OS into a single repository:
- axiom-nmea: Raymarine LightHouse protocol decoder
- dbus-generator-ramp: Generator current ramp controller
- dbus-lightning: Blitzortung lightning monitor
- dbus-meteoblue-forecast: Meteoblue weather forecast
- dbus-no-foreign-land: noforeignland.com tracking
- dbus-tides: Tide prediction from depth + harmonics
- dbus-vrm-history: VRM cloud history proxy
- dbus-windy-station: Windy.com weather upload
- mfd-custom-app: MFD app deployment package
- venus-html5-app: Custom Victron HTML5 app fork
- watermaker: Watermaker PLC control UI

Adds root README, .gitignore, project template, and per-project
.gitignore files. Sensitive config files excluded via .gitignore
with .example templates provided.

Made-with: Cursor
2026-03-16 17:04:16 +00:00

95 lines
3.2 KiB
Python

#!/usr/bin/env python3
"""
Analyze packet structure to understand why offsets vary.
Look for common headers and protobuf nesting patterns.
"""
import struct
def read_pcap(filename):
packets = []
with open(filename, 'rb') as f:
header = f.read(24)
magic = struct.unpack('<I', header[0:4])[0]
swapped = magic == 0xd4c3b2a1
endian = '>' if swapped else '<'
while True:
pkt_header = f.read(16)
if len(pkt_header) < 16:
break
ts_sec, ts_usec, incl_len, orig_len = struct.unpack(f'{endian}IIII', pkt_header)
pkt_data = f.read(incl_len)
if len(pkt_data) < incl_len:
break
if len(pkt_data) > 42 and pkt_data[12:14] == b'\x08\x00':
ip_header_len = (pkt_data[14] & 0x0F) * 4
payload_start = 14 + ip_header_len + 8
if payload_start < len(pkt_data):
packets.append(pkt_data[payload_start:])
return packets
packets = read_pcap("raymarine_sample_TWD_62-70_HDG_29-35.pcap")
# Group by size
by_size = {}
for pkt in packets:
pkt_len = len(pkt)
if pkt_len not in by_size:
by_size[pkt_len] = []
by_size[pkt_len].append(pkt)
print("=" * 70)
print("PACKET HEADER ANALYSIS")
print("=" * 70)
for pkt_len in sorted(by_size.keys()):
if pkt_len < 100:
continue
pkts = by_size[pkt_len][:3] # First 3 packets of each size
print(f"\n{'='*70}")
print(f"PACKET SIZE: {pkt_len} bytes ({len(by_size[pkt_len])} total)")
print("=" * 70)
pkt = pkts[0]
# Show first 128 bytes as hex
print("\nFirst 128 bytes (hex):")
for row in range(0, min(128, pkt_len), 16):
hex_str = ' '.join(f'{b:02x}' for b in pkt[row:row+16])
ascii_str = ''.join(chr(b) if 32 <= b < 127 else '.' for b in pkt[row:row+16])
print(f" 0x{row:04x}: {hex_str:<48} {ascii_str}")
# Look for the GPS pattern (0x09 followed by lat, 0x11 followed by lon)
gps_offset = None
for i in range(0x20, min(pkt_len - 18, 0x60)):
if pkt[i] == 0x09 and i + 9 < pkt_len and pkt[i + 9] == 0x11:
lat = struct.unpack('<d', pkt[i+1:i+9])[0]
lon = struct.unpack('<d', pkt[i+10:i+18])[0]
if -90 <= lat <= 90 and -180 <= lon <= 180 and abs(lat) > 1:
gps_offset = i
print(f"\n GPS found at offset 0x{i:04x}: {lat:.6f}, {lon:.6f}")
break
# Look for string patterns (device name)
for i in range(0x10, min(pkt_len - 10, 0x40)):
if pkt[i:i+5] == b'AXIOM':
print(f" 'AXIOM' string at offset 0x{i:04x}")
# Show surrounding context
end = min(i + 30, pkt_len)
print(f" Context: {pkt[i:end]}")
break
# Analyze protobuf wire types at key offsets
print(f"\n Protobuf tags at key offsets:")
for offset in [0x0070, 0x00a0, 0x00a7, 0x00c5, 0x00fc]:
if offset < pkt_len:
tag = pkt[offset]
wire_type = tag & 0x07
field_num = tag >> 3
wire_names = {0: 'varint', 1: 'fixed64', 2: 'length', 5: 'fixed32'}
print(f" 0x{offset:04x}: tag=0x{tag:02x} (field {field_num}, {wire_names.get(wire_type, '?')})")