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
135 lines
4.3 KiB
Python
135 lines
4.3 KiB
Python
"""
|
|
GPS D-Bus service for Venus OS.
|
|
|
|
Publishes GPS position, speed, and course data to the Venus OS D-Bus
|
|
using the com.victronenergy.gps service type.
|
|
|
|
D-Bus paths:
|
|
/Altitude - Height in meters
|
|
/Course - Direction in degrees (COG)
|
|
/Fix - GPS fix status (0=no fix, 1=fix)
|
|
/NrOfSatellites - Number of satellites (not available from Raymarine)
|
|
/Position/Latitude - Latitude in degrees
|
|
/Position/Longitude - Longitude in degrees
|
|
/Speed - Speed in m/s (SOG)
|
|
"""
|
|
|
|
import logging
|
|
from typing import Any, Dict, Optional
|
|
|
|
from .service import VeDbusServiceBase
|
|
from ..data.store import SensorData
|
|
from ..protocol.constants import MS_TO_KTS
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Conversion: knots to m/s
|
|
KTS_TO_MS = 1.0 / MS_TO_KTS # Approximately 0.514444
|
|
|
|
|
|
class GpsService(VeDbusServiceBase):
|
|
"""GPS D-Bus service for Venus OS.
|
|
|
|
Publishes GPS position, speed, and course from Raymarine sensors
|
|
to the Venus OS D-Bus.
|
|
|
|
Example:
|
|
sensor_data = SensorData()
|
|
gps_service = GpsService(sensor_data)
|
|
gps_service.register()
|
|
|
|
# In update loop:
|
|
gps_service.update()
|
|
"""
|
|
|
|
service_type = "gps"
|
|
product_name = "Raymarine GPS"
|
|
product_id = 0xA140 # Custom product ID for Raymarine GPS
|
|
|
|
# Maximum age in seconds before GPS data is considered stale
|
|
MAX_DATA_AGE = 10.0
|
|
|
|
def __init__(
|
|
self,
|
|
sensor_data: SensorData,
|
|
device_instance: int = 0,
|
|
custom_name: Optional[str] = None,
|
|
):
|
|
"""Initialize GPS service.
|
|
|
|
Args:
|
|
sensor_data: SensorData instance to read GPS values from
|
|
device_instance: Unique instance number (default: 0)
|
|
custom_name: Optional custom display name
|
|
"""
|
|
super().__init__(
|
|
device_instance=device_instance,
|
|
connection="Raymarine LightHouse GPS",
|
|
custom_name=custom_name,
|
|
)
|
|
self._sensor_data = sensor_data
|
|
|
|
def _get_paths(self) -> Dict[str, Dict[str, Any]]:
|
|
"""Return GPS-specific D-Bus paths."""
|
|
return {
|
|
'/Altitude': {'initial': None},
|
|
'/Course': {'initial': None},
|
|
'/Fix': {'initial': 0},
|
|
'/NrOfSatellites': {'initial': None},
|
|
'/Position/Latitude': {'initial': None},
|
|
'/Position/Longitude': {'initial': None},
|
|
'/Speed': {'initial': None},
|
|
}
|
|
|
|
def _update(self) -> None:
|
|
"""Update GPS values from sensor data."""
|
|
data = self._sensor_data
|
|
|
|
# Check if we have valid GPS data
|
|
has_position = (
|
|
data.latitude is not None and
|
|
data.longitude is not None
|
|
)
|
|
|
|
# Check data freshness
|
|
is_stale = data.is_stale('gps', self.MAX_DATA_AGE)
|
|
|
|
if has_position and not is_stale:
|
|
# Valid GPS fix
|
|
self._set_value('/Fix', 1)
|
|
self._set_value('/Position/Latitude', data.latitude)
|
|
self._set_value('/Position/Longitude', data.longitude)
|
|
|
|
# Course over ground (degrees)
|
|
if data.cog_deg is not None:
|
|
self._set_value('/Course', round(data.cog_deg, 1))
|
|
else:
|
|
self._set_value('/Course', None)
|
|
|
|
# Speed over ground (convert knots to m/s)
|
|
if data.sog_kts is not None:
|
|
speed_ms = data.sog_kts * KTS_TO_MS
|
|
self._set_value('/Speed', round(speed_ms, 2))
|
|
else:
|
|
self._set_value('/Speed', None)
|
|
|
|
# Altitude not available from Raymarine multicast
|
|
# (would need NMEA GGA sentence with altitude field)
|
|
self._set_value('/Altitude', None)
|
|
|
|
# Number of satellites not available from Raymarine
|
|
self._set_value('/NrOfSatellites', None)
|
|
|
|
else:
|
|
# No GPS fix or stale data
|
|
self._set_value('/Fix', 0)
|
|
self._set_value('/Position/Latitude', None)
|
|
self._set_value('/Position/Longitude', None)
|
|
self._set_value('/Course', None)
|
|
self._set_value('/Speed', None)
|
|
self._set_value('/Altitude', None)
|
|
self._set_value('/NrOfSatellites', None)
|
|
|
|
# Update connection status
|
|
self.set_connected(not is_stale and has_position)
|