Skip to content

Instantly share code, notes, and snippets.

@mfreeze77
Last active March 16, 2025 11:26
Show Gist options
  • Save mfreeze77/dd45a04ac2715699d64bbff886a1bbab to your computer and use it in GitHub Desktop.
Save mfreeze77/dd45a04ac2715699d64bbff886a1bbab to your computer and use it in GitHub Desktop.

README: iRacing Telemetry Integration with OpenAI Realtime API (Beta)

1. Overview

This repository demonstrates how to capture and utilize iRacing telemetry data and send it to the OpenAI Realtime API (currently in Beta) for low-latency, multi-modal AI interactions. Instead of the older ChatCompletion endpoints, this guide uses GPT-4o Realtime models via WebRTC or WebSockets, enabling advanced features like audio output, function calling, voice activity detection, and more.

Key Objectives:

  • Gather iRacing telemetry via iRSDK.
  • Connect to OpenAI Realtime API using either WebRTC or WebSockets.
  • Send real-time telemetry events to GPT-4o or GPT-4o-mini models.
  • Receive AI responses in text and/or audio form (e.g., voice spotter, real-time suggestions).
  • Potentially incorporate function-calling for advanced automation.

2. iRacing Telemetry Basics

2.1 Prerequisites for iRacing

  1. A valid iRacing subscription installed on Windows.
  2. Telemetry enabled in iRacing (under Options → Session → Telemetry).

2.2 iRSDK for Python

  • The iRSDK Python bindings let you interact with iRacing's memory-mapped telemetry in real time.
  • Telemetry is only available while you're in an active driving session, not in the main menu.

2.3 Important Telemetry Channels

Below is a non-exhaustive list of data iRacing typically provides. Actual availability may vary by car/track/session:

  • Speed (m/s or mph)
  • Throttle (0.0 to 1.0)
  • Brake (0.0 to 1.0)
  • SteeringWheelAngle (radians or degrees)
  • RPM (engine revolutions per minute)
  • Gear (integer: -1 for reverse, 0 for neutral, 1-n for forward)
  • FuelLevel (liters or gallons)
  • TirePressure, TireTemp, TireWear
  • LapCurrentLapTime
  • LapDistPct (percentage of lap completed, 0.0 to 1.0)
  • Damage (aero, suspension, or engine damage states)
  • SuspensionDeflection
  • LatAccel / LonAccel (in G)
  • SessionFlags (bitfield for green, yellow, etc.)
  • Weather (track temperature, air temperature, etc.)
  • CarIdxLapDistPct (for other cars on track)
  • TrackSurface (grip level or rubber usage)

Check the official iRacing SDK docs for the latest channel names and data types.


3. Environment & Installation

3.1 Python Environment

  • Windows + Python 3.7+

  • Install irsdk to read iRacing telemetry:

    pip install irsdk
  • Install a WebSocket library if you plan to use the Realtime WebSocket approach. For Python, one common choice is websocket-client:

    pip install websocket-client
  • If you use Node.js for ephemeral token generation or a server, that's also possible (see sections below).

3.2 OpenAI Realtime API Access

  1. Realtime API Beta: Ensure you're enrolled in the Beta program. This approach is different from the standard ChatCompletion endpoint.
  2. API Keys:
    • Standard API Key: Created via your OpenAI account. Must be stored securely (server-side).
    • Ephemeral API Key: For WebRTC or public-facing clients. Ephemeral keys are minted on your server by calling the Realtime Sessions REST API, and passed to the client. They expire quickly and limit exposure.

4. Detailed Implementation

4.1 Capturing iRacing Telemetry (Python Example)

Below is a comprehensive Python script to capture all major telemetry data from iRacing:

import irsdk
import time
import json

# Initialize iRacing SDK
ir = irsdk.IRSDK()
ir.startup()

def get_all_telemetry():
    """
    Capture every critical telemetry parameter from iRacing.
    Returns a dictionary containing the data or None if iRacing is not initialized.
    """
    if not ir.is_initialized:
        print("iRacing not initialized. Are you in an active session?")
        return None
    
    player_idx = ir['PlayerCarIdx']
    
    data = {
        "speed": ir['Speed'],
        "throttle": ir['Throttle'],
        "brake": ir['Brake'],
        "rpm": ir['RPM'],
        "gear": ir['Gear'],
        "steering_wheel_angle": ir['SteeringWheelAngle'],
        "tire_pressure": ir['TirePressure'],
        "tire_temperature": ir['TireTemp'],
        "tire_wear": ir['TireWear'],
        "fuel_level": ir['FuelLevel'],
        "lap_current_time": ir['LapCurrentLapTime'],
        "weather": {
            "track_temp": ir['TrackTempCrew'],
            "air_temp": ir['AirTemp']
        },
        "damage": ir['CarDamage'],
        "suspension_deflection": ir['SuspensionDeflection'],
        "lat_accel": ir['LatAccel'],
        "lon_accel": ir['LongAccel'],
        "track_surface": ir['TrackSurface'],
        "car_status": ir['SessionFlags']
    }
    
    if player_idx is not None:
        data["position_pct"] = ir['CarIdxLapDistPct' + str(player_idx)]
    
    return data

def export_to_json(data, filename='telemetry.json'):
    """Optional: Export telemetry to a JSON file."""
    with open(filename, 'w') as f:
        json.dump(data, f, indent=4)

def main():
    try:
        while True:
            telemetry = get_all_telemetry()
            if telemetry:
                # Here is where you'd send to the Realtime API (see next sections)
                print(telemetry)
                
                # Optionally export to JSON
                # export_to_json(telemetry)
            time.sleep(1)
    except KeyboardInterrupt:
        print("Telemetry capture stopped.")

if __name__ == "__main__":
    main()

Note: We'll integrate this telemetry capture logic with the Realtime API calls in later sections.


5. Using the Realtime API

OpenAI offers two ways to connect to GPT-4o Realtime models:

  1. WebRTC: Recommended for client-side applications (like in-browser), especially if you want direct audio input from a user's mic or play audio from the model.
  2. WebSockets: Great for server-to-server or more controlled environments. You can still send audio data if you wish but must handle encoding/decoding yourself.

5.1 Realtime Models

The main GPT-4o Realtime models are:

  • gpt-4o-realtime-preview-2024-12-17
  • gpt-4o-mini-realtime-preview-2024-12-17

Model snapshot versions are listed in the OpenAI docs.


6. Connecting via WebRTC (Client-Side)

When you want to capture user audio (like a microphone) in the browser and play back the model's voice responses, WebRTC is ideal. Below is an abridged version of the official docs.

6.1 WebRTC Connection Flow

  1. The browser requests an ephemeral API key from your server (which holds your standard OpenAI API key securely).
  2. The server calls POST /v1/realtime/sessions with your standard key to generate a short-lived ephemeral key.
  3. The browser uses that ephemeral key to establish a WebRTC peer connection to https://api.openai.com/v1/realtime.

Important: Never expose your standard API key in the client. Use ephemeral keys for in-browser usage.

