657 lines
18 KiB
Markdown
657 lines
18 KiB
Markdown
# 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)
|
||
|
||
```txt
|
||
# 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)
|
||
|
||
```txt
|
||
# 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
|
||
|
||
```txt
|
||
# 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)
|
||
|
||
```json
|
||
// 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)
|
||
|
||
```python
|
||
# 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
|
||
|
||
```yaml
|
||
# 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)
|
||
|
||
```batch
|
||
@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)
|
||
|
||
```batch
|
||
@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)
|
||
|
||
```python
|
||
# 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}
|
||
```
|