18 KiB
18 KiB
RFCP Dependencies & Installer Specification
Overview
All dependencies needed for RFCP to work out of the box, including GPU acceleration. The installer must handle everything — user should NOT need to run pip manually.
Python Dependencies
Core (MUST have)
# requirements.txt
# Web framework
fastapi>=0.104.0
uvicorn[standard]>=0.24.0
websockets>=12.0
# Scientific computing
numpy>=1.24.0
scipy>=1.11.0
# Geospatial
pyproj>=3.6.0 # coordinate transformations
shapely>=2.0.0 # geometry operations (boundary contours)
# Terrain data
rasterio>=1.3.0 # GeoTIFF reading (optional, for custom terrain)
# Note: SRTM .hgt files read with numpy directly
# OSM data
requests>=2.31.0 # HTTP client for OSM Overpass API
geopy>=2.4.0 # distance calculations
# Database
# sqlite3 is built-in Python — no install needed
# Utilities
orjson>=3.9.0 # fast JSON (optional, faster API responses)
pydantic>=2.0.0 # data validation (FastAPI dependency)
GPU Acceleration (OPTIONAL — auto-detected)
# requirements-gpu-nvidia.txt
cupy-cuda12x>=12.0.0 # For CUDA 12.x (RTX 30xx, 40xx)
# OR
cupy-cuda11x>=11.0.0 # For CUDA 11.x (older cards)
# requirements-gpu-opencl.txt
pyopencl>=2023.1 # For ANY GPU (Intel, AMD, NVIDIA)
Development / Testing
# requirements-dev.txt
pytest>=7.0.0
pytest-asyncio>=0.21.0
httpx>=0.25.0 # async test client
System Dependencies
NVIDIA GPU Support
REQUIRED: NVIDIA Driver (comes with GPU)
REQUIRED: CUDA Toolkit 12.x (for CuPy)
Check if installed:
nvidia-smi → shows driver version
nvcc --version → shows CUDA toolkit version
If missing CUDA toolkit:
Download from: https://developer.nvidia.com/cuda-downloads
Select: Windows > x86_64 > 11/10 > exe (local)
Size: ~3 GB
Alternative: cupy auto-installs CUDA runtime!
pip install cupy-cuda12x
This bundles CUDA runtime (~700 MB) — no separate install needed
Intel GPU Support (OpenCL)
REQUIRED: Intel GPU Driver (usually pre-installed)
REQUIRED: Intel OpenCL Runtime
Check if installed:
Open Device Manager → Display Adapters → Intel UHD/Iris
For OpenCL:
Download Intel GPU Computing Runtime:
https://github.com/intel/compute-runtime/releases
Or: Intel oneAPI Base Toolkit (includes OpenCL)
https://www.intel.com/content/www/us/en/developer/tools/oneapi/base-toolkit-download.html
AMD GPU Support (OpenCL)
REQUIRED: AMD Adrenalin Driver (includes OpenCL)
Download from: https://www.amd.com/en/support
Node.js / Frontend Dependencies
System Requirements
Node.js >= 18.0.0 (LTS recommended)
npm >= 9.0.0
Check:
node --version
npm --version
Frontend packages (managed by npm)
// package.json — key dependencies
{
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"leaflet": "^1.9.4",
"react-leaflet": "^4.2.0",
"recharts": "^2.8.0",
"zustand": "^4.4.0",
"lucide-react": "^0.294.0"
},
"devDependencies": {
"vite": "^5.0.0",
"typescript": "^5.3.0",
"tailwindcss": "^3.4.0",
"@types/leaflet": "^1.9.0"
}
}
Installer Script
Windows Installer (NSIS or Electron-Builder)
# install_rfcp.py — Python-based installer/setup script
import subprocess
import sys
import platform
import os
import shutil
import json
def check_python():
"""Verify Python 3.10+ is available."""
version = sys.version_info
if version.major < 3 or version.minor < 10:
print(f"❌ Python 3.10+ required, found {version.major}.{version.minor}")
return False
print(f"✅ Python {version.major}.{version.minor}.{version.micro}")
return True
def check_node():
"""Verify Node.js 18+ is available."""
try:
result = subprocess.run(["node", "--version"], capture_output=True, text=True)
version = result.stdout.strip().lstrip('v')
major = int(version.split('.')[0])
if major < 18:
print(f"❌ Node.js 18+ required, found {version}")
return False
print(f"✅ Node.js {version}")
return True
except FileNotFoundError:
print("❌ Node.js not found")
return False
def detect_gpu():
"""Detect available GPU hardware."""
gpus = {
"nvidia": False,
"nvidia_name": "",
"intel": False,
"intel_name": "",
"amd": False,
"amd_name": ""
}
# Check NVIDIA
try:
result = subprocess.run(
["nvidia-smi", "--query-gpu=name,driver_version,memory.total",
"--format=csv,noheader"],
capture_output=True, text=True, timeout=5
)
if result.returncode == 0:
info = result.stdout.strip()
gpus["nvidia"] = True
gpus["nvidia_name"] = info.split(",")[0].strip()
print(f"✅ NVIDIA GPU: {info}")
except (FileNotFoundError, subprocess.TimeoutExpired):
print("ℹ️ No NVIDIA GPU detected")
# Check Intel/AMD via WMI (Windows)
if platform.system() == "Windows":
try:
result = subprocess.run(
["wmic", "path", "win32_videocontroller", "get",
"name,adapterram,driverversion", "/format:csv"],
capture_output=True, text=True, timeout=5
)
for line in result.stdout.strip().split('\n'):
if 'Intel' in line:
gpus["intel"] = True
gpus["intel_name"] = [x for x in line.split(',') if 'Intel' in x][0]
print(f"✅ Intel GPU: {gpus['intel_name']}")
elif 'AMD' in line or 'Radeon' in line:
gpus["amd"] = True
gpus["amd_name"] = [x for x in line.split(',') if 'AMD' in x or 'Radeon' in x][0]
print(f"✅ AMD GPU: {gpus['amd_name']}")
except Exception:
pass
return gpus
def install_core_dependencies():
"""Install core Python dependencies."""
print("\n📦 Installing core dependencies...")
subprocess.run([
sys.executable, "-m", "pip", "install", "-r", "requirements.txt",
"--quiet", "--no-warn-script-location"
], check=True)
print("✅ Core dependencies installed")
def install_gpu_dependencies(gpus: dict):
"""Install GPU-specific dependencies based on detected hardware."""
print("\n🎮 Setting up GPU acceleration...")
gpu_installed = False
# NVIDIA — install CuPy (includes CUDA runtime)
if gpus["nvidia"]:
print(f" Installing CuPy for {gpus['nvidia_name']}...")
try:
# Try CUDA 12 first (newer cards)
subprocess.run([
sys.executable, "-m", "pip", "install", "cupy-cuda12x",
"--quiet", "--no-warn-script-location"
], check=True, timeout=300)
print(f" ✅ CuPy (CUDA 12) installed for {gpus['nvidia_name']}")
gpu_installed = True
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
try:
# Fallback to CUDA 11
subprocess.run([
sys.executable, "-m", "pip", "install", "cupy-cuda11x",
"--quiet", "--no-warn-script-location"
], check=True, timeout=300)
print(f" ✅ CuPy (CUDA 11) installed for {gpus['nvidia_name']}")
gpu_installed = True
except Exception as e:
print(f" ⚠️ CuPy installation failed: {e}")
print(f" 💡 Manual install: pip install cupy-cuda12x")
# Intel/AMD — install PyOpenCL
if gpus["intel"] or gpus["amd"]:
gpu_name = gpus["intel_name"] or gpus["amd_name"]
print(f" Installing PyOpenCL for {gpu_name}...")
try:
subprocess.run([
sys.executable, "-m", "pip", "install", "pyopencl",
"--quiet", "--no-warn-script-location"
], check=True, timeout=120)
print(f" ✅ PyOpenCL installed for {gpu_name}")
gpu_installed = True
except Exception as e:
print(f" ⚠️ PyOpenCL installation failed: {e}")
print(f" 💡 Manual install: pip install pyopencl")
if not gpu_installed:
print(" ℹ️ No GPU acceleration available — using CPU (NumPy)")
print(" 💡 This is fine! GPU just makes large calculations faster.")
return gpu_installed
def install_frontend():
"""Install frontend dependencies and build."""
print("\n🌐 Setting up frontend...")
frontend_dir = os.path.join(os.path.dirname(__file__), "frontend")
if os.path.exists(os.path.join(frontend_dir, "package.json")):
subprocess.run(["npm", "install"], cwd=frontend_dir, check=True)
subprocess.run(["npm", "run", "build"], cwd=frontend_dir, check=True)
print("✅ Frontend built")
else:
print("⚠️ Frontend directory not found")
def download_terrain_data():
"""Pre-download SRTM terrain tiles for Ukraine."""
print("\n🏔️ Checking terrain data...")
cache_dir = os.path.expanduser("~/.rfcp/terrain")
os.makedirs(cache_dir, exist_ok=True)
# Ukraine bounding box: lat 44-53, lon 22-41
# SRTM tiles needed for typical use
required_tiles = [
# Lviv oblast area (common test area)
"N49E025", "N49E024", "N49E026",
"N50E025", "N50E024", "N50E026",
# Dnipro area
"N48E034", "N48E035",
"N49E034", "N49E035",
]
existing = [f.replace(".hgt", "") for f in os.listdir(cache_dir) if f.endswith(".hgt")]
missing = [t for t in required_tiles if t not in existing]
if missing:
print(f" {len(missing)} terrain tiles needed (auto-download on first use)")
else:
print(f" ✅ {len(existing)} terrain tiles cached")
def create_launcher():
"""Create desktop shortcut / launcher script."""
print("\n🚀 Creating launcher...")
if platform.system() == "Windows":
# Create .bat launcher
launcher = os.path.join(os.path.dirname(__file__), "RFCP.bat")
with open(launcher, 'w') as f:
f.write('@echo off\n')
f.write('title RFCP - RF Coverage Planner\n')
f.write('echo Starting RFCP...\n')
f.write(f'cd /d "{os.path.dirname(__file__)}"\n')
f.write(f'"{sys.executable}" -m uvicorn backend.app.main:app --host 0.0.0.0 --port 8888\n')
print(f" ✅ Launcher created: {launcher}")
return True
def verify_installation():
"""Run quick verification tests."""
print("\n🔍 Verifying installation...")
checks = []
# Check core imports
try:
import numpy as np
checks.append(f"✅ NumPy {np.__version__}")
except ImportError:
checks.append("❌ NumPy missing")
try:
import scipy
checks.append(f"✅ SciPy {scipy.__version__}")
except ImportError:
checks.append("❌ SciPy missing")
try:
import fastapi
checks.append(f"✅ FastAPI {fastapi.__version__}")
except ImportError:
checks.append("❌ FastAPI missing")
try:
import shapely
checks.append(f"✅ Shapely {shapely.__version__}")
except ImportError:
checks.append("⚠️ Shapely missing (boundary features disabled)")
# Check GPU
try:
import cupy as cp
device = cp.cuda.Device(0)
checks.append(f"✅ CuPy → {device.name} ({device.mem_info[1]//1024//1024} MB)")
except ImportError:
checks.append("ℹ️ CuPy not available")
except Exception as e:
checks.append(f"⚠️ CuPy error: {e}")
try:
import pyopencl as cl
devices = []
for p in cl.get_platforms():
for d in p.get_devices():
devices.append(d.name)
checks.append(f"✅ PyOpenCL → {', '.join(devices)}")
except ImportError:
checks.append("ℹ️ PyOpenCL not available")
except Exception as e:
checks.append(f"⚠️ PyOpenCL error: {e}")
for check in checks:
print(f" {check}")
return all("❌" not in c for c in checks)
def main():
"""Main installer entry point."""
print("=" * 60)
print(" RFCP — RF Coverage Planner — Installer")
print("=" * 60)
print()
# Step 1: Check prerequisites
print("📋 Checking prerequisites...")
if not check_python():
sys.exit(1)
check_node()
# Step 2: Detect GPU
gpus = detect_gpu()
# Step 3: Install dependencies
install_core_dependencies()
install_gpu_dependencies(gpus)
# Step 4: Frontend
install_frontend()
# Step 5: Terrain data
download_terrain_data()
# Step 6: Launcher
create_launcher()
# Step 7: Verify
print()
success = verify_installation()
# Summary
print()
print("=" * 60)
if success:
print(" ✅ RFCP installed successfully!")
print()
print(" To start RFCP:")
print(" python -m uvicorn backend.app.main:app --port 8888")
print(" Then open: http://localhost:8888")
print()
if gpus["nvidia"]:
print(f" 🎮 GPU: {gpus['nvidia_name']} (CUDA)")
elif gpus["intel"] or gpus["amd"]:
gpu_name = gpus["intel_name"] or gpus["amd_name"]
print(f" 🎮 GPU: {gpu_name} (OpenCL)")
else:
print(" 💻 Mode: CPU only")
else:
print(" ⚠️ Installation completed with warnings")
print(" Some features may be limited")
print("=" * 60)
if __name__ == "__main__":
main()
Electron-Builder / NSIS Packaging
For .exe Installer
# electron-builder.yml
appId: com.rfcp.coverage-planner
productName: "RFCP - RF Coverage Planner"
copyright: "RFCP 2026"
directories:
output: dist
buildResources: build
files:
- "backend/**/*"
- "frontend/dist/**/*"
- "requirements.txt"
- "install_rfcp.py"
- "!**/*.pyc"
- "!**/node_modules/**"
- "!**/venv/**"
extraResources:
- from: "python-embedded/"
to: "python/"
- from: "terrain-data/"
to: "terrain/"
win:
target:
- target: nsis
arch: [x64]
icon: "build/icon.ico"
nsis:
oneClick: false
allowToChangeInstallationDirectory: true
installerIcon: "build/icon.ico"
license: "LICENSE.md"
# Custom NSIS script for GPU detection
include: "build/gpu-detect.nsh"
# Install steps:
# 1. Extract files
# 2. Run install_rfcp.py (detects GPU, installs deps)
# 3. Create Start Menu shortcuts
# 4. Create Desktop shortcut
Portable Version (.zip)
RFCP-Portable/
├── RFCP.bat # Main launcher
├── install.bat # First-time setup
├── backend/
│ ├── app/
│ │ ├── main.py
│ │ ├── api/
│ │ ├── services/
│ │ └── models/
│ └── requirements.txt
├── frontend/
│ └── dist/ # Pre-built frontend
├── python/ # Embedded Python (optional)
│ ├── python.exe
│ └── Lib/
├── terrain/ # Pre-cached .hgt files
│ ├── N49E025.hgt
│ └── ...
├── data/
│ ├── osm_cache.db # SQLite cache (created on first run)
│ └── config.json # User settings
└── README.md
install.bat (First-Time Setup)
@echo off
title RFCP - First Time Setup
echo ============================================
echo RFCP - RF Coverage Planner - Setup
echo ============================================
echo.
REM Check if Python exists
python --version >nul 2>&1
if errorlevel 1 (
echo ERROR: Python not found!
echo Please install Python 3.10+ from python.org
pause
exit /b 1
)
REM Run installer
python install_rfcp.py
echo.
echo Setup complete! Run RFCP.bat to start.
pause
RFCP.bat (Launcher)
@echo off
title RFCP - RF Coverage Planner
cd /d "%~dp0"
REM Check if installed
if not exist "backend\app\main.py" (
echo ERROR: RFCP not found. Run install.bat first.
pause
exit /b 1
)
echo Starting RFCP...
echo Open http://localhost:8888 in your browser
echo Press Ctrl+C to stop
echo.
python -m uvicorn backend.app.main:app --host 0.0.0.0 --port 8888
Dependency Size Estimates
| Component | Size |
|---|---|
| Python (embedded) | ~30 MB |
| Core pip packages | ~80 MB |
| CuPy + CUDA runtime | ~700 MB |
| PyOpenCL | ~15 MB |
| Frontend (built) | ~5 MB |
| SRTM terrain (Ukraine) | ~300 MB |
| Total (with CUDA) | ~1.1 GB |
| Total (CPU only) | ~415 MB |
Runtime Requirements
| Resource | Minimum | Recommended |
|---|---|---|
| RAM | 4 GB | 8+ GB |
| Disk | 500 MB | 2 GB (with terrain cache) |
| CPU | 4 cores | 8+ cores |
| GPU | - | NVIDIA GTX 1060+ / Intel UHD 630+ |
| OS | Windows 10 | Windows 10/11 64-bit |
| Python | 3.10 | 3.11+ |
| Node.js | 18 | 20 LTS |
Auto-Update Mechanism (Future)
# Check for updates on startup
async def check_for_updates():
try:
response = await httpx.get(
"https://api.github.com/repos/user/rfcp/releases/latest",
timeout=5
)
latest = response.json()["tag_name"]
current = get_current_version()
if latest != current:
return {
"update_available": True,
"current": current,
"latest": latest,
"download_url": response.json()["assets"][0]["browser_download_url"]
}
except:
pass
return {"update_available": False}