6.2 Example Browser Code

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Realtime API WebRTC Example</title>
  </head>
  <body>
    <button id="start">Start Connection</button>
    <script>
      document.getElementById('start').addEventListener('click', async () => {
        // 1. Fetch ephemeral key from your server
        const tokenResponse = await fetch("/session");
        const data = await tokenResponse.json();
        const EPHEMERAL_KEY = data.client_secret.value;

        // 2. Create RTCPeerConnection
        const pc = new RTCPeerConnection();

        // (Optional) add an <audio> element to stream model output
        const audioEl = document.createElement("audio");
        audioEl.autoplay = true;
        pc.ontrack = e => audioEl.srcObject = e.streams[0];
        document.body.appendChild(audioEl);

        // 3. Grab microphone input
        const ms = await navigator.mediaDevices.getUserMedia({ audio: true });
        pc.addTrack(ms.getTracks()[0]);

        // 4. Create data channel for realtime events
        const dc = pc.createDataChannel("oai-events");
        dc.addEventListener("message", (e) => {
          // handle server-sent events
          console.log("Server event:", e.data);
        });

        // 5. Create & send offer
        const offer = await pc.createOffer();
        await pc.setLocalDescription(offer);

        const baseUrl = "https://api.openai.com/v1/realtime";
        const model = "gpt-4o-realtime-preview-2024-12-17";

        const sdpResponse = await fetch(`${baseUrl}?model=${model}`, {
          method: "POST",
          body: offer.sdp,
          headers: {
            Authorization: `Bearer ${EPHEMERAL_KEY}`,
            "Content-Type": "application/sdp"
          },
        });

        const remoteSDP = await sdpResponse.text();
        await pc.setRemoteDescription({ type: "answer", sdp: remoteSDP });
        console.log("WebRTC connection established!");
      });
    </script>
  </body>
</html>

6.3 Creating an Ephemeral Token (Server-Side)

On your server (Node.js, for example):

import express from "express";
import fetch from "node-fetch";

const app = express();
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;

app.get("/session", async (req, res) => {
  try {
    const r = await fetch("https://api.openai.com/v1/realtime/sessions", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${OPENAI_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        model: "gpt-4o-realtime-preview-2024-12-17",
        voice: "verse", // optional voice choice
      }),
    });
    const data = await r.json();
    res.json(data);
  } catch (err) {
    console.error(err);
    res.status(500).send("Error creating ephemeral token");
  }
});

app.listen(3000, () => {
  console.log("Server running on http://localhost:3000");
});

7. Connecting via WebSockets (Server-to-Server)

If you want to handle everything on a secure server (like a Python service that captures iRacing telemetry), then forward those events to GPT-4o Realtime, WebSockets might be more convenient. Here's an outline:

  • You'll connect to wss://api.openai.com/v1/realtime?model=MODEL_NAME
  • Include the header:
    • Authorization: Bearer YOUR_API_KEY
    • OpenAI-Beta: realtime=v1 (during the beta period)

7.1 Python Example using websocket-client

Below is a comprehensive Python script that:

  1. Captures detailed iRacing telemetry with irsdk.
  2. Connects to the Realtime API over WebSocket (using a standard API key).
  3. Sends telemetry events with each telemetry cycle.
import os
import json
import time
import irsdk
import websocket  # from "websocket-client" package
import threading

OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")

# WebSocket Realtime endpoint
REALTIME_URL = "wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-12-17"

# Build the headers required for Beta
HEADERS = [
    "Authorization: Bearer " + OPENAI_API_KEY,
    "OpenAI-Beta: realtime=v1"
]

# Initialize iRacing SDK
ir = irsdk.IRSDK()
ir.startup()

def get_all_telemetry():
    """
    Capture every critical telemetry parameter from iRacing.
    Returns a dictionary containing the data or None if iRacing is not initialized.
    """
    if not ir.is_initialized:
        print("iRacing not initialized. Are you in an active session?")
        return None
    
    player_idx = ir['PlayerCarIdx']
    
    data = {
        "speed": ir['Speed'],
        "throttle": ir['Throttle'],
        "brake": ir['Brake'],
        "rpm": ir['RPM'],
        "gear": ir['Gear'],
        "steering_wheel_angle": ir['SteeringWheelAngle'],
        "tire_pressure": ir['TirePressure'],
        "tire_temperature": ir['TireTemp'],
        "tire_wear": ir['TireWear'],
        "fuel_level": ir['FuelLevel'],
        "lap_current_time": ir['LapCurrentLapTime'],
        "weather": {
            "track_temp": ir['TrackTempCrew'],
            "air_temp": ir['AirTemp']
        },
        "damage": ir['CarDamage'],
        "suspension_deflection": ir['SuspensionDeflection'],
        "lat_accel": ir['LatAccel'],
        "lon_accel": ir['LongAccel'],
        "track_surface": ir['TrackSurface'],
        "car_status": ir['SessionFlags']
    }
    
    if player_idx is not None:
        data["position_pct"] = ir['CarIdxLapDistPct' + str(player_idx)]
    
    return data

def on_open(ws):
    print("Connected to Realtime API via WebSocket.")

    # Example: We can send a "session.update" event to adjust config
    session_event = {
        "type": "session.update",
        "session": {
            "instructions": "You are an AI assisting with iRacing telemetry analysis."
        }
    }
    ws.send(json.dumps(session_event))

def on_message(ws, message):
    # Parse server-sent events from Realtime API
    data = json.loads(message)
    print("Realtime Event:", json.dumps(data, indent=2))

def on_error(ws, error):
    print("Error:", error)

def on_close(ws, close_status_code, close_msg):
    print("WebSocket closed.", close_status_code, close_msg)

def main():
    # Connect to Realtime API
    ws = websocket.WebSocketApp(
        REALTIME_URL,
        header=HEADERS,
        on_open=on_open,
        on_message=on_message,
        on_error=on_error,
        on_close=on_close
    )

    # We'll run the WebSocket in a separate thread
    # Then in the main thread, we can capture iRacing telemetry
    wst = threading.Thread(target=ws.run_forever)
    wst.daemon = True
    wst.start()

    # Wait a bit for the connection to establish
    time.sleep(2)

    try:
        while True:
            if not ir.is_initialized:
                print("iRacing not initialized or not in session.")
            else:
                # Gather telemetry
                telemetry = get_all_telemetry()
                
                if telemetry:
                    # Example: We'll send a "conversation.item.create" event
                    # so the model can "see" the telemetry as user input
                    event = {
                        "type": "conversation.item.create",
                        "item": {
                            "type": "message",
                            "role": "user",
                            "content": [
                                {
                                    "type": "input_text",
                                    "text": f"Telemetry update: {json.dumps(telemetry, indent=2)}"
                                }
                            ]
                        }
                    }
                    ws.send(json.dumps(event))

                    # Then request the model to respond with text and/or audio
                    response_event = {
                        "type": "response.create",
                        "response": {
                            "modalities": ["text"],
                            "instructions": "Please provide brief insights about driving performance, tire wear, and fuel strategy based on the telemetry data."
                        }
                    }
                    ws.send(json.dumps(response_event))
            time.sleep(5)
    except KeyboardInterrupt:
        print("Stopping iRacing telemetry streaming...")
    finally:
        ws.close()

if __name__ == "__main__":
    main()

Key Points:

  • We connect with wss://api.openai.com/v1/realtime + ?model=gpt-4o-realtime-preview-2024-12-17.
  • We set the headers to include Authorization: Bearer <API_KEY> and OpenAI-Beta: realtime=v1.
  • In on_message(), we receive all the server events from GPT-4o (e.g., partial text outputs, audio deltas, function calls, etc.).
  • We can keep sending telemetry or messages in a loop, or upon certain conditions (like new laps starting).

7.2 Advanced Telemetry Streaming with JSON Enforcement

For more structured responses, you can request the AI to respond in JSON format, which is very useful for integration with dashboards or overlays:

import os
import json
import time
import irsdk
import websocket
import threading

OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
REALTIME_URL = "wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-12-17"
HEADERS = [
    "Authorization: Bearer " + OPENAI_API_KEY,
    "OpenAI-Beta: realtime=v1"
]

# Initialize iRacing SDK
ir = irsdk.IRSDK()
ir.startup()

