from __future__ import annotations import queue import threading import time from typing import Optional from .base import LinInterface, LinFrame class MockBabyLinInterface(LinInterface): """A mock LIN interface that echoes frames and synthesizes responses. Useful for local development without hardware. Thread-safe. """ def __init__(self, bitrate: int = 19200, channel: int = 1) -> None: self.bitrate = bitrate self.channel = channel self._rx: "queue.Queue[LinFrame]" = queue.Queue() self._lock = threading.RLock() self._connected = False def connect(self) -> None: with self._lock: self._connected = True def disconnect(self) -> None: with self._lock: self._connected = False # drain queue try: while True: self._rx.get_nowait() except queue.Empty: pass def send(self, frame: LinFrame) -> None: if not self._connected: raise RuntimeError("Mock interface not connected") # echo back the frame as a received event self._rx.put(frame) def receive(self, id: Optional[int] = None, timeout: float = 1.0) -> Optional[LinFrame]: if not self._connected: raise RuntimeError("Mock interface not connected") deadline = time.time() + max(0.0, timeout) while time.time() < deadline: try: frm = self._rx.get(timeout=max(0.0, deadline - time.time())) if id is None or frm.id == id: return frm # not matching, requeue tail-safe self._rx.put(frm) except queue.Empty: break return None def request(self, id: int, length: int, timeout: float = 1.0) -> Optional[LinFrame]: if not self._connected: raise RuntimeError("Mock interface not connected") # synthesize a deterministic response payload of requested length payload = bytes((id + i) & 0xFF for i in range(max(0, min(8, length)))) frm = LinFrame(id=id, data=payload) self._rx.put(frm) return self.receive(id=id, timeout=timeout) def flush(self) -> None: while not self._rx.empty(): try: self._rx.get_nowait() except queue.Empty: # pragma: no cover - race guard break