Files
venus/axiom-nmea/raymarine_nmea/venus_dbus/meteo.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

152 lines
5.2 KiB
Python

"""
Meteo (Weather) D-Bus service for Venus OS.
Publishes wind and environmental data to the Venus OS D-Bus
using the com.victronenergy.meteo service type.
D-Bus paths:
/WindDirection - True wind direction in degrees (0-360)
/WindSpeed - True wind speed in m/s (Venus OS requirement)
/ExternalTemperature - Air temperature in Celsius
/CellTemperature - Not used (panel temperature for solar)
/Irradiance - Not used (solar irradiance)
"""
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 MeteoService(VeDbusServiceBase):
"""Meteo (Weather) D-Bus service for Venus OS.
Publishes wind direction, wind speed, and temperature from
Raymarine sensors to the Venus OS D-Bus.
Example:
sensor_data = SensorData()
meteo_service = MeteoService(sensor_data)
meteo_service.register()
# In update loop:
meteo_service.update()
"""
service_type = "meteo"
product_name = "Weather"
product_id = 0xA141 # Custom product ID for Raymarine Meteo
# Maximum age in seconds before 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 Meteo service.
Args:
sensor_data: SensorData instance to read values from
device_instance: Unique instance number (default: 0)
custom_name: Optional custom display name
"""
super().__init__(
device_instance=device_instance,
connection="Raymarine LightHouse Weather",
custom_name=custom_name,
)
self._sensor_data = sensor_data
def _get_paths(self) -> Dict[str, Dict[str, Any]]:
"""Return meteo-specific D-Bus paths."""
return {
# Standard meteo paths
'/WindDirection': {'initial': None},
'/WindSpeed': {'initial': None},
'/ExternalTemperature': {'initial': None},
'/CellTemperature': {'initial': None},
'/Irradiance': {'initial': None},
'/ErrorCode': {'initial': 0},
# Extended paths for apparent wind
# These may not be recognized by all Venus OS components
# but are useful for custom dashboards
'/ApparentWindAngle': {'initial': None},
'/ApparentWindSpeed': {'initial': None},
# Barometric pressure (custom extension)
'/Pressure': {'initial': None},
}
def _update(self) -> None:
"""Update meteo values from sensor data."""
data = self._sensor_data
# Check data freshness
wind_stale = data.is_stale('wind', self.MAX_DATA_AGE)
temp_stale = data.is_stale('temp', self.MAX_DATA_AGE)
pressure_stale = data.is_stale('pressure', self.MAX_DATA_AGE)
# True wind direction (degrees 0-360)
if not wind_stale and data.twd_deg is not None:
self._set_value('/WindDirection', round(data.twd_deg, 1))
else:
self._set_value('/WindDirection', None)
# True wind speed (convert knots to m/s for Venus OS)
if not wind_stale and data.tws_kts is not None:
speed_ms = data.tws_kts * KTS_TO_MS
self._set_value('/WindSpeed', round(speed_ms, 2))
else:
self._set_value('/WindSpeed', None)
# Apparent wind angle (degrees, relative to bow)
if not wind_stale and data.awa_deg is not None:
self._set_value('/ApparentWindAngle', round(data.awa_deg, 1))
else:
self._set_value('/ApparentWindAngle', None)
# Apparent wind speed (convert knots to m/s for Venus OS)
if not wind_stale and data.aws_kts is not None:
speed_ms = data.aws_kts * KTS_TO_MS
self._set_value('/ApparentWindSpeed', round(speed_ms, 2))
else:
self._set_value('/ApparentWindSpeed', None)
# Air temperature (Celsius)
if not temp_stale and data.air_temp_c is not None:
self._set_value('/ExternalTemperature', round(data.air_temp_c, 1))
else:
self._set_value('/ExternalTemperature', None)
# Barometric pressure (convert mbar to hPa - they're equivalent)
if not pressure_stale and data.pressure_mbar is not None:
self._set_value('/Pressure', round(data.pressure_mbar, 1))
else:
self._set_value('/Pressure', None)
# Cell temperature and irradiance are not available
self._set_value('/CellTemperature', None)
self._set_value('/Irradiance', None)
# Error code: 0 = OK
has_any_data = (
(not wind_stale and data.twd_deg is not None) or
(not temp_stale and data.air_temp_c is not None) or
(not pressure_stale and data.pressure_mbar is not None)
)
self._set_value('/ErrorCode', 0 if has_any_data else 1)
# Update connection status
self.set_connected(not wind_stale)