Files
venus/axiom-nmea/debug/field_mapping.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

157 lines
6.0 KiB
Python

#!/usr/bin/env python3
"""
Extract and display the field mapping for sensor data.
Based on protobuf structure analysis.
"""
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
# Based on protobuf analysis, here's the structure:
STRUCTURE = """
================================================================================
RAYMARINE PACKET STRUCTURE (from protobuf analysis)
================================================================================
FIXED HEADER (20 bytes @ 0x0000-0x0013):
0x0000-0x0007: Packet identifier (00 00 00 00 00 00 00 01)
0x0008-0x000B: Source ID
0x000C-0x000F: Message type indicator
0x0010-0x0013: Payload length
PROTOBUF MESSAGE (starts @ 0x0014):
Field 1 (length-delim): Device Info
└─ Field 1: Device name ("AXIOM 12")
└─ Field 2: Serial number info
Field 2 (length-delim): GPS/Position Data
├─ Field 1 (fixed64/double): LATITUDE
├─ Field 2 (fixed64/double): LONGITUDE
├─ Field 3 (fixed64/double): (unknown, often NaN)
├─ Field 4 (fixed64/double): (altitude or similar)
├─ Field 5 (fixed64/double): (timestamp or distance)
└─ Field 6 (fixed64/double): (timestamp or distance)
Field 3 (length-delim): Heading Block
├─ Field 1 (fixed32/float): Heading value 1 (radians)
└─ Field 2 (fixed32/float): HEADING (radians) ← ~31-33°
Field 6 (length-delim): [only in 446+ byte packets]
└─ Field 1 (fixed32/float): (unknown angle)
Field 8 (length-delim): Rate/Motion Data
├─ Field 1 (fixed32/float): Rate of turn?
└─ Field 2 (fixed32/float): (often NaN)
Field 13 (length-delim): WIND/NAVIGATION DATA ← Main sensor block
├─ Field 1 (fixed32/float): Heading copy (radians)
├─ Field 2 (fixed32/float): Heading smoothed (radians)
├─ Field 3 (fixed32/float): (small angle, ~0°)
├─ Field 4 (fixed32/float): TRUE WIND DIRECTION (radians) ← ~62-70°
├─ Field 5 (fixed32/float): WIND SPEED (m/s) ← ~7.26 = 14.1 kts
├─ Field 6 (fixed32/float): Wind speed (different value)
├─ Field 7 (fixed32/float): (large angle ~245°)
├─ Field 8-13: Duplicates/smoothed values of above
Field 14 (length-delim): Additional sensor data (multiple instances)
Field 21 (length-delim): More angles
Field 38, 41: Empty/reserved
================================================================================
KEY SENSOR MAPPINGS
================================================================================
SENSOR PARENT FIELD CHILD FIELD UNIT
------------------ ------------- ----------- --------
Latitude Field 2 Field 1 degrees (double)
Longitude Field 2 Field 2 degrees (double)
Heading Field 3 Field 2 radians (float)
True Wind Dir Field 13 Field 4 radians (float)
Wind Speed Field 13 Field 5 m/s (float)
================================================================================
"""
print(STRUCTURE)
# Now verify with actual data
print("VERIFICATION WITH ACTUAL DATA:")
print("=" * 70)
packets = read_pcap("raymarine_sample_TWD_62-70_HDG_29-35.pcap")
# Get a 344-byte packet
for pkt in packets:
if len(pkt) == 344:
# Skip 20-byte header, protobuf starts at 0x14
proto = pkt[0x14:]
print(f"\n344-byte packet analysis:")
print(f" Expected: TWD 62-70°, Heading 29-35°")
# Field 3 starts around offset 0x54 in original packet = 0x40 in proto
# But we need to navigate by protobuf structure
# Let's use the known byte offsets and verify
# From earlier analysis:
# - 0x0070 had heading ~32.6°
# - 0x00a0 had TWD ~61.7°
def get_float(data, offset):
if offset + 4 <= len(data):
return struct.unpack('<f', data[offset:offset+4])[0]
return None
# Check field 3 (heading block) - starts around 0x54
# Field 3, field 2 should be heading
heading_offset = 0x0070 # From earlier analysis
heading_val = get_float(pkt, heading_offset)
if heading_val:
heading_deg = heading_val * 57.2958
print(f" Heading @ 0x{heading_offset:04x}: {heading_val:.4f} rad = {heading_deg:.1f}°")
# Check field 13, field 4 (TWD)
twd_offset = 0x00a0 # From earlier analysis
twd_val = get_float(pkt, twd_offset)
if twd_val:
twd_deg = twd_val * 57.2958
print(f" TWD @ 0x{twd_offset:04x}: {twd_val:.4f} rad = {twd_deg:.1f}°")
# Check field 13, field 5 (wind speed)
ws_offset = 0x00a5 # Should be right after TWD
ws_val = get_float(pkt, ws_offset)
if ws_val:
ws_kts = ws_val * 1.94384
print(f" Wind Speed @ 0x{ws_offset:04x}: {ws_val:.2f} m/s = {ws_kts:.1f} kts")
break
print("\n" + "=" * 70)
print("CONCLUSION: The protobuf structure explains the 'random' offsets:")
print(" - Same field numbers have different byte offsets per packet size")
print(" - This is because preceding fields vary in length")
print(" - Solution: Parse protobuf structure, not fixed byte offsets")
print("=" * 70)