An AI-powered air traffic controller for DCS World Server. The bot listens on SRS radio frequencies, transcribes pilot transmissions using faster-whisper STT, generates realistic ATC responses using an LLM, and broadcasts them back over SRS using Piper TTS.
Note: This project is in early development. Expect frequent updates as bugs are fixed and the AI is trained to produce more accurate ATC responses.
- Windows 10/11, Linux (tested on Ubuntu 24.04 LTS), or macOS (untested — may require manual setup)
- DCS World Server — a dedicated server instance is required (the bot reads telemetry and weather exports from the running server). Runs on Windows natively or on Linux via Wine. The bot itself can run on a separate machine, connecting to the DCS server over the network.
- SRS (SimpleRadio Standalone) — server must be running
- Tacview — with real-time telemetry enabled
- Python 3.11+
- An AI provider account (OpenAI, Groq, or local Ollama)
Windows:
install.bat
Linux:
chmod +x install.sh && ./install.sh
The install script will:
- Create a Python virtual environment (
.venv/) - Install Python dependencies
- Download and extract Piper TTS
- Download the voice model set in
config.lua(default:en_US-amy-medium) - Create
.envfrom.env.example
If you have an NVIDIA GPU, install CUDA dependencies for GPU-accelerated speech recognition. Without these, faster-whisper falls back to CPU which is significantly slower.
Option A — Python packages (quickest, works on any distro):
source .venv/bin/activate
pip install nvidia-cublas-cu12 nvidia-cudnn-cu12
Option B — System CUDA toolkit:
Ubuntu / Debian:
sudo apt install nvidia-cuda-toolkit
Fedora / RHEL:
sudo dnf install cuda-toolkit
Arch Linux:
sudo pacman -S cuda
source .venv/bin/activate
python3 main.py
Linux note: DCS World is Windows-only. The bot can run on Linux and connect to an SRS server running on a Windows machine over the network. Set
BOT_HOSTindcs_atc_export.luato the Linux machine's IP address.
- Copy
dcs_atc_export.luato:%USERPROFILE%\Saved Games\DCS_Server Instance\Scripts\Hooks\ - Open the file and set
BOT_HOST:- Same machine as DCS:
"127.0.0.1" - Bot on a different machine: set to that machine's local IP (e.g.
"192.168.1.50")
- Same machine as DCS:
Copy .env.example to .env and fill in your API key:
OPENAI_API_KEY=sk-... # if using OpenAI
GROQ_API_KEY=gsk_... # if using Groq (free)
Then create your local config and DCS hook:
cp dcs_atc_export.lua.example dcs_atc_export.luaCreate a file called config.local.lua in the project root with your airfield settings. The fastest way is to copy the bundled template:
cp config.local.lua.example config.local.luaThen edit config.local.lua to match your airfield, map, and SRS frequencies. The template contains the minimum required fields:
AIRPORT_ICAO = "LCRA"
DCS_MAP = "Syria" -- DCS theatre name (see config.lua for valid values)
ATC_CALLSIGN = "Akrotiri"
ACTIVE_RUNWAY = "28"
FREQ_APPROACH = 119000000
FREQ_APPROACH_2 = 257000000
FREQ_TOWER = 119500000
FREQ_TOWER_2 = 257500000
FREQ_GROUND = 118000000
FREQ_GROUND_2 = 258000000
DCS_MAPis required. It must match the theatre your DCS server is running, and the chosenAIRPORT_ICAOmust exist on that map. The bot will refuse to start with a clear error message if either is missing or mismatched. This disambiguates airfields that exist on multiple maps (e.g. Beirut OLBA appears on both Syria and Sinai). TheDCS_MAPvalue next to each map header in config.lua is the exact string to use.
Important: Do NOT edit
config.luadirectly — it is tracked by git and will cause merge conflicts ongit pull. Put all your settings inconfig.local.luainstead. This file is gitignored and will never be overwritten by updates. Any value inconfig.local.luaoverrides the same value inconfig.lua.
Copy dcs_atc_export.lua to your DCS hooks folder (%USERPROFILE%\Saved Games\DCS\Scripts\Hooks\) and set BOT_HOST to your bot machine's IP.
Upgrading from an older version? If you previously edited config.lua directly, rename it to config.local.lua and run git checkout config.lua to restore the default. Future git pull will be conflict-free.
On a dedicated server, Tacview is configured via options.lua rather than the GUI. Open:
%USERPROFILE%\Saved Games\DCS_DEDICATED_SERVER\Config\Options.lua
Find the ["Tacview"] block inside ["plugins"] and set the following values:
["tacviewRealTimeTelemetryEnabled"] = true,
["tacviewRealTimeTelemetryPort"] = "42674",
["tacviewRemoteControlEnabled"] = true,
["tacviewRemoteControlPort"] = "42675",
["tacviewPlaybackDelay"] = 0,Important:
tacviewPlaybackDelaymust be0. Any non-zero value introduces a delay in the telemetry stream and the bot will not receive live position data.
If the ["Tacview"] block does not exist, add it inside the ["plugins"] table. Leave ["tacviewRealTimeTelemetryPassword"] and ["tacviewRemoteControlPassword"] as empty strings unless you want password protection.
All operational settings are in config.lua (defaults) and config.local.lua (your overrides). The default config.lua is self-documented with a full airfield reference at the top. Only the values you want to change need to go in config.local.lua.
| Setting | Description |
|---|---|
AIRPORT_ICAO |
ICAO code of your airfield (see reference list in config.lua) |
DCS_MAP |
Required. DCS theatre name e.g. "Syria", "Caucasus", "PersianGulf" (see config.lua for full list) |
ATC_CALLSIGN |
Base callsign e.g. "ANAPA" — bot appends APPROACH / TOWER / GROUND automatically |
ACTIVE_RUNWAY |
Active runway designator e.g. "22" |
MAGNETIC_VAR |
Magnetic variation in degrees east for your map |
TACAN_CHANNEL |
TACAN channel e.g. "99X" — set to "" to disable |
TACAN_INBOUND_COURSE |
TACAN inbound course in magnetic degrees |
TACAN_MDA_FT |
Minimum descent altitude in feet |
AI_PROVIDER |
"openai", "groq", or "ollama" |
AI_MODEL |
Override the default model (optional) |
FREQ_APPROACH |
Approach frequency in Hz e.g. 131000000 |
FREQ_TOWER |
Tower frequency in Hz |
FREQ_GROUND |
Ground frequency in Hz |
INSTRUCTIONS |
Optional custom ATC instructions appended to the system prompt |
PIPER_VOICE |
Voice model name e.g. "en_GB-alan-medium" — see available voices |
DCS_CHAT_ENABLED |
true to post ATC responses in DCS in-game chat (default: true) |
DCS_CHAT_PORT |
UDP port the Lua hook listens on for chat (default: 15100) |
When enabled, every ATC response is also posted to the DCS in-game text chat so pilots can read what was said. This is useful when TTS audio is hard to understand or when reviewing instructions.
- Enabled by default. Set
DCS_CHAT_ENABLED = falseinconfig.local.luato disable. - Requires the updated
dcs_atc_export.luahook (re-copy to your DCS hooks folder after updating). - The bot sends UDP to the DCS machine on port
15100(configurable viaDCS_CHAT_PORT). - If the bot runs on a different machine than DCS, set
DCS_CHAT_HOSTin.envto the DCS server's IP.
Set LOG_LEVEL in your .env to control output verbosity:
| Level | Description |
|---|---|
DEBUG |
All messages including SRS pings, Tacview updates, audio details (default) |
INFO |
Transmissions, ATC responses, model loading — recommended for normal use |
WARNING |
Only warnings and errors |
ERROR |
Only errors |
Example .env setting to disable debug noise:
LOG_LEVEL=INFO
| Provider | Cost | Setup |
|---|---|---|
| Groq | Free tier | Sign up at console.groq.com, add GROQ_API_KEY to .env |
| Ollama | Free, local | Install from ollama.com, run ollama pull llama3.1 |
| OpenAI | Paid | Add OPENAI_API_KEY to .env |
Set AI_PROVIDER in config.local.lua to switch between them.
The bot uses faster-whisper for speech recognition. Set WHISPER_MODEL in your .env to choose a model (default: distil-large-v3).
| Model | Size | Speed | Accuracy | VRAM |
|---|---|---|---|---|
tiny |
75 MB | Fastest | Low | ~1 GB |
base |
142 MB | Very fast | Fair | ~1 GB |
small |
466 MB | Fast | Good | ~2 GB |
medium |
1.5 GB | Moderate | Very good | ~5 GB |
large-v3 |
3.1 GB | Slow | Best | ~10 GB |
distil-large-v3 |
1.5 GB | Fast | Near-best | ~4 GB |
Example .env setting:
WHISPER_MODEL=small
Models are downloaded automatically from HuggingFace on first run. Smaller models are faster but less accurate — distil-large-v3 offers the best speed/accuracy tradeoff for ATC use.
Add site-specific ATC rules to the INSTRUCTIONS field in config.local.lua:
INSTRUCTIONS = "Expect fast jet traffic. All aircraft report 10 nautical miles. Preferred approach is straight-in runway 22."Option A — Executable (recommended)
Run build_launcher.bat once to compile the launcher, then double-click ATC Bot.exe.
A console window will open showing the bot status. Press Ctrl+C or close the window to stop the bot.
All output is also written to bot.log.
Option B — Command line
Windows:
python main.py
Linux:
source .venv/bin/activate
python3 main.py
Output is logged to both the terminal and bot.log on all platforms.
- Bot connects to SRS and listens on all configured frequencies simultaneously
- Pilot transmits on any ATC frequency
- Audio is decoded and transcribed by faster-whisper STT (distil-large-v3)
- Pilot callsign is extracted from the transmission
- LLM generates an ATC response using live traffic data (Tacview) and weather (DCS export)
- Piper TTS synthesises the response to audio
- Bot transmits the audio back on the same frequency the pilot used
- Bot callsign automatically matches the service: APPROACH, TOWER, or GROUND
The bot uses speech-to-text to identify your callsign from each transmission. Clear, NATO-style callsigns work best.
Recommended format: Callsign Number separated by a pipe | from your pilot name in the SRS client.
Whiskey 1-1 | Bob
Viper 11 | Maverick
Chevy 2-1 | Dave
The bot reads the portion before the pipe as your radio callsign. Keep it simple:
- Short phonetic words: Whiskey, Viper, Chevy, Dodge, Hawk, Fury, Rage
- One or two digit group: 1-1, 11, 2-1, 31
Avoid intricate or unusual callsigns — the speech-to-text model may struggle to transcribe them accurately, leading to the bot not recognising you or misreading your callsign.
Set frequencies in config.local.lua to match your SRS server. Each service has a primary and secondary frequency:
FREQ_APPROACH = 131000000 -- 131.000 MHz
FREQ_APPROACH_2 = 260000000 -- 260.000 MHz (UHF)
FREQ_TOWER = 131000000
FREQ_TOWER_2 = 260000000
FREQ_GROUND = 131000000
FREQ_GROUND_2 = 260000000The bot can run on a machine that is not on the same LAN as the DCS server. Each connection requires a different approach:
No changes needed — SRS already connects to a hostname/IP and works over the internet.
- On the DCS server's router, forward TCP port 42674 to the DCS machine's LAN IP
- In the bot's
.env, setTACVIEW_HOSTto the DCS server's public IP or DDNS hostname:TACVIEW_HOST=your-server.ddns.net
- On the bot machine's router, forward UDP port 15099 to the bot machine's LAN IP
- In
dcs_atc_export.lua, setBOT_HOSTto the bot machine's public IP or DDNS hostname:local BOT_HOST = "your-bot-machine.ddns.net"
- The default port
15099can be changed by editing bothBOT_PORTindcs_atc_export.luaandDCS_EXPORT_PORTin.env
| Connection | Direction | Port to forward | Where to forward |
|---|---|---|---|
| Tacview | Bot → DCS server | TCP 42674 on DCS router | DCS machine LAN IP |
| DCS weather export | DCS server → Bot | UDP 15099 on bot router | Bot machine LAN IP |
| SRS | Bot → SRS server | None (outbound only) | — |
The bot runs alongside a DCS World Server instance. The heaviest local component is faster-whisper (speech-to-text) — everything else is lightweight.
| Component | Requirement |
|---|---|
| CPU | 4-core, Intel 8th gen+ / Ryzen 2000+ |
| RAM | 8 GB |
| GPU | None required (Whisper runs on CPU, but slower) |
| Storage | ~2 GB for models + Python environment |
On CPU-only systems, transcription with
distil-large-v3will be slow (~3-5x real-time). SetWHISPER_MODEL=smallorbasein.envfor faster results at the cost of accuracy.
| Component | Requirement |
|---|---|
| CPU | 4+ cores |
| RAM | 16 GB |
| GPU | NVIDIA with 4+ GB VRAM and CUDA support (e.g. GTX 1650 / RTX 3050) |
| Storage | ~2 GB for models + Python environment |
The bot auto-detects GPU availability (device="auto" in faster-whisper). With a CUDA-capable GPU, transcription is near real-time.
Bottom line: If your machine can run DCS, it can run the bot — especially with a smaller Whisper model like small or base.
Bot shows wrong callsign / old airfield name
Your .env file may be overriding config.local.lua. Remove any ATC_CALLSIGN, AIRPORT_ICAO, or frequency entries from .env — these should only be in config.local.lua.
No weather data
Check %USERPROFILE%\Saved Games\DCS_Server Instance\Logs\atc_export.log. If it shows socket=false, the DCS Lua socket library is not available. Ensure DCS is not in a restricted export mode.
STT not transcribing
faster-whisper downloads the distil-large-v3 model from HuggingFace on first run — this may take a few minutes. Check bot.log for errors.
Piper not found
Verify piper/piper.exe exists in the project root and the voice model is in piper/voices/.
Bot not responding on radio
- Confirm SRS server is running and the bot has connected (check
bot.log) - Confirm Tacview real-time telemetry is enabled
- Confirm frequencies in
config.local.luamatch the SRS frequencies you are transmitting on
| File | Purpose |
|---|---|
config.lua |
Default configuration and airfield reference — do not edit, use config.local.lua for overrides |
config.local.lua |
Your personal settings (gitignored) — overrides values in config.lua |
dcs_atc_export.lua.example |
Example DCS hook — copy to dcs_atc_export.lua and install in DCS hooks folder |
.env |
Secret keys (API keys, server addresses) — never committed to git |
dcs_atc_export.lua |
DCS Lua hook — install to Saved Games/DCS_Server Instance/Scripts/Hooks/ |
main.py |
Bot entry point and audio pipeline |
components/atc_brain.py |
LLM interface — generates ATC responses |
components/atc_state.py |
Tracks aircraft strips, squawks, runway state |
components/tacview_client.py |
Reads live traffic from Tacview |
components/srs_client.py |
SRS radio audio send/receive |
components/stt_engine.py |
faster-whisper speech-to-text |
components/tts_engine.py |
Piper text-to-speech |
components/dcs_export.py |
Receives weather data from DCS |
components/airfield_db.py |
ICAO coordinate lookup table |
bot.log |
Runtime log — always written on all platforms |
This project is built on the following open-source projects and services:
| Project | Role | License / Link |
|---|---|---|
| DCS World | Combat flight simulator | Eagle Dynamics |
| SRS (SimpleRadio Standalone) | In-game radio communications | Ciribob |
| Tacview | Real-time flight telemetry | Vyrtuoz |
| faster-whisper | Speech-to-text (CTranslate2 Whisper) | MIT |
| OpenAI Whisper | Original Whisper model architecture | MIT |
| Piper TTS | Text-to-speech engine | MIT |
| OpenAI API | LLM provider (GPT-4o-mini) | OpenAI |
| Groq | LLM provider (free tier) | Groq |
| Ollama | Local LLM inference | MIT |
| NumPy | Audio array processing | BSD |
| SciPy | Audio signal processing | BSD |
| opuslib | Opus audio codec bindings | BSD |
| python-dotenv | Environment variable management | BSD |
| CTranslate2 | Optimised transformer inference | MIT |
| HuggingFace | Model hosting and distribution | HuggingFace |
This project was developed with Claude Code (Anthropic).