diff --git a/.azure-pipelines/azure-pipelines.yml b/.azure-pipelines/azure-pipelines.yml index d96dc95b34..54ae01b13f 100644 --- a/.azure-pipelines/azure-pipelines.yml +++ b/.azure-pipelines/azure-pipelines.yml @@ -49,17 +49,6 @@ parameters: stages: -########################################### -# WAKE-UP SIGNAL # -########################################### -#- template: templates/wake-signal.yml -# parameters: -# stageName: Wake_Up_Signal -# displayName: 'Send Wake-Up Signal' -# dependsOn: [] -# poolName: AlarmClock -# condition: always() - ########################################### # SET VERSION BUILD NUMBER # ########################################### @@ -84,6 +73,25 @@ stages: echo "##vso[build.addbuildtag]${{ parameters.buildType }}" displayName: 'Set Build Number to Version' +########################################### +# WAKE SIGNAL # +########################################### +- template: templates/sleep-wake-signal.yml + parameters: + stageName: Sleep_Wake_Signal + displayName: 'Send Wake Signal' + dependsOn: Set_Build_Number + poolName: AlarmClock + action: Wake + condition: | + and( + succeeded(), + or( + eq('${{ parameters.targetOS }}', 'Windows'), + eq('${{ parameters.targetOS }}', 'Linux'), + eq('${{ parameters.targetOS }}', 'All') + ) + ) ########################################### # WINDOWS BUILD JOB # @@ -92,8 +100,7 @@ stages: - stage: Windows_Build_x64 displayName: Windows Build x64 dependsOn: - #- Wake_Up_Signal - - Set_Build_Number + - Sleep_Wake_Signal condition: and(succeeded(), or(eq('${{ parameters.targetOS }}', 'Windows'), eq('${{ parameters.targetOS }}', 'All'))) jobs: @@ -281,8 +288,7 @@ stages: - stage: Linux_Build_x64 displayName: Linux Build x64 dependsOn: - #- Wake_Up_Signal - - Set_Build_Number + - Sleep_Wake_Signal condition: and(succeeded(), or(eq('${{ parameters.targetOS }}', 'Linux'), eq('${{ parameters.targetOS }}', 'All'))) jobs: @@ -388,9 +394,26 @@ stages: chmod +x appimagetool-x86_64.AppImage chmod +x linuxdeploy-plugin-gstreamer.sh + echo "=== Copying Wayland platform plugins ===" + mkdir -p "$APP_DIR/usr/plugins/platforms" + cp /opt/Qt/$(qt_version)/gcc_64/plugins/platforms/libqwayland*.so \ + "$APP_DIR/usr/plugins/platforms/" 2>/dev/null || true + + mkdir -p "$APP_DIR/usr/plugins/wayland-decoration-client" + cp -a /opt/Qt/$(qt_version)/gcc_64/plugins/wayland-decoration-client/. \ + "$APP_DIR/usr/plugins/wayland-decoration-client/" 2>/dev/null || true + + mkdir -p "$APP_DIR/usr/plugins/wayland-graphics-integration-client" + cp -a /opt/Qt/$(qt_version)/gcc_64/plugins/wayland-graphics-integration-client/. \ + "$APP_DIR/usr/plugins/wayland-graphics-integration-client/" 2>/dev/null || true + + mkdir -p "$APP_DIR/usr/plugins/wayland-shell-integration" + cp -a /opt/Qt/$(qt_version)/gcc_64/plugins/wayland-shell-integration/. \ + "$APP_DIR/usr/plugins/wayland-shell-integration/" 2>/dev/null || true + echo "=== Running linuxdeploy to package AppImage ===" export QMAKE="/opt/Qt/$(qt_version)/gcc_64/bin/qmake" - export EXTRA_QT_PLUGINS="multimedia" + export EXTRA_QT_PLUGINS="multimedia;waylandcompositor" ./linuxdeploy-x86_64.AppImage \ --appdir "$APP_DIR" \ -d "$APP_DIR/usr/share/applications/SerialPrograms.desktop" \ @@ -407,6 +430,7 @@ stages: echo "=== Patching AppRun to force GStreamer backend ===" sed -i '2i export PA_APPIMAGE_DIR=$(cd "$(dirname "$APPIMAGE")" && pwd)' "$APP_DIR/AppRun" sed -i '2i export QT_MEDIA_BACKEND=gstreamer' "$APP_DIR/AppRun" + sed -i '2i if [ -n "$WAYLAND_DISPLAY" ] && [ -z "$QT_QPA_PLATFORM" ]; then export QT_QPA_PLATFORM=wayland; elif [ -z "$QT_QPA_PLATFORM" ]; then export QT_QPA_PLATFORM=xcb; fi' "$APP_DIR/AppRun" echo "=== Packaging AppImage ===" ./appimagetool-x86_64.AppImage AppDir @@ -551,16 +575,25 @@ stages: condition: succeeded() ########################################### -# SLEEP # +# SLEEP SIGNAL # ########################################### -#- template: templates/sleep-signal.yml -# parameters: -# stageName: Sleep_Signal -# displayName: 'Send Sleep Signal' -# dependsOn: -# - Upload_Debug_Symbols -# poolName: AlarmClock -# condition: always() +- template: templates/sleep-wake-signal.yml + parameters: + stageName: Sleep_Wake_Signal2 + displayName: 'Send Sleep Signal' + dependsOn: + - Upload_Debug_Symbols + poolName: AlarmClock + action: Sleep + condition: | + and( + in(dependencies.Sleep_Wake_Signal.result, 'Succeeded'), + or( + eq('${{ parameters.targetOS }}', 'Windows'), + eq('${{ parameters.targetOS }}', 'Linux'), + eq('${{ parameters.targetOS }}', 'All') + ) + ) ########################################## # ARTIFACT VALIDATION # diff --git a/.azure-pipelines/templates/sleep-signal.yml b/.azure-pipelines/templates/sleep-signal.yml deleted file mode 100644 index 52950a5ace..0000000000 --- a/.azure-pipelines/templates/sleep-signal.yml +++ /dev/null @@ -1,51 +0,0 @@ -# templates/sleep-signal.yml -# Template for putting Windows/Linux host to sleep - -parameters: - stageName: '' - displayName: '' - dependsOn: [] - condition: '' - -stages: - - stage: ${{ parameters.stageName }} - displayName: ${{ parameters.displayName }} - dependsOn: ${{ parameters.dependsOn }} - condition: ${{ parameters.condition }} - - jobs: - - job: SendSleepSignal - displayName: Send Sleep Signal - pool: - name: ${{ parameters.poolName }} - timeoutInMinutes: 10 - steps: - - checkout: none - - - script: | - if [ -z "$HOST_IP" ] || [ -z "$HOST_PORT" ] || [ -z "$HOST_SECRET" ]; then - echo "❌ One or more required environment variables are missing." - exit 1 - fi - - URL="http://${HOST_IP}:${HOST_PORT}/sleep/" - echo "Sending POST to the host" - HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ - -X POST \ - -H "X-Auth: ${HOST_SECRET}" \ - -H "Content-Length: 0" \ - "$URL") - - echo "HTTP response code: $HTTP_CODE" - if [[ "$HTTP_CODE" =~ ^2 ]]; then - echo "✅ Sleep signal sent successfully." - else - echo "❌ Failed to send sleep signal (HTTP $HTTP_CODE)" - exit 1 - fi - displayName: 'Send Sleep Signal' - condition: succeeded() - env: - HOST_IP: $(HOST_IP) - HOST_PORT: $(HOST_PORT) - HOST_SECRET: $(HOST_SECRET) diff --git a/.azure-pipelines/templates/sleep-wake-signal.yml b/.azure-pipelines/templates/sleep-wake-signal.yml new file mode 100644 index 0000000000..409e6d381c --- /dev/null +++ b/.azure-pipelines/templates/sleep-wake-signal.yml @@ -0,0 +1,78 @@ +# templates/sleep-wake-signal.yml +# Template for putting Windows/Linux host to sleep/wake via USB relay controller +# AT commands: AT+CH1=1 (close relay), AT+CH1=0 (open relay) + +parameters: + stageName: '' + displayName: '' + dependsOn: [] + condition: '' + action: '' # 'Wake' or 'Sleep' + +stages: + - stage: ${{ parameters.stageName }} + displayName: ${{ parameters.displayName }} + dependsOn: ${{ parameters.dependsOn }} + condition: ${{ parameters.condition }} + + jobs: + - job: SendSleepWakeSignal + displayName: Send Sleep/Wake Signal via Relay + pool: + name: ${{ parameters.poolName }} + timeoutInMinutes: 10 + steps: + - checkout: none + + - task: UsePythonVersion@0 + inputs: + versionSpec: '3.14' + addToPath: true + name: UsePythonVersion + displayName: 'Use Python 3.14' + + - powershell: | + $action = "${{ parameters.action }}" + $pingUrl = "http://$($env:HOST_IP):$($env:HOST_PORT)/ping/" + $isAwake = $false + + try { + $response = Invoke-WebRequest -Uri $pingUrl -Headers @{ "X-Auth" = $env:HOST_SECRET } -TimeoutSec 10 -UseBasicParsing -ErrorAction Stop + if ($response.Content.Trim() -eq "ack") { + Write-Host "Ping response: ack. Host is awake." + $isAwake = $true + } else { + Write-Host "Ping response: unexpected content '$($response.Content.Trim())'. Assuming asleep." + } + } catch { + Write-Host "Ping failed or no response. Assuming host is asleep: $_" + } + + if ($action -eq "Wake" -and $isAwake) { + Write-Host "Action is 'Wake' but host is already awake. Skipping script." + } elseif ($action -eq "Sleep" -and -not $isAwake) { + Write-Host "Action is 'Sleep' but host is already asleep. Skipping script." + } else { + Write-Host "Running relay script for action: $action" + $f = "$env:TEMP\relay.py" + Set-Content $f "import serial" + Add-Content $f "import time" + Add-Content $f "with serial.Serial('COM3', 9600, timeout=1) as ser:" + Add-Content $f " print('Closing the relay...')" + Add-Content $f " ser.write(b'AT+CH1=1\r\n')" + Add-Content $f " time.sleep(0.5)" + Add-Content $f " print('Opening the relay...')" + Add-Content $f " ser.write(b'AT+CH1=0\r\n')" + python $f + + if ($LASTEXITCODE -ne 0) { + Write-Error "Relay script failed with exit code $LASTEXITCODE" + exit $LASTEXITCODE + } + } + displayName: 'Send ${{ parameters.action }} Signal via Relay' + condition: succeeded() + env: + HOST_IP: $(host_ip) + HOST_PORT: $(host_port) + HOST_SECRET: $(host_secret) diff --git a/.azure-pipelines/templates/wake-signal.yml b/.azure-pipelines/templates/wake-signal.yml deleted file mode 100644 index 46291c203e..0000000000 --- a/.azure-pipelines/templates/wake-signal.yml +++ /dev/null @@ -1,53 +0,0 @@ -# templates/wake-signal.yml -# Template for waking up the Windows/Linux host - -parameters: - stageName: '' - displayName: '' - dependsOn: [] - condition: '' - -stages: - - stage: ${{ parameters.stageName }} - displayName: ${{ parameters.displayName }} - dependsOn: ${{ parameters.dependsOn }} - condition: ${{ parameters.condition }} - - jobs: - - job: SendWakeUpSignal - displayName: Send Wake-Up Signal - pool: - name: ${{ parameters.poolName }} - timeoutInMinutes: 10 - steps: - - checkout: none - - - script: | - macAddress="${HOST_MAC}" - macAddressCleaned=$(echo "$macAddress" | tr -d ':-') - if [ ${#macAddressCleaned} -ne 12 ]; then - echo "❌ Invalid MAC address format" - exit 1 - fi - - magicPacket="" - for i in {1..6}; do - magicPacket="${magicPacket}ff" - done - - for i in {1..16}; do - magicPacket="${magicPacket}${macAddressCleaned}" - done - - if [ -n "$HOST_IP" ]; then - echo "$magicPacket" | xxd -r -p | nc -w1 -u "$HOST_IP" 9 - echo "✅ Sent directly to $HOST_IP:9" - fi - - echo "$magicPacket" | xxd -r -p | nc -b -w1 -u 255.255.255.255 9 - echo "✅ Sent to global broadcast (255.255.255.255:9)" - displayName: 'Send Wake-Up Signal' - condition: succeeded() - env: - HOST_MAC: $(HOST_MAC) - HOST_IP: $(HOST_IP)