# Restart the WLED Screen Controller server # Uses graceful shutdown first (lets the server persist data to disk), # then force-kills as a fallback. $serverRoot = 'c:\Users\Alexei\Documents\wled-screen-controller\server' # Read API key from config for authenticated shutdown request $configPath = Join-Path $serverRoot 'config\default_config.yaml' $apiKey = $null if (Test-Path $configPath) { $inKeys = $false foreach ($line in Get-Content $configPath) { if ($line -match '^\s*api_keys:') { $inKeys = $true; continue } if ($inKeys -and $line -match '^\s+\w+:\s*"(.+)"') { $apiKey = $Matches[1]; break } if ($inKeys -and $line -match '^\S') { break } # left the api_keys block } } # Find running server processes $procs = Get-CimInstance Win32_Process -Filter "Name='python.exe'" | Where-Object { $_.CommandLine -like '*wled_controller*' -and $_.CommandLine -notlike '*demo*' -and $_.CommandLine -notlike '*vscode*' -and $_.CommandLine -notlike '*isort*' } if ($procs) { # Step 1: Request graceful shutdown via API (triggers lifespan shutdown + store save) $shutdownOk = $false if ($apiKey) { Write-Host "Requesting graceful shutdown..." try { $headers = @{ Authorization = "Bearer $apiKey" } Invoke-RestMethod -Uri 'http://localhost:8080/api/v1/system/shutdown' ` -Method Post -Headers $headers -TimeoutSec 5 -ErrorAction Stop | Out-Null $shutdownOk = $true } catch { Write-Host " API shutdown failed ($($_.Exception.Message)), falling back to process kill" } } if ($shutdownOk) { # Step 2: Wait for the server to exit gracefully (up to 15 seconds) # The server needs time to stop processors, disconnect devices, and persist stores. Write-Host "Waiting for graceful shutdown..." $waited = 0 while ($waited -lt 15) { Start-Sleep -Seconds 1 $waited++ $still = Get-CimInstance Win32_Process -Filter "Name='python.exe'" | Where-Object { $_.CommandLine -like '*wled_controller*' -and $_.CommandLine -notlike '*demo*' -and $_.CommandLine -notlike '*vscode*' -and $_.CommandLine -notlike '*isort*' } if (-not $still) { Write-Host " Server exited cleanly after ${waited}s" break } } # Step 3: Force-kill stragglers $still = Get-CimInstance Win32_Process -Filter "Name='python.exe'" | Where-Object { $_.CommandLine -like '*wled_controller*' -and $_.CommandLine -notlike '*demo*' -and $_.CommandLine -notlike '*vscode*' -and $_.CommandLine -notlike '*isort*' } if ($still) { Write-Host " Force-killing remaining processes..." foreach ($p in $still) { Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue } Start-Sleep -Seconds 1 } } else { # No API key or API call failed — force-kill directly foreach ($p in $procs) { Write-Host "Stopping server (PID $($p.ProcessId))..." Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue } Start-Sleep -Seconds 2 } } # Merge registry PATH with current PATH so newly-installed tools (e.g. scrcpy) are visible $regUser = [Environment]::GetEnvironmentVariable('PATH', 'User') if ($regUser) { $currentDirs = $env:PATH -split ';' | ForEach-Object { $_.TrimEnd('\') } foreach ($dir in ($regUser -split ';')) { if ($dir -and ($currentDirs -notcontains $dir.TrimEnd('\'))) { $env:PATH = "$env:PATH;$dir" } } } # Start server detached (set WLED_RESTART=1 to skip browser open) Write-Host "Starting server..." $env:WLED_RESTART = "1" $pythonExe = (Get-Command python -ErrorAction SilentlyContinue).Source if (-not $pythonExe) { # Fallback to known install location $pythonExe = "$env:LOCALAPPDATA\Programs\Python\Python313\python.exe" } Start-Process -FilePath $pythonExe -ArgumentList '-m', 'wled_controller' ` -WorkingDirectory $serverRoot ` -WindowStyle Hidden Start-Sleep -Seconds 3 # Verify it's running $check = Get-CimInstance Win32_Process -Filter "Name='python.exe'" | Where-Object { $_.CommandLine -like '*wled_controller*' -and $_.CommandLine -notlike '*demo*' -and $_.CommandLine -notlike '*vscode*' -and $_.CommandLine -notlike '*isort*' } if ($check) { Write-Host "Server started (PID $($check[0].ProcessId))" } else { Write-Host "WARNING: Server does not appear to be running!" }