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
157 lines
6.0 KiB
Python
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)
|