$ErrorActionPreference = "Stop" $LogFile = Join-Path $env:TEMP "xlight-upgrade.log" $ActivationSubmitted = $false trap { ($_ | Out-String) | Out-File $LogFile -Append -Encoding UTF8 Write-Host "" Write-Host "XLight firmware upgrade was not completed." -ForegroundColor Red Write-Host "Reason: $($_.Exception.Message)" -ForegroundColor Yellow if ($ActivationSubmitted) { Write-Host "Activation status: the code was submitted. Contact support before retrying." } else { Write-Host "Activation status: the code was NOT submitted and can be used again." } Write-Host "Technical log: $LogFile" exit 1 } $Server = "https://light.chuncui.icu/activation" $WorkDir = Join-Path $env:TEMP ("xlight-upgrade-" + [guid]::NewGuid().ToString()) $Firmware = Join-Path $WorkDir "xlight-merged.bin" $Venv = Join-Path $WorkDir "venv" function Find-XLightPort([string]$RequestedPort) { if ($RequestedPort) { return $RequestedPort } $ports = Get-CimInstance Win32_SerialPort | Where-Object { $_.Name -match "ESP32|USB JTAG|USB Serial|Espressif" -or $_.PNPDeviceID -match "VID_303A" } if (@($ports).Count -ne 1) { throw "XLight COM port was not detected uniquely. Run this script with a port, for example: .\xlight-upgrade-windows.ps1 COM5" } return @($ports)[0].DeviceID } function Send-BridgeControl([string]$Control) { $Client = New-Object System.Net.Sockets.TcpClient try { $Client.Connect("127.0.0.1", 18266) $Stream = $Client.GetStream() $Payload = [Text.Encoding]::UTF8.GetBytes( (@{ control = $Control } | ConvertTo-Json -Compress) ) $Stream.Write($Payload, 0, $Payload.Length) $Buffer = New-Object byte[] 64 $Read = $Stream.Read($Buffer, 0, $Buffer.Length) return [Text.Encoding]::UTF8.GetString($Buffer, 0, $Read).Trim() } catch { return "" } finally { $Client.Close() } } function Stop-LegacyXLight { $Dest = Join-Path $HOME ".xlight" Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -and $_.CommandLine -like "*$Dest*" } | ForEach-Object { if ($_.ProcessId -ne $PID) { & cmd.exe /d /c "taskkill /PID $($_.ProcessId) /T /F >nul 2>&1" } } Start-Sleep -Seconds 2 } function Start-XLightBridge { $Vbs = Join-Path $HOME ".xlight\bin\xlight-daemon.vbs" if (Test-Path $Vbs) { Start-Process "wscript.exe" -ArgumentList "`"$Vbs`"" -WindowStyle Hidden ` -ErrorAction SilentlyContinue } } function Find-Python { $Candidates = @() foreach ($Name in @("python", "python3")) { $Command = Get-Command $Name -ErrorAction SilentlyContinue if ($Command) { $Candidates += ,@{ Command = $Command.Source; Args = @() } } } $Launcher = Get-Command py -ErrorAction SilentlyContinue if ($Launcher) { $Candidates = @(,@{ Command = $Launcher.Source; Args = @("-3") }) + $Candidates } foreach ($Pattern in @( "$env:LOCALAPPDATA\Programs\Python\Python*\python.exe", "$env:ProgramFiles\Python*\python.exe", "${env:ProgramFiles(x86)}\Python*\python.exe" )) { Get-Item $Pattern -ErrorAction SilentlyContinue | ForEach-Object { $Candidates += ,@{ Command = $_.FullName; Args = @() } } } foreach ($Candidate in $Candidates) { try { $Output = & $Candidate.Command @($Candidate.Args) -c ` "import sys; print(sys.executable); raise SystemExit(0 if sys.version_info >= (3,9) else 1)" 2>$null if ($LASTEXITCODE -eq 0 -and $Output) { return $Candidate } } catch {} } return $null } $Python = Find-Python if (-not $Python) { throw "Python 3.9 or newer was not found. Install it from https://www.python.org/downloads/windows/ and enable Add Python to PATH." } $Port = Find-XLightPort $args[0] $BridgePaused = $false $LegacyStopped = $false do { $Code = (Read-Host "Enter activation code only (format: XL-XXXXX-XXXXX-XXXXX)").Trim().ToUpperInvariant() if ($Code -notmatch "^XL-[A-HJ-NP-Z2-9]{5}-[A-HJ-NP-Z2-9]{5}-[A-HJ-NP-Z2-9]{5}$") { Write-Warning "Invalid activation code format. Contact WeChat orz_8888888 for a code." $Code = "" } } while (-not $Code) try { New-Item -ItemType Directory -Force $WorkDir | Out-Null Write-Host "Python selected: $($Python.Command) $($Python.Args -join ' ')" Write-Host "[1/5] Creating a temporary Python environment..." & $Python.Command @($Python.Args) -m venv $Venv if ($LASTEXITCODE -ne 0) { throw "Could not create the Python virtual environment. Exit code: $LASTEXITCODE" } $ToolPython = Join-Path $Venv "Scripts\python.exe" if (-not (Test-Path $ToolPython)) { throw "The virtual environment was created without python.exe." } Write-Host "[2/5] Installing esptool..." & $ToolPython -m pip install --disable-pip-version-check --timeout 120 ` --retries 5 --index-url "https://pypi.tuna.tsinghua.edu.cn/simple" ` "esptool==5.3.0" if ($LASTEXITCODE -ne 0) { Write-Warning "The Tsinghua mirror failed. Retrying with the official Python package server..." & $ToolPython -m pip install --disable-pip-version-check --timeout 120 ` --retries 5 "esptool==5.3.0" } if ($LASTEXITCODE -ne 0) { throw "esptool installation failed on both package servers. Switch to a stable internet connection and run the script again. Exit code: $LASTEXITCODE" } Write-Host "[3/5] Asking XLight to release serial port $Port..." $Reply = Send-BridgeControl "pause" if ($Reply -eq "PAUSED") { $BridgePaused = $true } else { Write-Warning "The installed XLight bridge is old. Stopping it for this upgrade..." Stop-LegacyXLight $LegacyStopped = $true } Start-Sleep -Seconds 1 & $ToolPython -m esptool --chip esp32c3 --port $Port chip-id if ($LASTEXITCODE -ne 0) { throw "Serial port $Port is busy or unavailable. Close Arduino, serial monitors and other XLight windows, then run this script again. The activation code was NOT submitted." } $Body = @{ code = $Code; device_mac = $Port } | ConvertTo-Json Write-Host "[4/5] Validating the activation code and downloading firmware..." $Activation = Invoke-RestMethod -Method Post -Uri "$Server/activate" ` -ContentType "application/json; charset=utf-8" -Body $Body $ActivationSubmitted = $true Invoke-WebRequest -Uri ($Server + $Activation.download_url) ` -OutFile $Firmware -UseBasicParsing Write-Host "[5/5] Flashing XLight firmware to $Port..." & $ToolPython -m esptool --chip esp32c3 --port $Port --baud 921600 ` write-flash 0x0 $Firmware if ($LASTEXITCODE -ne 0) { throw "Firmware flashing failed. Exit code: $LASTEXITCODE" } Write-Host "" Write-Host "Firmware upgrade completed successfully." -ForegroundColor Green Write-Host "Wait for XLight to restart. Do not unplug it during restart." } finally { if ($BridgePaused) { [void](Send-BridgeControl "resume") } if ($LegacyStopped) { Start-XLightBridge } Remove-Item $WorkDir -Recurse -Force -ErrorAction SilentlyContinue }