def get_all_telemetry():
    """
    Capture every critical telemetry parameter from iRacing.
    Returns a dictionary containing the data or None if iRacing is not initialized.
    """
    if not ir.is_initialized:
        print("iRacing not initialized. Are you in an active session?")
        return None
    
    player_idx = ir['PlayerCarIdx']
    
    data = {
        "speed": ir['Speed'],
        "throttle": ir['Throttle'],
        "brake": ir['Brake'],
        "rpm": ir['RPM'],
        "gear": ir['Gear'],
        "steering_wheel_angle": ir['SteeringWheelAngle'],
        "tire_pressure": ir['TirePressure'],
        "tire_temperature": ir['TireTemp'],
        "tire_wear": ir['TireWear'],
        "fuel_level": ir['FuelLevel'],
        "lap_current_time": ir['LapCurrentLapTime'],
        "weather": {
            "track_temp": ir['TrackTempCrew'],
            "air_temp": ir['AirTemp']
        },
        "damage": ir['CarDamage'],
        "suspension_deflection": ir['SuspensionDeflection'],
        "lat_accel": ir['LatAccel'],
        "lon_accel": ir['LongAccel'],
        "track_surface": ir['TrackSurface'],
        "car_status": ir['SessionFlags']
    }
    
    if player_idx is not None:
        data["position_pct"] = ir['CarIdxLapDistPct' + str(player_idx)]
    
    return data

def on_open(ws):
    print("Connected to Realtime API via WebSocket.")
    
    # Set up a JSON-enforcing system message
    session_event = {
        "type": "session.update",
        "session": {
            "instructions": (
                "You are an AI assistant analyzing iRacing telemetry data. "
                "Given the telemetry data, please return a valid JSON object with the following keys: "
                "'analysis', 'recommendation', and 'warnings'. "
                "Do not include any extra text outside the JSON."
            )
        }
    }
    ws.send(json.dumps(session_event))

def on_message(ws, message):
    # Parse server-sent events from Realtime API
    data = json.loads(message)
    
    # If this is a text delta, try to accumulate and parse as JSON
    if 'type' in data and data['type'] == 'response.text.delta':
        text_content = data.get('delta', {}).get('text', '')
        print(text_content, end='')
    
    # If response is done, try to parse the accumulated response
    if 'type' in data and data['type'] == 'response.done':
        print("\nResponse complete")

def on_error(ws, error):
    print("Error:", error)

def on_close(ws, close_status_code, close_msg):
    print("WebSocket closed.", close_status_code, close_msg)

def main():
    # Connect to Realtime API
    ws = websocket.WebSocketApp(
        REALTIME_URL,
        header=HEADERS,
        on_open=on_open,
        on_message=on_message,
        on_error=on_error,
        on_close=on_close
    )

    # Run WebSocket in a separate thread
    wst = threading.Thread(target=ws.run_forever)
    wst.daemon = True
    wst.start()

    # Wait for connection to establish
    time.sleep(2)

    try:
        while True:
            if not ir.is_initialized:
                print("iRacing not initialized or not in session.")
            else:
                telemetry = get_all_telemetry()
                
                if telemetry:
                    # Send telemetry as a conversation item
                    event = {
                        "type": "conversation.item.create",
                        "item": {
                            "type": "message",
                            "role": "user",
                            "content": [
                                {
                                    "type": "input_text",
                                    "text": f"Telemetry update: {json.dumps(telemetry, indent=2)}"
                                }
                            ]
                        }
                    }
                    ws.send(json.dumps(event))

                    # Request structured JSON response
                    response_event = {
                        "type": "response.create",
                        "response": {
                            "modalities": ["text"],
                            "instructions": "Analyze the telemetry and respond with JSON containing analysis, recommendations, and warnings."
                        }
                    }
                    ws.send(json.dumps(response_event))
            time.sleep(5)
    except KeyboardInterrupt:
        print("Stopping iRacing telemetry streaming...")
    finally:
        ws.close()

if __name__ == "__main__":
    main()

8. Sending and Receiving Realtime Events

Once you have a live WebRTC or WebSocket connection:

  1. Client-Sent Events (e.g., conversation.item.create, response.create, session.update) let you feed data into the model or change session parameters.
  2. Server-Sent Events (like response.text.delta, response.audio.delta, response.done) deliver streaming text/audio or function-call instructions back to you.

8.1 Example: Generating Model Responses from Telemetry

  • Capture telemetry every few seconds in iRacing.
  • Send it to GPT-4o with a conversation.item.create (role=user).
  • Initiate a new response with response.create.
  • Listen for response.text.delta or response.done to retrieve the final text.
  • For voice, you can use WebRTC or send/receive audio in base64 over WebSockets using response.audio.delta.

8.2 VAD, Function Calls, and More

Refer to the official Realtime docs for:

  • Voice Activity Detection (VAD): Automatic detection of user speech.
  • Function Calling: Provide the model a list of "functions" it can call with JSON arguments. The model can request an external function at runtime.

9. Integration with Custom Dashboards, Overlays, or Voice

9.1 Dashboard Tools

  • Tools like SimHub or RaceLabApps can read your generated JSON or call a local HTTP endpoint to render data on screen.
  • Combine the AI's commentary or recommendations (parsed from JSON) with raw telemetry for a more advanced display.

9.2 Web Overlays

  • A small Flask or Node.js server can broadcast your AI data via WebSockets to a browser overlay.
  • If you're comfortable with front-end frameworks, you can build fully interactive dashboards.

9.3 Voice Spotter

  • Use pyttsx3 or an online TTS service to convert the AI's real-time commentary into audio.
  • For structured responses, read the AI's "analysis" or "recommendation" keys and speak them aloud.

Example code for a simple TTS engine integration:

import pyttsx3
import json

# Initialize the TTS engine
engine = pyttsx3.init()

# In your WebSocket on_message handler:
def on_message(ws, message):
    data = json.loads(message)
    
    if 'type' in data and data['type'] == 'response.text.delta':
        text_content = data.get('delta', {}).get('text', '')
        print(text_content, end='')
        
        # For important messages, convert to speech
        if "warning" in text_content.lower() or "caution" in text_content.lower():
            engine.say(text_content)
            engine.runAndWait()

10. Usage Guide

  1. Start iRacing and load a session.
  2. Run Your Realtime Integration:
    • WebRTC approach:
      • Start your ephemeral key server (Node.js or similar).
      • Open your browser page that sets up the RTCPeerConnection with https://api.openai.com/v1/realtime.
    • WebSocket approach:
      • Run the Python (or Node.js) code that uses wss://api.openai.com/v1/realtime?model=... plus your standard API key server-side.
  3. Observe AI Responses in text or audio.
  4. Add Voice if desired, by adding local audio tracks (WebRTC) or sending base64-encoded audio (WebSockets).
  5. Handle or store model commentary, suggestions, or function calls.

11. Troubleshooting & Tips

  • Invalid Key or 401: Check that you're passing the correct key in the Authorization header.
  • No Telemetry: Confirm iRacing is actually in session, and irsdk.IRSDK().startup() is returning True.
  • Too Slow or Expensive: Realtime models are more resource-intensive. Tweak how often you send data.
  • Audio Output: If using WebRTC in the browser, ensure your domain is secure (HTTPS). Browsers often block mic/camera streams on non-HTTPS origins.
  • Missing Telemetry Values: Some telemetry channels might return None if they're not available for your current car or track. Add error handling to manage this gracefully.

12. Contributing

  • Pull Requests: If you improve Realtime integration or add new iRacing data channels, please open a PR.
  • Issues: Use GitHub Issues to report bugs or request features.

13. Legal & Disclaimer

  • iRacing EULA: Certain telemetry uses or modifications may be restricted. Check official policies.
  • OpenAI Realtime Beta: Subject to usage fees and Beta policies.
  • No Warranty: Provided "as is" for educational purposes.

