| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- # ==============================================================================
- # lf_mri_platform -- Start services and GUI
- # Usage:
- # .\start.ps1 -- plug mode: Docker + GUI
- # .\start.ps1 -Mode real -- real mode: Docker + spectrometer + GUI
- # .\start.ps1 -GuiOnly -- GUI only (services already running)
- # .\start.ps1 -ServicesOnly -- Docker + spectrometer, no GUI
- # .\start.ps1 -SkipInstall -- skip venv check
- # .\start.ps1 -SkipSpectrometer -- skip native spectrometer even in real mode
- # ==============================================================================
- param(
- [ValidateSet("plug", "real")]
- [string]$Mode = "plug",
- [switch]$GuiOnly,
- [switch]$ServicesOnly,
- [switch]$SkipInstall,
- [switch]$SkipSpectrometer
- )
- $ErrorActionPreference = "Stop"
- $Root = $PSScriptRoot
- $GuiDir = Join-Path $Root "apps\gui"
- $VenvPython = Join-Path $GuiDir ".venv\Scripts\python.exe"
- $AppScript = Join-Path $Root "apps\gui\app.py"
- $EnvFile = Join-Path $Root ".env"
- $EnvExample = Join-Path $Root ".env.example"
- $LogFile = Join-Path $Root "start_log.txt"
- # Write all output to log file so errors are readable even if window closes
- Start-Transcript -Path $LogFile -Append | Out-Null
- Write-Host "=== start.ps1 $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Mode=$Mode ==="
- function Write-Step($msg) { Write-Host "`n==> $msg" -ForegroundColor Cyan }
- function Write-OK($msg) { Write-Host " [OK] $msg" -ForegroundColor Green }
- function Write-Warn($msg) { Write-Host " [!!] $msg" -ForegroundColor Yellow }
- function Write-Fail($msg) {
- Write-Host "`n[FAIL] $msg" -ForegroundColor Red
- Write-Host " Log: $LogFile" -ForegroundColor DarkGray
- try { Stop-Transcript | Out-Null } catch {}
- Write-Host "`n Press Enter to close..." -ForegroundColor DarkGray
- $null = Read-Host
- exit 1
- }
- # Keep window open on any unhandled error
- trap {
- Write-Host "`n[ERROR] $_" -ForegroundColor Red
- Write-Host " Log: $LogFile" -ForegroundColor DarkGray
- try { Stop-Transcript | Out-Null } catch {}
- Write-Host "`n Press Enter to close..." -ForegroundColor DarkGray
- $null = Read-Host
- exit 1
- }
- function Get-EnvPort($key, $default) {
- if (Test-Path $EnvFile) {
- $line = Get-Content $EnvFile | Select-String "^$key=(\d+)"
- if ($line) { return $line.Matches[0].Groups[1].Value }
- }
- return $default
- }
- # Returns $true if the TCP port is accepting connections.
- # Using TCP instead of HTTP so HTTP 4xx/auth errors don't cause false negatives.
- function Test-ServiceUp($svc) {
- try {
- $uri = [System.Uri]$svc.Url
- $port = if ($uri.Port -gt 0) { $uri.Port } else { 80 }
- $tcp = New-Object System.Net.Sockets.TcpClient
- $ar = $tcp.BeginConnect($uri.Host, $port, $null, $null)
- $ok = $ar.AsyncWaitHandle.WaitOne(1500, $false)
- try { $tcp.Close() } catch {}
- return $ok
- } catch {
- return $false
- }
- }
- # -- 1. Check / install GUI venv -----------------------------------------------
- if (-not $GuiOnly -and -not $SkipInstall) {
- Write-Step "Checking GUI environment"
- $needsInstall = $false
- if (-not (Test-Path $VenvPython)) {
- Write-Warn "Virtual environment not found -- running install"
- $needsInstall = $true
- } else {
- & $VenvPython -c "import PySide6" 2>$null
- if ($LASTEXITCODE -ne 0) {
- Write-Warn "GUI packages missing -- running install"
- $needsInstall = $true
- } else {
- Write-OK "Virtual environment ready"
- }
- }
- if ($needsInstall) {
- $installScript = Join-Path $Root "install.ps1"
- if (-not (Test-Path $installScript)) { Write-Fail "install.ps1 not found" }
- & powershell.exe -ExecutionPolicy Bypass -File $installScript
- if ($LASTEXITCODE -ne 0) { Write-Fail "install.ps1 failed" }
- }
- }
- # -- 2. Docker -----------------------------------------------------------------
- if (-not $GuiOnly) {
- Write-Step "Checking Docker"
- $dockerOk = $false
- try { & docker info *>$null; $dockerOk = $true } catch {}
- if (-not $dockerOk) {
- Write-Warn "Docker not responding -- starting Docker Desktop..."
- $dockerExe = "C:\Program Files\Docker\Docker\Docker Desktop.exe"
- if (Test-Path $dockerExe) { Start-Process $dockerExe }
- else { Write-Fail "Docker Desktop not found -- install from https://docker.com" }
- $waited = 0
- while ($waited -lt 60) {
- Start-Sleep 5
- $waited += 5
- try { & docker info *>$null; $dockerOk = $true; break } catch {}
- Write-Host " ... $waited/60 s" -ForegroundColor DarkGray
- }
- if (-not $dockerOk) { Write-Fail "Docker did not start in time" }
- }
- Write-OK "Docker is running"
- # -- 3. .env ---------------------------------------------------------------
- if (-not (Test-Path $EnvFile)) {
- if (Test-Path $EnvExample) {
- Copy-Item $EnvExample $EnvFile
- Write-Warn ".env created from .env.example"
- } else {
- Write-Warn ".env missing -- using Compose defaults"
- }
- }
- # -- 4. Start containers ---------------------------------------------------
- Write-Step "Starting Docker services (mode: $Mode)"
- $env:ORCHESTRATOR_MODE = $Mode
- $envArgs = if (Test-Path $EnvFile) { @("--env-file", $EnvFile) } else { @() }
- & docker compose @envArgs up -d
- if ($LASTEXITCODE -ne 0) { Write-Fail "docker compose up failed" }
- Write-OK "Containers started"
- # -- 5. Native spectrometer ------------------------------------------------
- if (-not $SkipSpectrometer) {
- Write-Step "Starting native spectrometer"
- $SpecDir = Join-Path $Root "services\spectrometer"
- $SpecVenv = Join-Path $SpecDir "mvenv\Scripts\python.exe"
- $PicoExe = Join-Path $SpecDir "bin\pico-tcp.exe"
- $oldPico = Get-Process -Name "pico-tcp" -ErrorAction SilentlyContinue
- if ($oldPico) { $oldPico | Stop-Process -Force; Write-Warn "Killed stale pico-tcp.exe" }
- if (-not (Test-Path $SpecVenv)) {
- Write-Warn "Spectrometer venv not found -- creating..."
- Push-Location $SpecDir
- & python -m venv mvenv
- if ($LASTEXITCODE -ne 0) { Write-Fail "Failed to create spectrometer venv" }
- Pop-Location
- Write-OK "Spectrometer venv created"
- } else {
- Write-OK "Spectrometer venv found"
- }
- # Always sync packages (catches new deps after git pull)
- Write-Host " Installing/updating spectrometer packages..." -ForegroundColor DarkGray
- Push-Location $SpecDir
- & $SpecVenv -m pip install -q --upgrade pip
- $pipOut = & $SpecVenv -m pip install -q -r requirements.txt 2>&1
- $pipFailed = ($LASTEXITCODE -ne 0)
- Pop-Location
- if ($pipFailed) {
- Write-Warn "pip install had issues:`n$pipOut"
- } else {
- Write-OK "Packages up to date"
- }
- # Migrations -- show output so errors are visible
- Write-Host " Running migrations..." -ForegroundColor DarkGray
- Push-Location $SpecDir
- $migrateOut = & $SpecVenv manage.py migrate --noinput 2>&1
- $migrateFailed = ($LASTEXITCODE -ne 0)
- Pop-Location
- if ($migrateFailed) {
- Write-Warn "Migration warnings:`n$migrateOut"
- } else {
- Write-OK "Migrations applied"
- }
- # pico-tcp.exe
- if (Test-Path $PicoExe) {
- Start-Process $PicoExe -WindowStyle Normal
- Write-OK "pico-tcp.exe started (visible window)"
- } else {
- Write-Warn "bin\pico-tcp.exe not found -- ADC proxy not started"
- }
- # Django runserver -- run via "cmd /k" so window stays open on error
- $specPort = Get-EnvPort "SPECTROMETER_PORT" "8000"
- $alreadyUp = $false
- try {
- $r = Invoke-WebRequest "http://localhost:$specPort/api/" `
- -UseBasicParsing -TimeoutSec 2 -ErrorAction Stop
- $alreadyUp = ($r.StatusCode -lt 400)
- } catch {}
- if ($alreadyUp) {
- Write-OK "Spectrometer already responding on port $specPort -- checking devices..."
- # Fall through to device registration check below
- } else {
- # Title makes the window easy to find in the taskbar.
- # /k keeps the window open even if Django crashes at startup.
- # --noreload disables Django's file-watcher subprocess (cleaner in cmd).
- $runCmd = "`"$SpecVenv`" manage.py runserver 0.0.0.0:$specPort --noreload"
- Start-Process "cmd.exe" `
- -ArgumentList "/k title LF-MRI Spectrometer && $runCmd" `
- -WorkingDirectory $SpecDir `
- -WindowStyle Normal
- Write-OK "Spectrometer started (window: 'LF-MRI Spectrometer', port $specPort)"
- }
- # -- Auto-register hardware devices if DB is empty --------------------
- Write-Host " Waiting for spectrometer API..." -ForegroundColor DarkGray
- $apiReady = $false
- $apiWait = 0
- while ($apiWait -lt 20) {
- if (Test-ServiceUp @{ Url = "http://localhost:$specPort/api/" }) {
- $apiReady = $true; break
- }
- Start-Sleep 1; $apiWait++
- }
- if ($apiReady) {
- try {
- $devResp = Invoke-WebRequest "http://localhost:$specPort/api/devices/" `
- -UseBasicParsing -TimeoutSec 5 -ErrorAction Stop
- $devJson = $devResp.Content | ConvertFrom-Json
- $devCount = if ($devJson.PSObject.Properties['results']) {
- $devJson.results.Count
- } else {
- @($devJson).Count
- }
- if ($devCount -eq 0) {
- Write-Warn "No devices in DB -- registering hardware..."
- $base = "http://localhost:$specPort/api/devices/"
- $hdr = @{ "Content-Type" = "application/json" }
- $devs = @(
- '{"device_type":"ADC", "brend":"Picoscope", "serial_model":"PS4000A", "proto":"adc_default", "proto_interface":"TCP"}',
- '{"device_type":"SDR", "brend":"HackRF", "serial_model":"HackRF", "proto":"sdr_default", "proto_interface":"USB"}',
- '{"device_type":"SYNC", "brend":"Arduino", "serial_model":"DuePP", "proto":"sync_default", "proto_interface":"USB"}',
- '{"device_type":"GRA", "brend":"ITMO", "serial_model":"GRU", "proto":"gra_default", "proto_interface":"UDP"}'
- )
- foreach ($body in $devs) {
- try {
- Invoke-WebRequest $base -Method Post -Headers $hdr `
- -Body $body -UseBasicParsing -TimeoutSec 5 | Out-Null
- $name = ($body | Select-String '"serial_model":"([^"]+)"').Matches[0].Groups[1].Value
- Write-Host " Registered: $name" -ForegroundColor DarkGray
- } catch {
- Write-Warn " Failed to register device: $_"
- }
- }
- Write-OK "Hardware devices registered"
- } else {
- Write-OK "Devices already in DB ($devCount found)"
- }
- } catch {
- Write-Warn "Could not check/register devices: $_"
- }
- } else {
- Write-Warn "Spectrometer API not ready in 20 s -- skipping device registration"
- }
- }
- # -- 6. Health check -------------------------------------------------------
- Write-Step "Waiting for services to become healthy"
- $checkSpec = -not $SkipSpectrometer
- $services = @(
- @{ Name = "Orchestrator"; Url = "http://localhost:$(Get-EnvPort 'ORCHESTRATOR_PORT' '1717')/health"; Required = $true },
- @{ Name = "Seq-Interp"; Url = "http://localhost:$(Get-EnvPort 'SEQ_INTERP_PORT' '7475')/health"; Required = $true },
- @{ Name = "Reconstructor"; Url = "http://localhost:$(Get-EnvPort 'RECONSTRUCTOR_PORT' '8081')/health"; Required = $true },
- @{ Name = "Spectroscopy"; Url = "http://localhost:$(Get-EnvPort 'SPECTROSCOPY_PORT' '8002')/health"; Required = $true },
- @{ Name = "Spectrometer"; Url = "http://localhost:$(Get-EnvPort 'SPECTROMETER_PORT' '8000')/api/"; Required = $checkSpec; AllowedCodes = @(200,301,302,401,403) }
- )
- $maxWait = 120; $interval = 3; $elapsed = 0
- while ($elapsed -lt $maxWait) {
- $pending = @()
- foreach ($svc in $services) {
- if (-not $svc.Required) { continue }
- if (-not (Test-ServiceUp $svc)) { $pending += $svc.Name }
- }
- if ($pending.Count -eq 0) { Write-OK "All required services healthy"; break }
- Write-Host (" ... {0}/{1} s waiting: {2}" -f $elapsed, $maxWait, ($pending -join ", ")) -ForegroundColor DarkGray
- Start-Sleep $interval
- $elapsed += $interval
- }
- if ($elapsed -ge $maxWait) { Write-Warn "Some services did not respond in time -- continuing anyway" }
- Write-Host ""
- foreach ($svc in $services) {
- $ok = Test-ServiceUp $svc
- $icon = if ($ok) { "[OK]" } else { "[--]" }
- $color = if ($ok) { "Green" } elseif ($svc.Required) { "Yellow" } else { "DarkGray" }
- $suffix = if (-not $svc.Required -and -not $ok) { " (native -- start manually)" } else { "" }
- Write-Host (" {0,-6} {1,-16} {2}{3}" -f $icon, $svc.Name, $svc.Url, $suffix) -ForegroundColor $color
- }
- # -- 7. Write server_config.json -------------------------------------------
- Write-Step "Configuring GUI"
- $orchPort = Get-EnvPort "ORCHESTRATOR_PORT" "1717"
- $seqPort = Get-EnvPort "SEQ_INTERP_PORT" "7475"
- $spectPort = Get-EnvPort "SPECTROSCOPY_PORT" "8002"
- $cfgPath = Join-Path $Root "apps\gui\cfg\server_config.json"
- $cfgObj = @{
- srv_name = "srv_interp"
- log_dir = "log"
- upload_dir = "data/input"
- output_dir = "data/output"
- server_host = "0.0.0.0"
- server_port = [int]$seqPort
- orchestrator_url = "http://localhost:$orchPort"
- seq_interp_url = "http://localhost:$seqPort"
- spectroscopy_url = "http://localhost:$spectPort"
- mode = $Mode
- }
- $cfgObj | ConvertTo-Json -Depth 3 | Set-Content $cfgPath -Encoding utf8
- Write-OK "server_config.json updated (mode=$Mode orch=:$orchPort seq=:$seqPort spectro=:$spectPort)"
- }
- # -- 8. Launch GUI -------------------------------------------------------------
- if (-not $ServicesOnly) {
- Write-Step "Launching GUI"
- if (-not (Test-Path $VenvPython)) {
- Write-Warn "Venv not found -- falling back to system Python"
- $VenvPython = "python"
- }
- if (-not (Test-Path $AppScript)) { Write-Fail "GUI entry point not found: $AppScript" }
- Start-Process $VenvPython -ArgumentList "`"$AppScript`"" -WorkingDirectory $Root -WindowStyle Normal
- Write-OK "GUI launched"
- }
- # -- Summary -------------------------------------------------------------------
- Write-Host ""
- Write-Host "============================================================" -ForegroundColor Green
- Write-Host (" LF-MRI platform is running [{0} mode]" -f $Mode.ToUpper()) -ForegroundColor Green
- Write-Host "============================================================" -ForegroundColor Green
- Write-Host ""
- if (-not $SkipSpectrometer) {
- Write-Host " Stop spectrometer: close the spectrometer terminal window"
- Write-Host " Stop pico-tcp: services\spectrometer\autokill.bat"
- }
- Write-Host " Stop all: .\stop.ps1"
- Write-Host " Update code: .\update.bat"
- Write-Host ""
- Write-Host " Log saved to: $LogFile" -ForegroundColor DarkGray
- try { Stop-Transcript | Out-Null } catch {}
- Write-Host " Press Enter to close this window..." -ForegroundColor DarkGray
- $null = Read-Host
|