from __future__ import annotations import struct import time from server.telemetry.data import TelemetryData from server.telemetry.parsers.base import BaseParser from utils.logger import get_logger logger = get_logger(__name__) class ForzaParser(BaseParser): FORZA_FORMATS = { "fh4": "Forza Horizon 4", "fh5": "Forza Horizon 5", "fm8": "Forza Motorsport", } def __init__(self, format_id: str = "fh5"): self._format = format_id def game_id(self) -> str: return self._format def parse(self, data: bytes, addr: tuple[str, int]) -> TelemetryData: td = TelemetryData(game_id=self._format, timestamp=time.time()) td.raw = {"raw_hex": data.hex(), "length": len(data), "addr": f"{addr[0]}:{addr[1]}"} if len(data) < 312: logger.warning("Forza data too short: %d bytes", len(data)) return td try: td.raw["is_race_on"] = struct.unpack_from(" str: return "acc" def parse(self, data: bytes, addr: tuple[str, int]) -> TelemetryData: td = TelemetryData(game_id="acc", timestamp=time.time()) td.raw = {"raw_hex": data.hex(), "length": len(data), "addr": f"{addr[0]}:{addr[1]}"} if len(data) < 200: logger.warning("ACC data too short: %d bytes", len(data)) return td try: td.speed_kmh = struct.unpack_from(" str: return "f1" def parse(self, data: bytes, addr: tuple[str, int]) -> TelemetryData: td = TelemetryData(game_id="f1", timestamp=time.time()) td.raw = {"raw_hex": data.hex(), "length": len(data), "addr": f"{addr[0]}:{addr[1]}"} if len(data) < 1289: logger.warning("F1 data too short: %d bytes", len(data)) return td try: td.speed_kmh = struct.unpack_from(" str: return "iracing" def parse(self, data: bytes, addr: tuple[str, int]) -> TelemetryData: td = TelemetryData(game_id="iracing", timestamp=time.time()) td.raw = {"raw_hex": data.hex(), "length": len(data), "addr": f"{addr[0]}:{addr[1]}"} if len(data) < 100: logger.warning("iRacing data too short: %d bytes", len(data)) return td try: td.speed_mph = struct.unpack_from(" str: return "ac" def parse(self, data: bytes, addr: tuple[str, int]) -> TelemetryData: td = TelemetryData(game_id="ac", timestamp=time.time()) td.raw = {"raw_hex": data.hex(), "length": len(data), "addr": f"{addr[0]}:{addr[1]}"} try: parts = data.decode("utf-8", errors="replace").rstrip("\r\n").split("\t") if len(parts) < 10: return td td.speed_kmh = float(parts[0]) td.speed_mph = td.speed_kmh * 0.621371 td.rpm = float(parts[1]) td.gear = int(float(parts[2])) td.throttle = float(parts[3]) td.brake = float(parts[4]) td.steering = float(parts[5]) td.fuel = float(parts[6]) td.raw.update({ "speed_kmh": td.speed_kmh, "rpm": td.rpm, "gear": td.gear, }) except Exception as e: logger.error("AC parse error: %s", e) return td PARSER_MAP: dict[str, type[BaseParser]] = { "forza": ForzaParser, "ac": ACParser, "acc": ACCParser, "f1": F1Parser, "iracing": IRacingParser, }