14. iRacing Mechanic: Interactive Setup Advisor

This integration perfectly supports building an application like iRacing Mechanic - an interactive setup advisor that provides real-time, personalized car setup recommendations based on telemetry data and driver feedback.

14.1 Core Functionality

  • Real-Time Interaction: Simply describe how your car feels (e.g., "the car is too unstable in corners" or "the front feels too heavy") and receive tailored recommendations on what to adjust in your setup.
  • Learn as You Go: Not only does the system give you immediate advice, but it also helps you learn how to fine-tune your car setups according to your unique driving style over time.
  • Easy and Accessible: Makes setup tuning straightforward, so you can focus more on enjoying the race and less on guesswork.

14.2 Implementation Example

Here's how to implement this functionality using the Realtime API:

# Add this to your WebSocket implementation
def on_open(ws):
    print("Connected to Realtime API via WebSocket.")
    
    # Set up session instructions specific to iRacing Mechanic
    session_event = {
        "type": "session.update",
        "session": {
            "instructions": (
                "You are iRacing Mechanic, an expert setup engineer for racing simulations. "
                "Analyze both telemetry data and the driver's subjective feedback about how the car feels. "
                "Provide specific, actionable setup adjustments (suspension, aero, tire pressures, etc.) "
                "that address the driver's concerns. Explain the reasoning behind each recommendation "
                "and how it will affect car handling. Keep recommendations practical and within "
                "iRacing's setup adjustment ranges. Format your responses as JSON with 'diagnosis', "
                "'recommendations', and 'explanation' fields."
            )
        }
    }
    ws.send(json.dumps(session_event))

# In your main loop, allow for driver feedback combined with telemetry
def main():
    # ... [WebSocket setup code here]
    
    try:
        while True:
            if not ir.is_initialized:
                print("iRacing not initialized or not in session.")
            else:
                telemetry = get_all_telemetry()
                
                if telemetry:
                    # Periodically prompt for driver feedback
                    driver_feedback = input("How does the car feel? (press Enter to skip): ")
                    
                    if driver_feedback:
                        # Send both telemetry and driver feedback together
                        event = {
                            "type": "conversation.item.create",
                            "item": {
                                "type": "message",
                                "role": "user",
                                "content": [
                                    {
                                        "type": "input_text",
                                        "text": f"Driver feedback: {driver_feedback}\n\nTelemetry data: {json.dumps(telemetry, indent=2)}"
                                    }
                                ]
                            }
                        }
                        ws.send(json.dumps(event))

                        # Request detailed setup recommendations
                        response_event = {
                            "type": "response.create",
                            "response": {
                                "modalities": ["text"],
                                "instructions": "Provide specific setup adjustments based on the driver's feedback and telemetry data."
                            }
                        }
                        ws.send(json.dumps(response_event))
            time.sleep(5)
    except KeyboardInterrupt:
        print("Stopping iRacing Mechanic...")
    finally:
        ws.close()

A. iRacing Mechanics Knowledge Base

For the most effective setup recommendations, implementing a knowledge base is crucial. This serves as Retrieval-Augmented Generation (RAG) content to enhance the model's racing-specific knowledge:

A.1 Database Structure

Create a structured database containing:

knowledge_base/
├── cars/
│   ├── gt3/
│   │   ├── ferrari_488_gt3.json
│   │   ├── bmw_m4_gt3.json
│   │   └── ...
│   ├── nascar/
│   │   ├── next_gen_chevrolet_camaro_zl1.json
│   │   └── ...
│   └── ...
├── tracks/
│   ├── road/
│   │   ├── watkins_glen.json
│   │   ├── spa.json
│   │   └── ...
│   ├── oval/
│   │   ├── daytona.json
│   │   ├── talladega.json
│   │   └── ...
│   └── ...
└── setup_guides/
    ├── ferrari_488_gt3_spa.json
    ├── ferrari_488_gt3_watkins_glen.json
    ├── bmw_m4_gt3_spa.json
    ├── next_gen_chevrolet_camaro_zl1_daytona.json
    └── ...

A.2 Car Data Example

Each car file should contain detailed setup parameters and adjustment ranges:

{
  "name": "Ferrari 488 GT3",
  "class": "GT3",
  "characteristics": {
    "handling": "Neutral with slight understeer tendency",
    "strengths": ["Brake stability", "Traction out of corners"],
    "weaknesses": ["Susceptible to front tire wear", "Sensitive to ride height"]
  },
  "setup_parameters": {
    "suspension": {
      "front_ride_height": {
        "range": [50, 80],
        "unit": "mm",
        "impact": "Lower values increase front downforce but risk bottoming out"
      },
      "rear_ride_height": {
        "range": [70, 110],
        "unit": "mm",
        "impact": "Affects rear stability and aero balance"
      },
      "front_spring_rate": {
        "range": [110000, 180000],
        "unit": "N/m",
        "impact": "Higher values reduce dive under braking but may reduce mechanical grip"
      },
      "rear_spring_rate": {
        "range": [130000, 200000],
        "unit": "N/m",
        "impact": "Higher values reduce squat under acceleration"
      }
    },
    "dampers": {
      "front_bump": {
        "range": [1, 20],
        "impact": "Controls compression speed of front suspension"
      },
      "front_rebound": {
        "range": [1, 20],
        "impact": "Controls extension speed of front suspension"
      }
    },
    "aero": {
      "front_splitter": {
        "range": [1, 8],
        "impact": "Higher values increase front downforce"
      },
      "rear_wing": {
        "range": [1, 15],
        "impact": "Higher values increase rear downforce and drag"
      }
    },
    "tires": {
      "front_pressure": {
        "range": [24.0, 30.0],
        "unit": "psi",
        "impact": "Optimal temperature range 80-85°C"
      },
      "rear_pressure": {
        "range": [24.0, 30.0],
        "unit": "psi",
        "impact": "Optimal temperature range 80-85°C"
      }
    }
  },
  "common_issues": [
    {
      "symptom": "Understeer on corner entry",
      "potential_fixes": [
        {"parameter": "front_splitter", "adjustment": "Increase by 1-2 steps"},
        {"parameter": "front_tire_pressure", "adjustment": "Decrease by 0.5-1.0 psi"}
      ]
    },
    {
      "symptom": "Oversteer on corner exit",
      "potential_fixes": [
        {"parameter": "rear_wing", "adjustment": "Increase by 1-2 steps"},
        {"parameter": "traction_control", "adjustment": "Increase by 1-2 steps"},
        {"parameter": "rear_toe", "adjustment": "Increase for more stability"}
      ]
    }
  ]
}

A.3 Track Data Example

Track files should contain characteristics and setup considerations:

{
  "name": "Spa-Francorchamps",
  "type": "Road",
  "length": "7.004 km",
  "characteristics": {
    "elevation_changes": "Significant",
    "corners": "18 (mix of high, medium, and low speed)",
    "straights": "Multiple long straights including Kemmel"
  },
  "key_sectors": [
    {
      "name": "Eau Rouge/Raidillon",
      "description": "High-speed uphill complex requiring stable aero platform"
    },
    {
      "name": "Pouhon",
      "description": "Fast, long left-hander requiring good front-end grip"
    }
  ],
  "setup_considerations": {
    "aero": "Medium to high downforce to handle elevation changes",
    "suspension": "Softer setup to handle curbs and elevation",
    "gearing": "Balance between top speed on Kemmel and acceleration out of La Source"
  },
  "weather_impacts": {
    "rain": "Extremely challenging, especially at Eau Rouge and Blanchimont",
    "temperature": "Cool temperatures common, affects tire warm-up"
  }
}

A.4 Integration with Realtime API

Update your WebSocket implementation to utilize this knowledge base:

