Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 88 additions & 15 deletions app-runner/Private/DeviceProviders/SwitchProvider.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class SwitchProvider : DeviceProvider {
[string]$TargetControlTool = 'ControlTarget.exe'
[string]$ApplicationRunnerTool = 'RunOnTarget.exe'
[string]$Target = $null # Stores the target device identifier for commands that need explicit --target
[string]$TargetPlatform = $null # Target Manager platform: 'NX' or 'Ounce', resolved from target serial

SwitchProvider() {
$this.Platform = 'Switch'
Expand All @@ -42,9 +43,9 @@ class SwitchProvider : DeviceProvider {
'poweroff' = @($this.TargetControlTool, 'power-off')
'press-power-button' = @($this.TargetControlTool, 'press-power-button')
'reset' = @($this.TargetControlTool, 'reset')
'getstatus' = @($this.TargetControlTool, 'get-default --detail --json', { $input | ConvertFrom-Json })
'getstatus' = @($this.TargetControlTool, 'get-default --detail --platform {0} --json', { $input | ConvertFrom-Json })
'get-default-target' = @($this.TargetControlTool, 'get-default --json', { $input | ConvertFrom-Json })
'set-default-target' = @($this.TargetControlTool, 'set-default --target "{0}"')
'set-default-target' = @($this.TargetControlTool, 'set-default --target "{0}" --platform {1}')
'list-target' = @($this.TargetControlTool, 'list-target --json', { $input | ConvertFrom-Json })
'detect-target' = @($this.TargetControlTool, 'detect-target --json', { $input | ConvertFrom-Json })
'register-target' = @($this.TargetControlTool, 'register --target "{0}"')
Expand All @@ -66,11 +67,57 @@ class SwitchProvider : DeviceProvider {
if (-not [string]::IsNullOrEmpty($target)) {
Write-Debug "$($this.Platform): Setting target device: $target"
$this.Target = $target
$this.InvokeCommand('set-default-target', @($target))

# Target Manager 2 requires the platform to be specified explicitly.
# Platform can be inferred from the target name (HAL* targets are Ounce/Switch 2, others are NX/Switch 1).
$this.TargetPlatform = if ($target -match '^HAL') { 'Ounce' } else { 'NX' }
Write-Debug "$($this.Platform): Using Target Manager platform: $($this.TargetPlatform)"

# Update device-control commands to include --target so they operate on the correct device
# when multiple targets (NX and Ounce) are registered in Target Manager.
$targetArg = " --target `"$target`""
$this.Commands['connect'] = @($this.TargetControlTool, "connect --force$targetArg")
$this.Commands['disconnect'] = @($this.TargetControlTool, "disconnect$targetArg")
$this.Commands['poweron'] = @($this.TargetControlTool, "power-on$targetArg")
$this.Commands['poweroff'] = @($this.TargetControlTool, "power-off$targetArg")
$this.Commands['press-power-button'] = @($this.TargetControlTool, "press-power-button$targetArg")
$this.Commands['reset'] = @($this.TargetControlTool, "reset$targetArg")

$this.InvokeCommand('set-default-target', @($target, $this.TargetPlatform))
}
return $this.Connect()
}

# Override DetectAndSetDefaultTarget to skip auto-detection when target was explicitly set in Connect
[void] DetectAndSetDefaultTarget() {
if (-not [string]::IsNullOrEmpty($this.Target)) {
Write-Debug "$($this.Platform): Target explicitly set to $($this.Target), skipping auto-detection"
return
}
([DeviceProvider] $this).DetectAndSetDefaultTarget()
}

# Override GetDeviceStatus to include `--platform` arg when querying device status so that it works correctly when multiple platforms are registered
[hashtable] GetDeviceStatus() {
Write-Debug "$($this.Platform): Getting device status"

$platform = if ($this.TargetPlatform) { $this.TargetPlatform } else { 'NX' }
$result = $this.InvokeCommand('getstatus', @($platform))

return @{
Platform = $this.Platform
Status = 'Online'
StatusData = $result
Timestamp = Get-Date
}
}

[bool] TestConnection() {
Write-Debug "$($this.Platform): Testing connection to device"
$this.GetDeviceStatus()
return $true
}

# Override Connect to provide Switch specific wakeup
[hashtable] Connect() {
Write-Debug 'Connecting to Switch Devkit...'
Expand Down Expand Up @@ -99,29 +146,49 @@ class SwitchProvider : DeviceProvider {
}
}

# Recovery steps are best-effort: if one fails, continue to the next
switch ($i) {
0 {
Write-Warning 'Attempting to wake up the Devkit from sleep...'
$this.InvokeCommand('press-power-button', @())
try {
Write-Warning 'Attempting to wake up the Devkit from sleep...'
$this.InvokeCommand('press-power-button', @())
} catch {
Write-Warning "Failed to wake up Devkit: $_"
}
}
1 {
Write-Warning 'Attempting to start the Devkit...'
$this.StartDevice()
try {
Write-Warning 'Attempting to start the Devkit...'
$this.StartDevice()
} catch {
Write-Warning "Failed to start Devkit: $_"
}
}
2 {
Write-Warning 'Attempting to reboot the Devkit...'
$job2 = Start-Job {
ControlTarget power-off
Start-Sleep -Seconds 5
ControlTarget power-on
try {
Write-Warning 'Attempting to reboot the Devkit...'
$powerOffCmd = $this.BuildCommand('poweroff', @()).Command
$powerOnCmd = $this.BuildCommand('poweron', @()).Command
$job2 = Start-Job {
param($offCmd, $onCmd)
Invoke-Expression $offCmd
Start-Sleep -Seconds 5
Invoke-Expression $onCmd
} -ArgumentList $powerOffCmd, $powerOnCmd
Wait-Job $job2 -Timeout 20 | Out-Null
} catch {
Write-Warning "Failed to reboot Devkit: $_"
}
Wait-Job $job2 -Timeout 20 | Out-Null
}
3 {
if ($info.IpAddress -ne $null) {
Write-Warning 'Attempting to reboot host bridge...'
Invoke-WebRequest -Uri "http://$($info.IpAddress)/cgi-bin/config?reboot"
$nextTimeout = 300 # This takes a long time so wait longer next time
try {
Invoke-WebRequest -Uri "http://$($info.IpAddress)/cgi-bin/config?reboot"
$nextTimeout = 300 # This takes a long time so wait longer next time
} catch {
Write-Warning "Failed to reboot host bridge: $_"
}
}
}
}
Expand All @@ -132,6 +199,12 @@ class SwitchProvider : DeviceProvider {
throw 'Failed to connect to the Switch Devkit.'
}

# Verify the device is actually connected after recovery attempts
$finalStatus = $this.GetDeviceStatus().StatusData
if ("$($finalStatus.Status)" -ne 'Connected') {
throw "Failed to connect to the Switch Devkit. Device status: $($finalStatus.Status)"
}

Write-Debug 'Successfully connected to the Switch Devkit.'
return $this.CreateSessionInfo()
}
Expand Down