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
392 lines
17 KiB
Python
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
|