def on_open(ws):
    print("Connected to Realtime API via WebSocket.")
    
    # Load active car and track data based on current session
    car_model = ir['CarPath']  # Get the current car model from iRacing
    track_name = ir['TrackName']  # Get the current track from iRacing
    
    # Load appropriate knowledge base files
    car_data = load_car_knowledge_base(car_model)
    track_data = load_track_knowledge_base(track_name)
    
    # Load car-track specific setup guide
    setup_guide = load_setup_guide(car_model, track_name)
    
    # Set up session with RAG context
    session_event = {
        "type": "session.update",
        "session": {
            "instructions": (
                "You are iRacing Mechanic, an expert setup engineer for racing simulations. "
                "Use the following car, track, and setup guide information as your knowledge base when "
                "making recommendations.\n\n"
                f"CAR DATA: {json.dumps(car_data, indent=2)}\n\n"
                f"TRACK DATA: {json.dumps(track_data, indent=2)}\n\n"
                f"SETUP GUIDE: {json.dumps(setup_guide, indent=2)}\n\n"
                "Analyze both telemetry data and the driver's subjective feedback about how the car feels. "
                "Provide specific, actionable setup adjustments that address the driver's concerns. "
                "Use precise values from the allowed ranges in the car data. "
                "Reference the car-track specific setup guide for baseline recommendations. "
                "Format your responses as JSON with 'diagnosis', 'recommendations', and 'explanation' fields."
            )
        }
    }
    ws.send(json.dumps(session_event))

def load_car_knowledge_base(car_model):
    """Load the knowledge base for the specific car model from your database"""
    # Map iRacing car path to your knowledge base file
    car_mapping = {
        "ferrari488gt3": "cars/gt3/ferrari_488_gt3.json",
        # Add mappings for all supported cars
    }
    
    try:
        kb_path = car_mapping.get(car_model.lower().replace(" ", ""), "cars/generic/default.json")
        with open(f"knowledge_base/{kb_path}", "r") as f:
            return json.load(f)
    except Exception as e:
        print(f"Error loading car knowledge base: {e}")
        return {"name": "Generic Car", "setup_parameters": {}}

def load_track_knowledge_base(track_name):
    """Load the knowledge base for the specific track from your database"""
    # Map iRacing track names to your knowledge base file
    track_mapping = {
        "spa": "tracks/road/spa.json",
        # Add mappings for all supported tracks
    }
    
    try:
        kb_path = track_mapping.get(track_name.lower().replace(" ", ""), "tracks/generic/default.json")
        with open(f"knowledge_base/{kb_path}", "r") as f:
            return json.load(f)
    except Exception as e:
        print(f"Error loading track knowledge base: {e}")
        return {"name": "Generic Track", "characteristics": {}}

def load_setup_guide(car_model, track_name):
    """Load the car-track specific setup guide from your database"""
    # Normalize names for consistent file lookup
    car_name = car_model.lower().replace(" ", "_")
    track_name = track_name.lower().replace(" ", "_")
    
    # Create car-track specific filename
    setup_filename = f"{car_name}_{track_name}.json"
    
    try:
        with open(f"knowledge_base/setup_guides/{setup_filename}", "r") as f:
            return json.load(f)
    except Exception as e:
        print(f"Error loading setup guide: {e}")
        # If no specific guide exists, at least return generic guidance
        return {
            "baseline": "No specific setup guide available for this car-track combination.",
            "general_recommendations": "Use medium-high downforce for road courses, low downforce for ovals/superspeedways."
        }

A.5 Setup Guide Example

Each car-track specific setup guide should contain baseline setups and track-specific advice:

{
  "car": "Ferrari 488 GT3",
  "track": "Spa-Francorchamps",
  "baseline_setup": {
    "suspension": {
      "front_ride_height": 65,
      "rear_ride_height": 85,
      "front_spring_rate": 145000,
      "rear_spring_rate": 165000
    },
    "dampers": {
      "front_bump": 8,
      "front_rebound": 9,
      "rear_bump": 7,
      "rear_rebound": 8
    },
    "aero": {
      "front_splitter": 5,
      "rear_wing": 9
    },
    "tires": {
      "front_pressure": 27.5,
      "rear_pressure": 28.0
    }
  },
  "sector_specific_advice": [
    {
      "sector": "Eau Rouge/Raidillon",
      "recommendation": "Stable rear end is crucial for this uphill complex. Consider slightly higher rear ride height and stiffer rear springs compared to other tracks."
    },
    {
      "sector": "Les Combes",
      "recommendation": "Heavy braking zone after Kemmel straight, ensure front brake bias and downforce are optimized."
    },
    {
      "sector": "Pouhon",
      "recommendation": "Fast, double-apex left-hander. Mid-corner understeer common here - consider slight reduction in front anti-roll bar stiffness."
    }
  ],
  "weather_adjustments": {
    "cold": {
      "tire_pressure": "Reduce by 1-1.5 psi all around",
      "front_wing": "Increase by 1-2 steps for more front grip in cold conditions"
    },
    "hot": {
      "tire_pressure": "Reduce by 0.5-1.0 psi to prevent overheating",
      "radiator": "Increase cooling to maximum"
    },
    "wet": {
      "ride_height": "Increase by 2-3mm all around",
      "differential": "More open diff settings to reduce wheelspin"
    }
  },
  "top_drivers_setup_trends": {
    "qualifying": "Increase rear wing by 1-2 steps for better rotation in La Source and Bus Stop",
    "race": "More conservative front splitter for consistency over long runs, reduce damage risk"
  }
}

By integrating this car-track specific knowledge base with the Realtime API, your recommendations will be much more precise and accurate to the specific car/track combination the driver is using.

14.4 Setup History & Learning

To make iRacing Mechanic more powerful over time, implement a history system that tracks which setup changes actually helped the driver:

import sqlite3
from datetime import datetime

# Setup the database connection
def init_history_db():
    conn = sqlite3.connect('iracing_mechanic_history.db')
    cursor = conn.cursor()
    
    # Create tables if they don't exist
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS setup_history (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        timestamp TEXT,
        driver_id TEXT,
        car_id TEXT,
        track_id TEXT,
        issue_reported TEXT,
        recommendations TEXT,
        applied INTEGER DEFAULT 0
    )
    ''')
    
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS feedback (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        setup_history_id INTEGER,
        timestamp TEXT,
        improvement_rating INTEGER,
        driver_comments TEXT,
        telemetry_before TEXT,
        telemetry_after TEXT,
        FOREIGN KEY (setup_history_id) REFERENCES setup_history(id)
    )
    ''')
    
    conn.commit()
    return conn

# Save a recommendation to history
def save_recommendation(driver_id, car_id, track_id, issue_reported, recommendations):
    conn = init_history_db()
    cursor = conn.cursor()
    
    timestamp = datetime.now().isoformat()
    cursor.execute(
        'INSERT INTO setup_history (timestamp, driver_id, car_id, track_id, issue_reported, recommendations) VALUES (?, ?, ?, ?, ?, ?)',
        (timestamp, driver_id, car_id, track_id, issue_reported, json.dumps(recommendations))
    )
    
    history_id = cursor.lastrowid
    conn.commit()
    conn.close()
    return history_id

# Record feedback on applied recommendations
def save_feedback(setup_history_id, improvement_rating, driver_comments, telemetry_before, telemetry_after):
    conn = init_history_db()
    cursor = conn.cursor()
    
    timestamp = datetime.now().isoformat()
    cursor.execute(
        'UPDATE setup_history SET applied = 1 WHERE id = ?',
        (setup_history_id,)
    )
    
    cursor.execute(
        'INSERT INTO feedback (setup_history_id, timestamp, improvement_rating, driver_comments, telemetry_before, telemetry_after) VALUES (?, ?, ?, ?, ?, ?)',
        (setup_history_id, timestamp, improvement_rating, driver_comments, json.dumps(telemetry_before), json.dumps(telemetry_after))
    )
    
    conn.commit()
    conn.close()

