This commit is contained in:
2026-01-29 22:37:59 +03:00
parent 1659970896
commit d4ffce28d5
16 changed files with 1826 additions and 0 deletions

View File

@@ -0,0 +1,173 @@
"""Vehicle state management."""
from dataclasses import dataclass, field
from typing import Dict, Optional, Any
from datetime import datetime
import threading
@dataclass
class PIDValue:
"""Single PID value with metadata.
Attributes:
pid_code: PID code
name: PID name
value: Current value
unit: Unit of measurement
timestamp: When value was last updated
"""
pid_code: int
name: str
value: float
unit: str
timestamp: datetime = field(default_factory=datetime.now)
def __str__(self) -> str:
return f"{self.name}: {self.value:.1f} {self.unit}"
def age_seconds(self) -> float:
"""Get age of value in seconds."""
return (datetime.now() - self.timestamp).total_seconds()
class VehicleState:
"""Thread-safe container for current vehicle state.
Stores latest values for all monitored PIDs with timestamps.
"""
def __init__(self):
self._values: Dict[int, PIDValue] = {}
self._lock = threading.RLock()
self._callbacks: list = []
def update(
self,
pid_code: int,
name: str,
value: float,
unit: str,
) -> None:
"""Update a PID value.
Args:
pid_code: PID code
name: PID name
value: New value
unit: Unit of measurement
"""
with self._lock:
old_value = self._values.get(pid_code)
new_value = PIDValue(
pid_code=pid_code,
name=name,
value=value,
unit=unit,
)
self._values[pid_code] = new_value
for callback in self._callbacks:
try:
callback(new_value, old_value)
except Exception:
pass
def get(self, pid_code: int) -> Optional[PIDValue]:
"""Get current value for a PID.
Args:
pid_code: PID code
Returns:
PIDValue if available, None otherwise
"""
with self._lock:
return self._values.get(pid_code)
def get_all(self) -> Dict[int, PIDValue]:
"""Get all current values.
Returns:
Dictionary of PID code to PIDValue
"""
with self._lock:
return dict(self._values)
def clear(self) -> None:
"""Clear all stored values."""
with self._lock:
self._values.clear()
def add_callback(self, callback) -> None:
"""Add a callback for value updates.
Callback signature: callback(new_value: PIDValue, old_value: Optional[PIDValue])
"""
self._callbacks.append(callback)
def remove_callback(self, callback) -> None:
"""Remove a callback."""
if callback in self._callbacks:
self._callbacks.remove(callback)
@property
def rpm(self) -> Optional[float]:
"""Get current RPM value."""
val = self.get(0x0C)
return val.value if val else None
@property
def speed(self) -> Optional[float]:
"""Get current speed value (km/h)."""
val = self.get(0x0D)
return val.value if val else None
@property
def coolant_temp(self) -> Optional[float]:
"""Get current coolant temperature (°C)."""
val = self.get(0x05)
return val.value if val else None
@property
def throttle(self) -> Optional[float]:
"""Get current throttle position (%)."""
val = self.get(0x11)
return val.value if val else None
@property
def fuel_level(self) -> Optional[float]:
"""Get current fuel level (%)."""
val = self.get(0x2F)
return val.value if val else None
@property
def oil_temp(self) -> Optional[float]:
"""Get current oil temperature (°C)."""
val = self.get(0x5C)
return val.value if val else None
def to_dict(self) -> Dict[str, Any]:
"""Convert state to dictionary."""
with self._lock:
return {
val.name: {
"value": val.value,
"unit": val.unit,
"timestamp": val.timestamp.isoformat(),
}
for val in self._values.values()
}
def __str__(self) -> str:
"""Format state as string."""
with self._lock:
if not self._values:
return "No data"
lines = []
for pid_code in sorted(self._values.keys()):
val = self._values[pid_code]
lines.append(f" {val}")
return "\n".join(lines)