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
216 lines
6.0 KiB
Python
216 lines
6.0 KiB
Python
"""
|
|
Transducer measurement NMEA sentences.
|
|
|
|
XDR - Transducer Measurements (generic)
|
|
|
|
Used for tanks, batteries, and other sensor data that doesn't have
|
|
a dedicated NMEA sentence type.
|
|
"""
|
|
|
|
from typing import Optional, List, Tuple
|
|
|
|
from ..sentence import NMEASentence
|
|
|
|
|
|
class XDRSentence(NMEASentence):
|
|
"""XDR - Transducer Measurements.
|
|
|
|
Format:
|
|
$IIXDR,T,D.D,U,N,T,D.D,U,N,...*CC
|
|
|
|
Each transducer reading has 4 fields:
|
|
1. Transducer type
|
|
2. Data value
|
|
3. Units
|
|
4. Transducer name/ID
|
|
|
|
Transducer Types:
|
|
A = Angular displacement (degrees)
|
|
C = Temperature (Celsius)
|
|
D = Depth (meters)
|
|
F = Frequency (Hz)
|
|
H = Humidity (percent)
|
|
I = Current (amps)
|
|
N = Force (Newtons)
|
|
P = Pressure (Pascals)
|
|
R = Flow Rate (liters/second)
|
|
S = Salinity (ppt)
|
|
T = Tachometer (RPM)
|
|
U = Volume (liters)
|
|
V = Voltage (volts)
|
|
G = Generic (no units)
|
|
|
|
Tank Level Examples:
|
|
$IIXDR,V,75.2,P,FUEL1,V,68.1,P,FUEL2*XX
|
|
(P = percent for tank levels)
|
|
|
|
Battery Examples:
|
|
$IIXDR,U,26.3,V,HOUSE1,U,27.2,V,HOUSE2*XX
|
|
(U is sometimes used for voltage, or V for volts unit)
|
|
|
|
Note:
|
|
XDR can contain multiple transducer readings in one sentence.
|
|
Maximum sentence length is 82 characters, so multiple XDR
|
|
sentences may be needed for many sensors.
|
|
"""
|
|
|
|
talker_id = "II"
|
|
sentence_type = "XDR"
|
|
|
|
def __init__(self, readings: Optional[List[Tuple[str, float, str, str]]] = None):
|
|
"""Initialize XDR sentence.
|
|
|
|
Args:
|
|
readings: List of (type, value, unit, name) tuples
|
|
Example: [("V", 75.2, "P", "FUEL1"), ("V", 26.3, "V", "HOUSE1")]
|
|
"""
|
|
self.readings = readings or []
|
|
|
|
def add_reading(
|
|
self,
|
|
transducer_type: str,
|
|
value: float,
|
|
unit: str,
|
|
name: str
|
|
) -> None:
|
|
"""Add a transducer reading.
|
|
|
|
Args:
|
|
transducer_type: Type code (e.g., "V" for volume, "U" for voltage)
|
|
value: Numeric value
|
|
unit: Unit code (e.g., "P" for percent, "V" for volts)
|
|
name: Transducer name/ID
|
|
"""
|
|
self.readings.append((transducer_type, value, unit, name))
|
|
|
|
def format_fields(self) -> Optional[str]:
|
|
"""Format XDR fields."""
|
|
if not self.readings:
|
|
return None
|
|
|
|
parts = []
|
|
for trans_type, value, unit, name in self.readings:
|
|
# Clean the name to be NMEA-safe (no commas, asterisks)
|
|
safe_name = name.replace(",", "_").replace("*", "_")
|
|
# Use 4 decimal places for pressure (bar ~1.0209), 1 for others
|
|
if trans_type == "P" and unit == "B":
|
|
parts.append(f"{trans_type},{value:.4f},{unit},{safe_name}")
|
|
else:
|
|
parts.append(f"{trans_type},{value:.1f},{unit},{safe_name}")
|
|
|
|
return ",".join(parts)
|
|
|
|
@classmethod
|
|
def for_tank(
|
|
cls,
|
|
tank_id: int,
|
|
level_pct: float,
|
|
name: Optional[str] = None
|
|
) -> 'XDRSentence':
|
|
"""Create XDR sentence for a tank level.
|
|
|
|
Args:
|
|
tank_id: Tank ID number
|
|
level_pct: Tank level in percent (0-100)
|
|
name: Tank name (uses ID if not provided)
|
|
|
|
Returns:
|
|
XDRSentence instance
|
|
"""
|
|
tank_name = name or f"TANK{tank_id}"
|
|
xdr = cls()
|
|
xdr.add_reading("V", level_pct, "P", tank_name)
|
|
return xdr
|
|
|
|
@classmethod
|
|
def for_battery(
|
|
cls,
|
|
battery_id: int,
|
|
voltage: float,
|
|
name: Optional[str] = None
|
|
) -> 'XDRSentence':
|
|
"""Create XDR sentence for a battery voltage.
|
|
|
|
Args:
|
|
battery_id: Battery ID number
|
|
voltage: Battery voltage in volts
|
|
name: Battery name (uses ID if not provided)
|
|
|
|
Returns:
|
|
XDRSentence instance
|
|
"""
|
|
battery_name = name or f"BATT{battery_id}"
|
|
xdr = cls()
|
|
xdr.add_reading("U", voltage, "V", battery_name)
|
|
return xdr
|
|
|
|
@classmethod
|
|
def for_tanks(
|
|
cls,
|
|
tanks: dict,
|
|
names: Optional[dict] = None
|
|
) -> 'XDRSentence':
|
|
"""Create XDR sentence for multiple tanks.
|
|
|
|
Args:
|
|
tanks: Dict of tank_id -> level_pct
|
|
names: Optional dict of tank_id -> name
|
|
|
|
Returns:
|
|
XDRSentence instance
|
|
"""
|
|
names = names or {}
|
|
xdr = cls()
|
|
for tank_id, level in sorted(tanks.items()):
|
|
tank_name = names.get(tank_id, f"TANK{tank_id}")
|
|
xdr.add_reading("V", level, "P", tank_name)
|
|
return xdr
|
|
|
|
@classmethod
|
|
def for_batteries(
|
|
cls,
|
|
batteries: dict,
|
|
names: Optional[dict] = None
|
|
) -> 'XDRSentence':
|
|
"""Create XDR sentence for multiple batteries.
|
|
|
|
Args:
|
|
batteries: Dict of battery_id -> voltage
|
|
names: Optional dict of battery_id -> name
|
|
|
|
Returns:
|
|
XDRSentence instance
|
|
"""
|
|
names = names or {}
|
|
xdr = cls()
|
|
for battery_id, voltage in sorted(batteries.items()):
|
|
battery_name = names.get(battery_id, f"BATT{battery_id}")
|
|
xdr.add_reading("U", voltage, "V", battery_name)
|
|
return xdr
|
|
|
|
@classmethod
|
|
def for_pressure(
|
|
cls,
|
|
pressure_mbar: float,
|
|
name: str = "Barometer"
|
|
) -> 'XDRSentence':
|
|
"""Create XDR sentence for barometric pressure.
|
|
|
|
Args:
|
|
pressure_mbar: Pressure in millibars (hPa)
|
|
name: Sensor name (default: "Barometer")
|
|
|
|
Returns:
|
|
XDRSentence instance
|
|
|
|
Note:
|
|
Pressure is output in bar (1 bar = 1000 mbar) as per NMEA convention.
|
|
Example: 1020.9 mbar = 1.0209 bar
|
|
Output: $IIXDR,P,1.0209,B,Barometer*XX
|
|
"""
|
|
xdr = cls()
|
|
# Convert mbar to bar (NMEA standard unit)
|
|
pressure_bar = pressure_mbar / 1000.0
|
|
xdr.add_reading("P", pressure_bar, "B", name)
|
|
return xdr
|