# Retrieve history to inform future recommendations
def get_similar_successful_recommendations(driver_id, car_id, track_id, issue_reported):
    conn = init_history_db()
    cursor = conn.cursor()
    
    # Find similar issues that were successfully resolved (rated 4 or 5)
    cursor.execute('''
    SELECT sh.issue_reported, sh.recommendations, f.improvement_rating, f.driver_comments
    FROM setup_history sh
    JOIN feedback f ON sh.id = f.setup_history_id
    WHERE sh.driver_id = ? AND sh.car_id = ? AND sh.track_id = ? AND f.improvement_rating >= 4
    ORDER BY f.timestamp DESC
    LIMIT 5
    ''', (driver_id, car_id, track_id))
    
    results = cursor.fetchall()
    conn.close()
    
    successful_setups = []
    for row in results:
        issue, recommendations, rating, comments = row
        # Use text similarity to see if the past issue is similar to current one
        if text_similarity(issue, issue_reported) > 0.7:  # Simple threshold
            successful_setups.append({
                "issue": issue,
                "recommendations": json.loads(recommendations),
                "rating": rating,
                "comments": comments
            })
    
    return successful_setups

# Simple text similarity function (could use more sophisticated NLP in production)
def text_similarity(text1, text2):
    # This is a very simple implementation - in production, use proper NLP
    words1 = set(text1.lower().split())
    words2 = set(text2.lower().split())
    intersection = words1.intersection(words2)
    union = words1.union(words2)
    return len(intersection) / len(union) if union else 0

# In your main loop, integrate this history system
def main():
    # ... [previous code] ...
    
    # Get driver ID (could be from user login or iRacing credentials)
    driver_id = "driver123"  # In production, get this from user login
    car_id = ir['CarPath']
    track_id = ir['TrackName']
    
    try:
        while True:
            if not ir.is_initialized:
                print("iRacing not initialized or not in session.")
            else:
                telemetry = get_all_telemetry()
                
                if telemetry:
                    # Periodically prompt for driver feedback
                    driver_feedback = input("How does the car feel? (press Enter to skip): ")
                    
                    if driver_feedback:
                        # Store telemetry before adjustments
                        telemetry_before = telemetry
                        
                        # Check history for similar issues and successful solutions
                        similar_solutions = get_similar_successful_recommendations(
                            driver_id, car_id, track_id, driver_feedback
                        )
                        
                        # Add history context to the conversation
                        history_context = ""
                        if similar_solutions:
                            history_context = "Based on your history, these adjustments helped with similar issues:\n"
                            for solution in similar_solutions:
                                history_context += f"- When you reported '{solution['issue']}', the following changes helped (rated {solution['rating']}/5): {json.dumps(solution['recommendations'])}\n"
                        
                        # Send telemetry, feedback and history to Realtime API
                        event = {
                            "type": "conversation.item.create",
                            "item": {
                                "type": "message",
                                "role": "user",
                                "content": [
                                    {
                                        "type": "input_text",
                                        "text": f"Driver feedback: {driver_feedback}\n\nTelemetry data: {json.dumps(telemetry, indent=2)}\n\nHistory context: {history_context}"
                                    }
                                ]
                            }
                        }
                        ws.send(json.dumps(event))
                        
                        # Request detailed setup recommendations
                        response_event = {
                            "type": "response.create",
                            "response": {
                                "modalities": ["text"],
                                "instructions": "Provide specific setup adjustments based on the driver's feedback, telemetry data, and their history of successful adjustments."
                            }
                        }
                        ws.send(json.dumps(response_event))
                        
                        # Save this recommendation (you'll get the actual recommendations from the model response)
                        # In a real implementation, extract the recommendations from the model's response
                        # This is simplified for demonstration purposes
                        model_recommendations = {"suspension": {"front_roll_bar": "Soften by 2 steps"}}  # Placeholder
                        history_id = save_recommendation(
                            driver_id, car_id, track_id, driver_feedback, model_recommendations
                        )
                        
                        # Ask for feedback after driver tries the adjustments
                        print("After you've tried these adjustments, please rate how they worked (1-5):")
                        rating = int(input("Rating (1-5): "))
                        comments = input("Comments: ")
                        
                        # Get new telemetry after adjustments
                        telemetry_after = get_all_telemetry()
                        
                        # Save the feedback
                        save_feedback(
                            history_id, rating, comments, telemetry_before, telemetry_after
                        )
            time.sleep(5)
    except KeyboardInterrupt:
        print("Stopping iRacing Mechanic...")
    finally:
        ws.close()

This history system creates a feedback loop that helps the system learn what works for each specific driver, car, and track combination. Over time, it builds a personalized knowledge base of effective setup changes.

14.5 Telemetry Visualization

Integrate real-time telemetry visualization alongside AI recommendations to help drivers understand the data that's informing the advice:

import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
from flask import Flask, render_template, jsonify
import threading
import time
import json

# Create a simple Flask server to serve the visualization
app = Flask(__name__)

# Store the latest telemetry and recommendations
latest_data = {
    "telemetry": {},
    "recommendations": {},
    "timestamp": time.time()
}

@app.route('/')
def index():
    return render_template('telemetry_dashboard.html')

@app.route('/api/latest')
def get_latest():
    return jsonify(latest_data)

# Start Flask in a separate thread
def start_webapp():
    app.run(host='127.0.0.1', port=5000, debug=False)

# Create an HTML template in templates/telemetry_dashboard.html
"""
<!DOCTYPE html>
<html>
<head>
    <title>iRacing Mechanic - Telemetry</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <style>
        body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
        .dashboard { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
        .card { background: white; border-radius: 8px; padding: 15px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
        .recommendations { grid-column: span 2; }
        h2 { margin-top: 0; color: #333; }
        .chart-container { height: 300px; }
    </style>
</head>
<body>
    <h1>iRacing Mechanic - Real-time Telemetry</h1>
    
    <div class="dashboard">
        <div class="card">
            <h2>Throttle/Brake Application</h2>
            <div class="chart-container">
                <canvas id="pedalChart"></canvas>
            </div>
        </div>
        
        <div class="card">
            <h2>Lateral G / Speed</h2>
            <div class="chart-container">
                <canvas id="gforceChart"></canvas>
            </div>
        </div>
        
        <div class="card">
            <h2>Tire Temperatures</h2>
            <div class="chart-container">
                <canvas id="tireChart"></canvas>
            </div>
        </div>
        
        <div class="card">
            <h2>Suspension Travel</h2>
            <div class="chart-container">
                <canvas id="suspensionChart"></canvas>
            </div>
        </div>
        
        <div class="card recommendations">
            <h2>AI Recommendations</h2>
            <div id="recommendationsContent"></div>
        </div>
    </div>

    <script>
        // Initialize charts
        const pedalChart = new Chart(document.getElementById('pedalChart').getContext('2d'), {
            type: 'line',
            data: {
                labels: [],
                datasets: [
                    {
                        label: 'Throttle',
                        data: [],
                        borderColor: 'green',
                        fill: false,
                        tension: 0.4
                    },
                    {
                        label: 'Brake',
                        data: [],
                        borderColor: 'red',
                        fill: false,
                        tension: 0.4
                    }
                ]
            },
            options: {
                scales: {
                    y: {
                        beginAtZero: true,
                        max: 1
                    }
                },
                animation: false,
                responsive: true,
                maintainAspectRatio: false
            }
        });
        
        // Initialize other charts similarly
        // ...
        
        // Fetch data every second
        const dataPoints = 20; // Show last 20 seconds of data
        const throttleData = Array(dataPoints).fill(0);
        const brakeData = Array(dataPoints).fill(0);
        const timeLabels = Array(dataPoints).fill('');
        
        function updateCharts() {
            fetch('/api/latest')
                .then(response => response.json())
                .then(data => {
                    // Update throttle/brake chart
                    throttleData.push(data.telemetry.throttle);
                    throttleData.shift();
                    brakeData.push(data.telemetry.brake);
                    brakeData.shift();
                    
                    const now = new Date();
                    timeLabels.push(now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds());
                    timeLabels.shift();
                    
                    pedalChart.data.labels = timeLabels;
                    pedalChart.data.datasets[0].data = throttleData;
                    pedalChart.data.datasets[1].data = brakeData;
                    pedalChart.update();
                    
                    // Update other charts
                    // ...
                    
                    // Update recommendations
                    if (data.recommendations && Object.keys(data.recommendations).length > 0) {
                        const recDiv = document.getElementById('recommendationsContent');
                        recDiv.innerHTML = `
                            <div class="diagnosis">
                                <h3>Diagnosis</h3>
                                <p>${data.recommendations.diagnosis || 'No diagnosis available'}</p>
                            </div>
                            <div class="changes">
                                <h3>Recommended Changes</h3>
                                <ul>
                                    ${Object.entries(data.recommendations.recommendations || {}).map(([category, items]) => `
                                        <li><strong>${category}</strong>: 
                                            <ul>
                                                ${Object.entries(items).map(([item, change]) => `
                                                    <li>${item}: ${change}</li>
                                                `).join('')}
                                            </ul>
                                        </li>
                                    `).join('')}
                                </ul>
                            </div>
                            <div class="explanation">
                                <h3>Explanation</h3>
                                <p>${data.recommendations.explanation || 'No explanation available'}</p>
                            </div>
                        `;
                    }
                })
                .catch(error => console.error('Error fetching telemetry:', error));
        }
        
        // Update every second
        setInterval(updateCharts, 1000);
    </script>
</body>
</html>
"""

