Files
venus/axiom-nmea/raymarine_nmea/protocol/constants.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

392 lines
17 KiB
Python

"""
Protocol constants for Raymarine LightHouse decoding.
This module defines the reverse-engineered protobuf schema for Raymarine's
LightHouse network protocol. The protocol uses Google Protocol Buffers over
UDP multicast (226.192.206.102:2565) with a 20-byte proprietary header.
================================================================================
PACKET STRUCTURE
================================================================================
┌──────────────────────────────────────────────────────────────────────────┐
│ Bytes 0-19: Raymarine Header (20 bytes) │
├──────────────────────────────────────────────────────────────────────────┤
│ Bytes 20+: Protobuf Payload (variable length) │
└──────────────────────────────────────────────────────────────────────────┘
================================================================================
MESSAGE HIERARCHY (Proto-like Schema)
================================================================================
LightHousePacket {
1: DeviceInfo device_info [message]
2: GpsPosition gps [message] ✓ RELIABLE
3: HeadingData heading [message] ~ VARIABLE
5: Navigation navigation [message] ✓ RELIABLE
7: DepthData depth [message] ~ VARIABLE
13: WindData wind [message] ~ VARIABLE
14: EngineData[] engines [repeated] ✓ RELIABLE
15: EnvironmentData environment [message] ✓ RELIABLE
16: TankData[] tanks [repeated] ✓ RELIABLE
20: BatteryData[] house_batteries [repeated] ✓ RELIABLE
}
GpsPosition (Field 2) {
1: double latitude [fixed64] // Decimal degrees, -90 to +90
2: double longitude [fixed64] // Decimal degrees, -180 to +180
}
HeadingData (Field 3) {
1: float cog [fixed32] // Course over ground (radians)
2: float heading [fixed32] // Compass heading (radians)
}
Navigation (Field 5) {
1: float cog [fixed32] // Course over ground (radians)
3: float sog [fixed32] // Speed over ground (m/s)
}
DepthData (Field 7) {
1: float depth [fixed32] // Depth in meters
}
WindData (Field 13) {
4: float twd [fixed32] // True wind direction (radians)
5: float tws [fixed32] // True wind speed (m/s)
6: float aws [fixed32] // Apparent wind speed (m/s)
}
EngineData (Field 14, repeated) {
1: int32 engine_id [varint] // 0=Port, 1=Starboard
3: EngineSensors sensors [message]
}
EngineSensors (Field 14.3) {
4: float battery_voltage [fixed32] // Volts
}
EnvironmentData (Field 15) {
1: float pressure [fixed32] // Barometric pressure (Pascals)
3: float air_temp [fixed32] // Air temperature (Kelvin)
9: float water_temp [fixed32] // Water temperature (Kelvin)
}
TankData (Field 16, repeated) {
1: int32 tank_id [varint] // Tank identifier (see inference)
2: int32 status [varint] // Tank type/status flag
3: float level [fixed32] // Fill percentage (0-100)
}
BatteryData (Field 20, repeated) {
1: int32 battery_id [varint] // Battery identifier
3: float voltage [fixed32] // Volts
}
================================================================================
INFERENCE RULES (Field-Presence Logic)
================================================================================
The protocol does NOT include explicit message type identifiers. Instead,
the presence or absence of fields determines meaning:
TANK ID INFERENCE (Field 16):
┌─────────────────────────────────────────────────────────────────────────┐
│ If tank_id (16.1) is ABSENT: │
│ - If status == 5 (WASTE) → tank_id = 100 (Black/Gray Water) │
│ - If status is ABSENT → tank_id = 2 (Port Fuel) │
│ │
│ Port Fuel is the ONLY tank that transmits with neither ID nor status. │
└─────────────────────────────────────────────────────────────────────────┘
ENGINE ID INFERENCE (Field 14):
┌─────────────────────────────────────────────────────────────────────────┐
│ If engine_id (14.1) is ABSENT: │
│ - Default to engine_id = 0 (Port Engine) │
│ │
│ Starboard engine explicitly sends engine_id = 1. │
└─────────────────────────────────────────────────────────────────────────┘
================================================================================
WIRE TYPES (Protobuf Encoding)
================================================================================
Protobuf encodes each field with a tag: (field_number << 3) | wire_type
Type 0 (VARINT): Variable-length integers (IDs, counts, enums)
Type 1 (FIXED64): 8-byte values (doubles for GPS coordinates)
Type 2 (LENGTH): Length-delimited (nested messages, strings)
Type 5 (FIXED32): 4-byte values (floats for angles, speeds, voltages)
================================================================================
UNIT CONVENTIONS
================================================================================
Angles: Radians (0 to 2π) → Convert with RAD_TO_DEG
Speed: Meters/second → Convert with MS_TO_KTS
Temperature: Kelvin → Subtract KELVIN_OFFSET for Celsius
Pressure: Pascals → Multiply by PA_TO_MBAR for millibars
Depth: Meters → Divide by FEET_TO_M for feet
Voltage: Volts (direct)
Tank Level: Percentage (0-100)
================================================================================
"""
# ==============================================================================
# WIRE TYPES
# ==============================================================================
WIRE_VARINT = 0 # Variable-length integers (IDs, counts, enums)
WIRE_FIXED64 = 1 # 8-byte values (GPS coordinates as doubles)
WIRE_LENGTH = 2 # Length-delimited (nested messages, strings)
WIRE_FIXED32 = 5 # 4-byte values (angles, speeds, voltages as floats)
# Raymarine packet header size (bytes before protobuf payload)
HEADER_SIZE = 20
# ==============================================================================
# TOP-LEVEL FIELDS
# ==============================================================================
class Fields:
"""Top-level protobuf field numbers in LightHousePacket.
All top-level fields are length-delimited messages (wire type 2).
Fields marked [repeated] appear multiple times for multiple instances.
"""
DEVICE_INFO = 1 # DeviceInfo - Device name and serial
GPS_POSITION = 2 # GpsPosition - Latitude/longitude (reliable)
HEADING = 3 # HeadingData - Compass heading (variable)
SOG_COG = 5 # Navigation - Speed/course over ground (reliable)
DEPTH = 7 # DepthData - Water depth (variable, large packets)
WIND_NAVIGATION = 13 # WindData - Wind speed/direction (variable)
ENGINE_DATA = 14 # EngineData[] - [repeated] Engine sensors + battery
TEMPERATURE = 15 # EnvironmentData - Temp/pressure (reliable)
TANK_DATA = 16 # TankData[] - [repeated] Tank levels (reliable)
HOUSE_BATTERY = 20 # BatteryData[] - [repeated] House batteries (reliable)
# ==============================================================================
# NESTED FIELD DEFINITIONS
# ==============================================================================
class GPSFields:
"""Field 2: GpsPosition - GPS coordinates.
Reliability: ✓ HIGH
Wire types: All fields are fixed64 (8-byte doubles)
Example values:
latitude: 26.123456 (decimal degrees)
longitude: -80.654321 (decimal degrees)
"""
LATITUDE = 1 # fixed64/double - Decimal degrees, -90 to +90
LONGITUDE = 2 # fixed64/double - Decimal degrees, -180 to +180
COG_RAD = 3 # fixed64/double - Course over ground (radians), NaN when stationary
SOG_MS = 4 # fixed32/float - Speed over ground (m/s)
class HeadingFields:
"""Field 3: HeadingData - Compass and course data.
Reliability: ~ VARIABLE (context-dependent)
Wire types: All fields are fixed32 (4-byte floats)
"""
COG_RAD = 1 # fixed32/float - Course over ground (radians)
HEADING_RAD = 2 # fixed32/float - Compass heading (radians, 0 to 2π)
class SOGCOGFields:
"""Field 5: Navigation - GPS-derived speed and course.
Reliability: ✓ HIGH
Wire types: All fields are fixed32 (4-byte floats)
Packet size: Found in 86-92 byte packets
This is the PRIMARY source for SOG/COG data.
"""
COG_RAD = 1 # fixed32/float - Course over ground (radians)
# Field 2 exists but purpose unknown
SOG_MS = 3 # fixed32/float - Speed over ground (m/s)
class DepthFields:
"""Field 7: DepthData - Water depth.
Reliability: ~ VARIABLE (only in larger packets)
Wire types: fixed32 (4-byte float)
"""
DEPTH_METERS = 1 # fixed32/float - Depth in meters
class WindFields:
"""Field 13: WindData - Wind speed and direction.
Reliability: ~ VARIABLE (depends on wind sensor availability)
Wire types: All fields are fixed32 (4-byte floats)
Note: Fields 1-3 exist but purpose unknown.
"""
# Fields 1-3 unknown
TRUE_WIND_DIRECTION = 4 # fixed32/float - TWD in radians
TRUE_WIND_SPEED = 5 # fixed32/float - TWS in m/s
APPARENT_WIND_SPEED = 6 # fixed32/float - AWS in m/s
class TemperatureFields:
"""Field 15: EnvironmentData - Temperature and pressure sensors.
Reliability: ✓ HIGH
Wire types: All fields are fixed32 (4-byte floats)
Note: Fields 2, 4-8 exist but purpose unknown.
"""
BAROMETRIC_PRESSURE = 1 # fixed32/float - Pascals (divide by 100 for mbar)
# Field 2 unknown
AIR_TEMP = 3 # fixed32/float - Kelvin (subtract 273.15 for °C)
# Fields 4-8 unknown
WATER_TEMP = 9 # fixed32/float - Kelvin (subtract 273.15 for °C)
class TankFields:
"""Field 16: TankData - Tank level sensors (repeated message).
Reliability: ✓ HIGH
Wire types: Mixed (see individual fields)
INFERENCE RULE:
If tank_id is ABSENT:
- status == 5 (WASTE) → tank_id = 100 (Black/Gray Water)
- status is ABSENT → tank_id = 2 (Port Fuel, unique case)
"""
TANK_ID = 1 # varint/int32 - Tank identifier (may be absent, see inference)
STATUS = 2 # varint/int32 - Tank type flag (5 = waste tank)
LEVEL_PCT = 3 # fixed32/float - Fill percentage (0-100)
class BatteryFields:
"""Field 20: BatteryData - House battery sensors (repeated message).
Reliability: ✓ HIGH
Wire types: Mixed (see individual fields)
Known battery IDs: 11, 13 (house batteries)
"""
BATTERY_ID = 1 # varint/int32 - Battery identifier
# Field 2 unknown
VOLTAGE = 3 # fixed32/float - Voltage in volts
class EngineFields:
"""Field 14: EngineData - Engine sensors (repeated message).
Reliability: ✓ HIGH
Wire types: Mixed (see individual fields)
Structure: 3 levels of nesting
Field 14 (EngineData)
└─ Field 14.3 (EngineSensors message)
└─ Field 14.3.4 (battery_voltage float)
INFERENCE RULE:
If engine_id is ABSENT → default to 0 (Port Engine)
Starboard engine explicitly sends engine_id = 1
Battery IDs: Stored as 1000 + engine_id (1000=Port, 1001=Starboard)
"""
ENGINE_ID = 1 # varint/int32 - Engine ID (0=Port, 1=Starboard)
# Field 2 unknown
SENSOR_DATA = 3 # message - EngineSensors (nested message)
# Within SENSOR_DATA (Field 14.3):
# Fields 1-3 unknown
BATTERY_VOLTAGE = 4 # fixed32/float - Battery voltage in volts
# ==============================================================================
# UNIT CONVERSIONS
# ==============================================================================
# Raymarine uses SI units internally. These convert to common marine units.
RAD_TO_DEG = 57.2957795131 # radians → degrees (180/π)
MS_TO_KTS = 1.94384449 # m/s → knots
FEET_TO_M = 0.3048 # feet → meters
KELVIN_OFFSET = 273.15 # Kelvin → Celsius (subtract)
FATHOMS_TO_M = 1.8288 # fathoms → meters
PA_TO_MBAR = 0.01 # Pascals → millibars (hPa)
# ==============================================================================
# VALIDATION RANGES
# ==============================================================================
# Values outside these ranges are rejected as invalid/corrupt data.
class ValidationRanges:
"""Valid ranges for decoded sensor values.
These ranges filter out corrupt or invalid data. Values outside
these bounds are discarded during decoding.
"""
# -------------------------------------------------------------------------
# GPS Position
# -------------------------------------------------------------------------
LATITUDE_MIN = -90.0
LATITUDE_MAX = 90.0
LONGITUDE_MIN = -180.0
LONGITUDE_MAX = 180.0
NULL_ISLAND_THRESHOLD = 0.1 # Reject positions within 0.1° of (0,0)
# -------------------------------------------------------------------------
# Angles (radians) - Heading, COG, Wind Direction
# -------------------------------------------------------------------------
ANGLE_MIN = 0.0
ANGLE_MAX = 6.5 # Slightly > 2π (6.283) to allow small errors
# -------------------------------------------------------------------------
# Speed (m/s) - SOG, Wind Speed
# -------------------------------------------------------------------------
SPEED_MIN = 0.0
SPEED_MAX = 100.0 # ~194 knots (theoretical max for any vessel)
# -------------------------------------------------------------------------
# Depth (meters)
# -------------------------------------------------------------------------
DEPTH_MIN = 0.0
DEPTH_MAX = 1000.0 # ~3280 feet / ~546 fathoms
# -------------------------------------------------------------------------
# Temperature (Kelvin)
# -------------------------------------------------------------------------
AIR_TEMP_MIN = 200.0 # -73°C (extreme cold)
AIR_TEMP_MAX = 350.0 # +77°C (extreme heat)
WATER_TEMP_MIN = 270.0 # -3°C (near freezing)
WATER_TEMP_MAX = 320.0 # +47°C (tropical/engine room)
# -------------------------------------------------------------------------
# Tank Level (percentage)
# -------------------------------------------------------------------------
TANK_MIN = 0.0
TANK_MAX = 100.0
# -------------------------------------------------------------------------
# Battery Voltage
# -------------------------------------------------------------------------
VOLTAGE_MIN = 10.0 # Below 10V = dead/disconnected
VOLTAGE_MAX = 60.0 # Covers 12V, 24V, 48V systems with headroom
# -------------------------------------------------------------------------
# Barometric Pressure (Pascals)
# -------------------------------------------------------------------------
PRESSURE_MIN = 87000.0 # ~870 mbar (record low: 870 mbar, Typhoon Tip)
PRESSURE_MAX = 108400.0 # ~1084 mbar (record high: 1084 mbar, Siberia)
# ==============================================================================
# PROTOCOL CONSTANTS
# ==============================================================================
# Tank status values (Field 16.2)
TANK_STATUS_WASTE = 5 # Black/gray water tanks transmit status=5