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
152 lines
5.2 KiB
Python
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)
|