# Update your main function to store the latest data for the web visualization
def main():
    # ... [previous code] ...
    
    # Start the web visualization in a separate thread
    webapp_thread = threading.Thread(target=start_webapp)
    webapp_thread.daemon = True
    webapp_thread.start()
    
    try:
        while True:
            if not ir.is_initialized:
                print("iRacing not initialized or not in session.")
            else:
                telemetry = get_all_telemetry()
                
                if telemetry:
                    # Update the latest data for visualization
                    latest_data["telemetry"] = telemetry
                    latest_data["timestamp"] = time.time()
                    
                    # Prompt for driver feedback as before
                    driver_feedback = input("How does the car feel? (press Enter to skip): ")
                    
                    if driver_feedback:
                        # ... [existing code for processing feedback] ...
                        
                        # When you get recommendations back from the model, update the visualization
                        # This would be done in your on_message handler for the WebSocket
                        def on_message(ws, message):
                            data = json.loads(message)
                            
                            # Accumulate text deltas to build the complete response
                            if 'type' in data and data['type'] == 'response.text.delta':
                                # ... [existing delta handling] ...
                                pass
                            
                            # When response is complete, try to parse as JSON
                            if 'type' in data and data['type'] == 'response.done':
                                try:
                                    # Assume we've been accumulating the response in ws.complete_response
                                    if hasattr(ws, 'complete_response'):
                                        response_json = json.loads(ws.complete_response)
                                        
                                        # Update the visualization with the recommendations
                                        latest_data["recommendations"] = response_json
                                        
                                        # Also save to history as before
                                        # ... [history saving code] ...
                                except json.JSONDecodeError:
                                    print("Could not parse response as JSON")
            time.sleep(1)
    except KeyboardInterrupt:
        print("Stopping iRacing Mechanic...")
    finally:
        ws.close()

This visualization provides interactive charts of key telemetry metrics alongside the AI's recommendations, helping drivers understand the relationship between the data and the suggested setup changes.

14.6 Setup Export/Import

Add functionality to directly export the AI's recommendations as iRacing setup files:

import os
import re
import shutil
from datetime import datetime

def export_to_iracing_setup(car_id, recommendations, driver_name=None):
    """
    Export the recommended setup changes to an iRacing setup file
    
    This function:
    1. Finds the current setup file being used
    2. Creates a copy with the AI's recommended changes
    3. Saves it in iRacing's setups folder for immediate use
    """
    # Map our internal parameters to iRacing's setup file parameters
    # This mapping will need to be expanded for each car type
    parameter_mapping = {
        "ferrari_488_gt3": {
            "front_wing": "FWSetting",
            "rear_wing": "RWSetting",
            "front_tire_pressure": "PressureLF, PressureRF",
            "rear_tire_pressure": "PressureLR, PressureRR",
            "front_roll_bar": "FrontARBSetting",
            "rear_roll_bar": "RearARBSetting",
            "front_ride_height": "FrontRideHeight",
            "rear_ride_height": "RearRideHeight"
            # Add more mappings as needed
        }
    }
    
    # Get car-specific parameter mapping
    car_params = parameter_mapping.get(car_id, {})
    if not car_params:
        print(f"No parameter mapping available for {car_id}")
        return False
    
    try:
        # Find the iRacing documents folder (this varies by system)
        # This is just an example - adjust for actual iRacing paths
        iracing_documents = os.path.expanduser("~/Documents/iRacing")
        setups_folder = os.path.join(iracing_documents, "setups", car_id)
        
        # Find the current active setup
        # In a real implementation, you'd get this from iRacing's API or memory
        # For this example, we'll just use a placeholder
        current_setup_path = os.path.join(setups_folder, "current.sto")
        
        if not os.path.exists(current_setup_path):
            print("Could not find current setup file")
            return False
        
        # Create a new setup name
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        driver_prefix = f"{driver_name}_" if driver_name else ""
        new_setup_name = f"{driver_prefix}AI_recommendation_{timestamp}.sto"
        new_setup_path = os.path.join(setups_folder, new_setup_name)
        
        # First, make a copy of the current setup
        shutil.copy2(current_setup_path, new_setup_path)
        
        # Read the setup file
        with open(new_setup_path, 'r') as f:
            setup_content = f.read()
        
        # Apply the recommended changes
        modified_content = setup_content
        for param_category, changes in recommendations.items():
            for param_name, new_value in changes.items():
                # Find the corresponding iRacing parameter
                if param_name in car_params:
                    iracing_params = car_params[param_name].split(',')
                    
                    # Handle text-based changes like "increase by 2 steps"
                    if isinstance(new_value, str) and ('increase' in new_value.lower() or 'decrease' in new_value.lower()):
                        # Parse the change directive
                        match = re.search(r'(increase|decrease) by (\d+)', new_value.lower())
                        if match:
                            direction, steps = match.groups()
                            steps = int(steps)
                            
                            # Apply to each mapped parameter
                            for iracing_param in iracing_params:
                                iracing_param = iracing_param.strip()
                                # Find current value in setup file
                                param_match = re.search(f"{iracing_param}=([0-9.-]+)", modified_content)
                                
                                if param_match:
                                    current_value = float(param_match.group(1))
                                    # Adjust value based on direction
                                    new_value = current_value + steps if direction == 'increase' else current_value - steps
                                    # Replace in the file
                                    modified_content = re.sub(
                                        f"{iracing_param}=[0-9.-]+",
                                        f"{iracing_param}={new_value}",
                                        modified_content
                                    )
                    # Handle direct numeric values
                    elif isinstance(new_value, (int, float)) or (isinstance(new_value, str) and new_value.replace('.', '', 1).isdigit()):
                        # Convert to float if it's a numeric string
                        if isinstance(new_value, str):
                            new_value = float(new_value)
                            
                        # Apply to each mapped parameter
                        for iracing_param in iracing_params:
                            iracing_param = iracing_param.strip()
                            # Replace in the file
                            modified_content = re.sub(
                                f"{iracing_param}=[0-9.-]+",
                                f"{iracing_param}={new_value}",
                                modified_content
                            )
        
        # Write the modified setup back to the file
        with open(new_setup_path, 'w') as f:
            f.write(modified_content)
        
        print(f"Setup exported successfully to {new_setup_path}")
        return new_setup_path
    
    except Exception as e:
        print(f"Error exporting setup: {e}")
        return False

# Add a function to import a setup file
def import_setup_file(setup_file_path):
    """
    Parse an iRacing setup file and return its parameters
    This can be used to analyze existing setups
    """
    try:
        with open(setup_file_path, 'r') as f:
            setup_content = f.read()
        
        # Extract parameters using regex
        # This is a simplified example - real parsing would be more complex
        parameters = {}
        
        # Parse key parameters
        for param in ["FWSetting", "RWSetting", "FrontARBSetting", "RearARBSetting", 
                      "PressureLF", "PressureRF", "PressureLR", "PressureRR",
                      "FrontRideHeight", "RearRideHeight"]:
            match = re.search(f"{param}=([0-9.-]+)", setup_content)
            if match:
                parameters[param] = float(match.group(1))
        
        return parameters
    
    except Exception as e:
        print(f"Error importing setup: {e}")
        return {}

# In your main function, add export functionality
def main():
    # ... [previous code] ...
    
    try:
        while True:
            # ... [existing telemetry and recommendation code] ...
            
            # After getting recommendations from the model
            if hasattr(ws, 'complete_response'):
                try:
                    response_json = json.loads(ws.complete_response)
                    
                    # Ask if user wants to export the setup
                    export_choice = input("Export these changes to an iRacing setup file? (y/n): ")
                    if export_choice.lower() == 'y':
                        # Get driver name for the filename
                        driver_name = input("Enter your name for the setup file (optional): ")
                        
                        # Export the recommendations
                        setup_path = export_to_iracing_setup(
                            car_id=ir['CarPath'],
                            recommendations=response_json.get('recommendations', {}),
                            driver_name=driver_name
                        )
                        
                        if setup_path:
                            print(f"Setup exported to: {setup_path}")
                            print("You can now load this setup in iRacing.")
                except json.JSONDecodeError:
                    print("Could not parse recommendations for export")
            
            time.sleep(5)
    except KeyboardInterrupt:
        print("Stopping iRacing Mechanic...")
    finally:
        ws.close()

This export functionality allows drivers to:

  1. Automatically apply the AI's recommendations to their current setup
  2. Save the new setup with a meaningful name in iRacing's setup folder
  3. Load the modified setup directly in the game

14.7 Voice Integration Option

For a hands-free experience during practice sessions, you can add voice input/output:

# Add these imports
import speech_recognition as sr
import pyttsx3

# Initialize speech recognition and TTS engine
recognizer = sr.Recognizer()
engine = pyttsx3.init()

# Function to capture voice input
def get_voice_feedback():
    try:
        with sr.Microphone() as source:
            print("Listening for feedback...")
            audio = recognizer.listen(source, timeout=5)
            feedback = recognizer.recognize_google(audio)
            print(f"Heard: {feedback}")
            return feedback
    except Exception as e:
        print(f"Error capturing voice: {e}")
        return None

# Function to speak feedback
def speak_response(text):
    engine.say(text)
    engine.runAndWait()

# Then in your main loop
if keyboard_pressed('v'):  # Some trigger to start voice input
    feedback = get_voice_feedback()
    if feedback:
        # Send to Realtime API as before
        # ...
        
# And in your on_message handler
def on_message(ws, message):
    data = json.loads(message)
    
    if 'type' in data and data['type'] == 'response.done':
        # Extract the complete response and speak it
        if hasattr(ws, 'accumulated_response'):
            speak_response(ws.accumulated_response)

15. License

This project is distributed under the MIT License. You're free to use, modify, and distribute the code under these terms.


Application: iRacing Mechanic

One powerful application of this integration is iRacing Mechanic - an interactive setup advisor that provides real-time, personalized car setup recommendations based on driver feedback.

How It Works

  1. Driver Input: As you drive, describe how your car feels using natural language (e.g., "the car is too unstable in corners" or "the front feels too heavy").

  2. AI Analysis: The system combines your subjective feedback with objective telemetry data captured through iRSDK to generate context-aware setup advice.

  3. Personalized Recommendations: Receive specific, actionable adjustments for your car setup that match your driving style and the current track conditions.

Implementation Example

Here's how to implement the iRacing Mechanic functionality using the Realtime API:

# Add this to your WebSocket implementation
def on_open(ws):
    print("Connected to Realtime API via WebSocket.")
    
    # Set up session instructions specific to iRacing Mechanic
    session_event = {
        "type": "session.update",
        "session": {
            "instructions": (
                "You are iRacing Mechanic, an expert setup engineer for racing simulations. "
                "Analyze both telemetry data and the driver's subjective feedback about how the car feels. "
                "Provide specific, actionable setup adjustments (suspension, aero, tire pressures, etc.) "
                "that address the driver's concerns. Explain the reasoning behind each recommendation "
                "and how it will affect car handling. Keep recommendations practical and within "
                "iRacing's setup adjustment ranges. Format your responses as JSON with 'diagnosis', "
                "'recommendations', and 'explanation' fields."
            )
        }
    }
    ws.send(json.dumps(session_event))

# In your main loop, allow for driver feedback combined with telemetry
def main():
    # ... [WebSocket setup code here]
    
    try:
        while True:
            if not ir.is_initialized:
                print("iRacing not initialized or not in session.")
            else:
                telemetry = get_all_telemetry()
                
                if telemetry:
                    # Periodically prompt for driver feedback
                    driver_feedback = input("How does the car feel? (press Enter to skip): ")
                    
                    if driver_feedback:
                        # Send both telemetry and driver feedback together
                        event = {
                            "type": "conversation.item.create",
                            "item": {
                                "type": "message",
                                "role": "user",
                                "content": [
                                    {
                                        "type": "input_text",
                                        "text": f"Driver feedback: {driver_feedback}\n\nTelemetry data: {json.dumps(telemetry, indent=2)}"
                                    }
                                ]
                            }
                        }
                        ws.send(json.dumps(event))

                        # Request detailed setup recommendations
                        response_event = {
                            "type": "response.create",
                            "response": {
                                "modalities": ["text"],
                                "instructions": "Provide specific setup adjustments based on the driver's feedback and telemetry data."
                            }
                        }
                        ws.send(json.dumps(response_event))
            time.sleep(5)
    except KeyboardInterrupt:
        print("Stopping iRacing Mechanic...")
    finally:
        ws.close()

Key Features

  • Real-Time Interaction: Get immediate advice while driving based on what you're feeling in the car.
  • Learn as You Go: Understand the reasoning behind setup changes to improve your knowledge over time.
  • Personalized to Your Style: Receive recommendations tailored to your unique driving preferences.
  • Voice Integration: Optional voice input/output for hands-free operation during practice sessions.

Final Notes

By integrating iRacing Telemetry with OpenAI's Realtime API via WebRTC or WebSockets, you can create a low-latency, voice-enabled, function-calling AI spotter or crew chief. Whether you want real-time voice commentary, strategic advice, or advanced function calls like the iRacing Mechanic setup advisor, the GPT-4o Realtime models offer a powerful, event-driven interface well beyond simple text chat.

Enjoy your AI-assisted racing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment