12. Resume the controller setup after the PC reboot
12.1 Know what the reboot did—and did not—reset
A Windows restart stopped OBS, the temporary PowerShell bridge, and the active Parsec session. It did not erase:
- The HORI/Pokkén firmware on the Pico.
- The Python environment under
C:\RemoteSwitch\switch-pico-sdl2. - The Parsec Virtual USB driver.
- The OBS profile and scene.
- The UART wiring.
For normal operation, leave the hardware connected like this:
Windows host USB
-> Raspberry Pi Debug Probe
-> UART U port
-> GPIO4 / GPIO5 / GND on Pico
Switch dock USB
-> Pico Micro-USB data port
The Pico belongs on the Switch dock. The Debug Probe belongs on the Windows PC.
12.2 Confirm the working bridge folder survived
Sign in to the dedicated remote-play Windows account. Open a normal, non-Administrator PowerShell window and run:
whoami
Test-Path C:\RemoteSwitch\switch-pico-sdl2\.venv\Scripts\python.exe
C:\RemoteSwitch\switch-pico-sdl2\.venv\Scripts\python.exe -c "import sys; print(sys._base_executable)"
git -C C:\RemoteSwitch\switch-pico-sdl2 rev-parse --short HEAD
Expected results:
...\RemoteSwitch
True
C:\Users\RemoteSwitch\AppData\Local\Python\pythoncore-3.13-64\python.exe
e13ba50
The exact computer name and Python patch release may differ. The important requirement is that the account and base-Python path belong to the same RemoteSwitch account that will run Parsec and the scheduled bridge. If the command reports Access is denied or points into another profile such as C:\Users\admin, the virtual environment is account-bound incorrectly. Do not run the bridge elevated; use the repair procedure in Step 14.2.
If Test-Path says False, do not continue to the bridge command. While still in the normal RemoteSwitch PowerShell session, recreate the pinned SDL2 environment:
Set-Location C:\RemoteSwitch
git clone https://github.com/jyapayne/switch-pico.git switch-pico-sdl2
Set-Location C:\RemoteSwitch\switch-pico-sdl2
git checkout e13ba506cfc31fa8f02f50a9ac2a445d1ecf246b
pymanager install 3.13
pymanager exec -V:3.13 -m venv .venv
.\.venv\Scripts\python.exe -m pip install --upgrade pip
.\.venv\Scripts\python.exe -m pip install -e .
.\.venv\Scripts\python.exe -m pip install "PySDL2-dll==2.32.10"
If pymanager is unavailable, install Python Install Manager for the current RemoteSwitch user using the command in Step 14.2, reopen a normal PowerShell window, and repeat. If the folder already exists, do not clone over it.
12.3 Reconfirm the Debug Probe COM port
Run:
C:\RemoteSwitch\switch-pico-sdl2\.venv\Scripts\python.exe -m serial.tools.list_ports -v
Find the USB Serial Device associated with the Raspberry Pi Debug Probe. The tested machine used COM3.
You can verify it visually:
- Open Device Manager.
- Expand Ports (COM & LPT).
- Unplug only the Debug Probe's USB cable.
- Note which COM entry disappears.
- Reconnect the probe and confirm the same entry returns.
Keep the Debug Probe in the same physical PC USB port. Windows normally preserves its COM number there.
Every command below uses COM3. If Windows now assigns another number, substitute that number everywhere, including the startup script.
12.4 Confirm the Switch-side prerequisites
Using a local Joy-Con if necessary:
- Confirm the Switch is awake and docked.
- Open System Settings > Controllers and Sensors.
- Confirm Pro Controller Wired Communication is On.
- Open System Settings > Sleep Mode.
- Confirm Auto-Sleep (Connected to TV) is Never.
The HORI compatibility firmware does not implement controller wake-from-sleep. Keeping the docked Switch awake is part of this design.
12.5 Confirm Parsec's host controller mode after reboot
On the Windows host:
- Open Parsec > Settings > Host.
- Confirm Parsec Virtual USB Gamepads is Enabled.
- Confirm the latest offered Virtual USB driver is installed.
- Set Virtual Gamepad Type to Xbox 360.
- Click Restart at the top of Parsec if you changed either setting.
Do not use DualShock 4 or DualSense emulation for this build. The SDL2 bridge and the tested remote path expect Parsec's standardized Xbox 360 device.
12.6 Prepare the sister's Xbox controller on her Mac
The recommended controller is the current official Xbox Wireless Controller—the Series X|S model with USB-C and a Share button.
On the Mac:
- Update macOS before first setup.
- Turn on the Xbox controller.
- Hold its pairing button until the Xbox light flashes.
- Open System Settings > Bluetooth and connect Xbox Wireless Controller.
- Open System Settings > Game Controllers.
- Select the Xbox controller and return its controls to their defaults if they were customized previously.
- Close Steam while commissioning this path so Steam Input cannot consume or duplicate controller events.
- Open Parsec > Settings > Gamepad.
- Select the Xbox controller.
- Press every control and watch Parsec's State display.
The intended Parsec meanings are:
| Physical Xbox control | Parsec control |
|---|---|
| A | A |
| B | B |
| X | X |
| Y | Y |
| Menu, three horizontal lines | Start |
| View, overlapping rectangles | Back |
| Xbox logo | Unused; macOS does not need to expose it for this build |
| Share | Misc/Share, when exposed |
| LB / RB | Left / Right Shoulder |
| LT / RT | Left / Right Trigger |
| Left stick click | Left Stick |
| Right stick click (R3) | Right Stick; the Windows bridge changes it to Switch HOME |
| D-pad | D-pad |
If a control is wrong, use Map in Parsec's Gamepad tab to assign it. Use Show Raw Data when you need to determine whether macOS and Parsec can see a button at all. Keep R3 assigned to Right Stick, not Guide, and keep every D-pad direction assigned normally.
12.7 Create the Parsec virtual controller on the host
The virtual controller does not exist merely because the host PC booted. Parsec creates it after the remote player connects and presses a face button.
- On the Mac, connect to the Windows host in Parsec.
- Press Xbox A once.
- The host should play the Windows USB-connected sound.
- On the host, press Windows+R, enter
joy.cpl, and press Enter. - Confirm an Xbox 360 Controller appears.
- Open its Properties > Test page.
- Confirm A/B/X/Y, both stick axes, both stick clicks, D-pad, bumpers, triggers, Menu, and View respond correctly.
If joy.cpl is empty, do not start debugging the Pico. Fix Parsec first:
- Confirm the remote user has gamepad permission.
- Confirm the client pressed a face button after connecting.
- Restart Parsec on the host, reconnect, and press A again.
- Reinstall the Parsec Virtual USB driver if the host still creates no device.
12.8 Confirm the SDL2 bridge sees only the intended controller
Disconnect local controllers from the Windows host. Then run:
Set-Location C:\RemoteSwitch\switch-pico-sdl2
.\.venv\Scripts\python.exe -m switch_pico_bridge.controller_uart_bridge --list-controllers
Expected output includes:
Detected controller 0: Xbox 360 Controller
No controllers detected is normal if the sister is not currently connected or has not pressed a face button. It is not evidence that the Python installation broke.
Avoid using the older physical Xbox One controller directly on the Windows host. Its SDL GUID reproduced a known SDL input-read problem during testing. Parsec's virtual Xbox 360 controller is the validated path.
12.9 Apply the R3-to-HOME and COM3 hot-plug fixes
Stop any old bridge console with Ctrl+C. If the scheduled task exists, stop it too:
Stop-ScheduledTask -TaskName "Remote Switch - Controller Bridge" -ErrorAction SilentlyContinue
Now apply both idempotent source changes. This block keeps one backup, maps R3 to HOME, and changes controller-removal cleanup so Windows releases COM3 before Parsec's replacement controller tries to claim it:
Set-Location C:\RemoteSwitch\switch-pico-sdl2
$bridge = "C:\RemoteSwitch\switch-pico-sdl2\src\switch_pico_bridge\controller_uart_bridge.py"
$backup = "$bridge.pre-remote-switch-fixes"
if (-not (Test-Path $backup)) {
Copy-Item $bridge $backup
}
@'
from pathlib import Path
path = Path(r"C:\RemoteSwitch\switch-pico-sdl2\src\switch_pico_bridge\controller_uart_bridge.py")
text = path.read_text(encoding="utf-8-sig")
text = text.replace(
"sdl2.SDL_CONTROLLER_BUTTON_RIGHTSTICK: SwitchButton.RCLICK,",
"sdl2.SDL_CONTROLLER_BUTTON_RIGHTSTICK: SwitchButton.HOME,",
)
start = text.find("def handle_device_removed(")
end = text.find("\n\ndef service_contexts(", start)
if start < 0 or end < 0:
raise SystemExit("ERROR: Could not locate handle_device_removed; the pinned source does not match this guide.")
patched_handler = '''def handle_device_removed(
event: sdl2.SDL_Event,
pairing: PairingState,
contexts: Dict[int, ControllerContext],
uarts: List[PicoUART],
console: Console,
) -> None:
"""Handle controller removal, close its UART, and release auto-pairing."""
instance_id = event.cdevice.which
ctx = contexts.pop(instance_id, None)
if not ctx:
return
console.print(
f"[yellow]Controller {instance_id} (id {ctx.stable_id}) removed[/yellow]"
)
# Windows opens COM ports exclusively. Close the old handle before
# returning the port to the pool for Parsec's replacement controller.
if ctx.uart:
try:
ctx.uart.close()
except Exception:
pass
if ctx.uart in uarts:
uarts.remove(ctx.uart)
ctx.uart = None
if ctx.controller_index in pairing.auto_assigned_indices:
freed = pairing.mapping_by_index.pop(ctx.controller_index, None)
pairing.auto_assigned_indices.discard(ctx.controller_index)
if freed and freed not in pairing.available_ports:
pairing.available_ports.append(freed)
console.print(f"[cyan]Released UART {freed} back to pool[/cyan]")
sdl2.SDL_GameControllerRumble(ctx.controller, 0, 0, 0)
sdl2.SDL_GameControllerClose(ctx.controller)'''
text = text[:start] + patched_handler + text[end:]
old_call = "handle_device_removed(event, pairing, contexts, console)"
new_call = "handle_device_removed(event, pairing, contexts, uarts, console)"
if old_call in text:
text = text.replace(old_call, new_call, 1)
elif new_call not in text:
raise SystemExit("ERROR: Could not update the controller-removal call site.")
if "sdl2.SDL_CONTROLLER_BUTTON_RIGHTSTICK: SwitchButton.HOME," not in text:
raise SystemExit("ERROR: R3-to-HOME mapping was not applied.")
path.write_text(text, encoding="utf-8")
print("Applied R3-to-HOME and Windows COM-port release fixes.")
'@ | .\.venv\Scripts\python.exe -
.\.venv\Scripts\python.exe -m py_compile $bridge
Select-String -Path $bridge -Pattern "RIGHTSTICK","ctx.uart.close","handle_device_removed\(event, pairing, contexts, uarts, console\)"
Expected output contains all three of these:
sdl2.SDL_CONTROLLER_BUTTON_RIGHTSTICK: SwitchButton.HOME,
ctx.uart.close()
handle_device_removed(event, pairing, contexts, uarts, console)
If Python reports a syntax error, or any expected line is absent, do not start the bridge. Restore $backup and repeat this step against the pinned e13ba50 source.
This deliberately bypasses Guide. R3 reaches the Parsec Xbox 360 virtual controller as an ordinary, dependable button, and the Windows bridge changes that button to the HOME bit before sending it over UART. Right-stick movement is unaffected; only right-stick click is reassigned.
The COM3 change is equally important for unattended use. Parsec may remove and recreate the host's virtual Xbox 360 controller during connection or reconnection. Closing the old UART object before releasing its mapping prevents the replacement controller from becoming stuck in an Access is denied retry loop.
12.10 Run the corrected bridge manually
Run:
Set-Location C:\RemoteSwitch\switch-pico-sdl2
.\.venv\Scripts\python.exe -u -m switch_pico_bridge.controller_uart_bridge --ports COM3 --no-imu --zero-sticks --deadzone 0.12 --zero-hotkey= --swap-hotkey=
Expected startup messages include:
Prepared 1 specified UART port(s) for auto-pairing.
Detected controller 0: Xbox 360 Controller
Auto-paired controller 0 to COM3
Controller 0 (...) paired to COM3
The terminal does not print a line for every button. Silence while playing is normal.
Why these options are final:
| Option | Reason |
|---|---|
--ports COM3 |
Auto-pairs the first arriving Parsec controller with the known Debug Probe port and supports hot-plugging. |
--no-imu |
The HORI firmware intentionally has no gyro path, and Animal Crossing does not require it. |
--zero-sticks |
Captures the initial neutral positions when the virtual controller arrives. Keep both sticks released during connection. |
--deadzone 0.12 |
Suppresses the approximately 8% return-to-center drift observed during testing without harming Animal Crossing. |
--zero-hotkey= |
Disables the terminal re-zero hotkey for unattended operation. |
--swap-hotkey= |
Prevents an accidental terminal keypress from changing ABXY later. |
There is deliberately no --swap-abxy flag. In this pinned SDL2 revision, no flag means Xbox labels map directly to the same Switch labels.
12.11 Final controller mapping
The resulting mapping is:
| Xbox controller | Nintendo Switch |
|---|---|
| A | A |
| B | B |
| X | X |
| Y | Y |
| LB | L |
| RB | R |
| LT | ZL |
| RT | ZR |
| Menu, three lines | Plus + |
| View, overlapping rectangles | Minus − |
| Xbox logo | Unused |
| Share | Capture, when Parsec exposes the Share/Misc button |
| Left stick click | L-stick click |
| Right stick click (R3) | Home |
| D-pad | D-pad |
| Left and right sticks | Left and right sticks |
This is label-for-label ABXY, not physical-position mapping. When Animal Crossing says “Press A,” press the Xbox button labeled A. There is no separate Switch R-stick-click input in this build because R3 is reserved for HOME.
12.12 Test every control and reconnect before automating it
On the Switch, open Controllers > Change Grip/Order, then test:
- Release the controller and wait five seconds. Nothing should move.
- Press A, B, X, and Y individually.
- Test all four D-pad directions.
- Move each stick in a full circle and release it.
- Press LB, RB, LT, and RT.
- Press Menu and verify Switch Plus.
- Press View and verify Switch Minus.
- Press R3 and confirm it opens the Switch HOME Menu.
- Press Share and look for the Switch capture confirmation.
- Click the left stick and confirm the Switch L-stick click.
Then launch Animal Crossing and confirm:
- Xbox A selects.
- Xbox B cancels.
- Xbox X and Y perform their expected in-game functions.
- Menu opens the NookPhone or the appropriate Plus action.
- View performs the Minus action.
- R3 returns to HOME.
Finally test the COM3 fix:
- Leave the bridge running.
- Disconnect the Parsec client.
- Wait five seconds.
- Reconnect through Parsec and press A once.
- Confirm the same bridge process automatically pairs the recreated Xbox 360 controller to COM3.
- Confirm the controller works again without restarting the scheduled task.
The bridge should report controller removal, Released UART COM3 back to pool, and then a new paired to COM3 line. It must not repeat PermissionError(13): Access is denied.
Do not automate the bridge until the controls pass this manual test.
12.13 Why this build does not use the Xbox logo
On the tested Mac, macOS did not expose the Xbox-logo button to Parsec as a usable controller input. Assigning ordinary controls such as R3 or D-pad Up to Parsec Guide also failed to produce Switch HOME, even though those physical controls were detected.
Do not spend more time trying to make Guide work:
- Leave the Xbox logo unused. If macOS opens Music when it is pressed, that does not affect remote play.
- Restore R3 to ordinary Right Stick in Parsec.
- Restore every D-pad direction to its matching D-pad direction.
- Apply the Windows R3-to-HOME change in Step 12.9.
- If Share appears in Parsec, leave it mapped to Misc/Share for optional Switch Capture.
The physical Xbox-logo button is not part of the validated path. R3 is the reliable HOME control.
13. Finish the Parsec video, audio, and access settings
13.1 Host settings
On the Windows host, open Parsec > Settings > Host and use:
- Hosting Enabled: On
- Resolution: 1920×1080
- FPS: 60
- Bandwidth Limit: 30 Mbps
- Display: the 1080p60 display carrying the OBS projector
- Audio: CABLE Input (VB-Audio Virtual Cable)
- Echo Cancelling: Off
- Parsec Virtual USB Gamepads: Enabled
- Virtual Gamepad Type: Xbox 360
- Quality: Lowest Latency
Restart Parsec after changing the gamepad type or Virtual USB driver.
13.2 Sister's client settings
On the Mac:
- Window Mode: Fullscreen
- VSync: Off initially
- Decoder: Hardware
- H.265/HEVC: Off for the baseline test
- 10-bit color: Off
- Controller: verified in Settings > Gamepad
- R3 mapping: ordinary Right Stick, not Guide
If she sees tearing, enable VSync and accept its small latency cost. If the network cannot sustain 30 Mbps, lower the host limit to 25 Mbps, then 20 Mbps.
13.3 Give access only to her account
Use separate Parsec accounts.
- Add the sister as a friend or invited guest.
- Enable Gamepad permission.
- Enable keyboard and mouse only if you want her to perform recovery actions.
- Enable unattended connection only for her trusted account.
- Do not share the host account password.
- Do not create a permanent public Parsec share link.
14. Make the controller bridge start automatically
14.1 Create the permanent bridge launcher
Open Notepad and save the following as:
C:\RemoteSwitch\start-switch-bridge.cmd
Use this exact content, replacing COM3 only if the Debug Probe uses another port:
@echo off
setlocal
set "ROOT=C:\RemoteSwitch\switch-pico-sdl2"
set "PYTHON=%ROOT%\.venv\Scripts\python.exe"
set "LOGDIR=C:\RemoteSwitch\logs"
if not exist "%LOGDIR%" mkdir "%LOGDIR%"
cd /d "%ROOT%"
echo [%date% %time%] Starting Remote Switch controller bridge>>"%LOGDIR%\switch-pico-bridge.log"
echo Account: %USERDOMAIN%\%USERNAME%>>"%LOGDIR%\switch-pico-bridge.log"
if not exist "%PYTHON%" (
echo ERROR: Missing virtual-environment Python at "%PYTHON%">>"%LOGDIR%\switch-pico-bridge.log"
exit /b 2
)
"%PYTHON%" -c "import sys; print('Virtual environment:', sys.executable); print('Base Python:', sys._base_executable)" >>"%LOGDIR%\switch-pico-bridge.log" 2>&1
if errorlevel 1 (
echo ERROR: The virtual environment cannot reach its base Python. Rebuild it under the same Windows account that runs this launcher.>>"%LOGDIR%\switch-pico-bridge.log"
exit /b 10
)
"%PYTHON%" -u -m switch_pico_bridge.controller_uart_bridge --ports COM3 --no-imu --zero-sticks --deadzone 0.12 --zero-hotkey= --swap-hotkey= >>"%LOGDIR%\switch-pico-bridge.log" 2>&1
exit /b %errorlevel%
Important:
- The path is
switch-pico-sdl2, notswitch-pico. - There is no
--swap-abxy. - The source line for
SDL_CONTROLLER_BUTTON_RIGHTSTICKmust still end inSwitchButton.HOME. - The controller-removal handler must close
ctx.uartand receive theuartslist as described in Step 12.9. - The launcher must run as the same
RemoteSwitchWindows account that owns the base Python runtime and.venv. - Do not launch
debug-bridge.pyfor daily use.
Both source edits survive a Windows reboot. Reapply Step 12.9 if you later replace controller_uart_bridge.py, forcibly restore the Git checkout, or delete and recreate switch-pico-sdl2.
14.2 Verify the Python account and test the launcher
A virtual environment is not a self-contained copy of Python. Its pyvenv.cfg records the base interpreter that created it. If .venv was created in an Administrator PowerShell belonging to admin, a launcher running as RemoteSwitch can find .venv\Scripts\python.exe but still fail with:
did not find executable at 'C:\Users\admin\AppData\Local\Python\pythoncore-3.13-64\python.exe': Access is denied.
That error is unrelated to the controller, UART, COM3, or Pico.
First close every Administrator: PowerShell window. From the RemoteSwitch desktop, open ordinary PowerShell—do not choose Run as administrator—and run:
whoami
Get-Content C:\RemoteSwitch\switch-pico-sdl2\.venv\pyvenv.cfg
C:\RemoteSwitch\switch-pico-sdl2\.venv\Scripts\python.exe -c "import sys; print(sys._base_executable)"
Pass conditions:
whoamiends in\RemoteSwitch, not\admin.- The base Python prints without an error.
- Neither output points into another user's profile.
If all three pass, skip to Retest the launcher below.
Repair an admin-bound .venv
Stop every running bridge before changing .venv. Stay in the normal, non-Administrator RemoteSwitch PowerShell window. If pymanager is not available for this account, install the current Python Install Manager:
winget install 9NQ7512CXL7T -e --accept-package-agreements --accept-source-agreements --disable-interactivity
Close and reopen ordinary PowerShell after installation. Confirm the account again, then install a Python 3.13 runtime owned by this user:
whoami
pymanager install 3.13
pymanager exec -V:3.13 -c "import sys; print(sys.executable)"
The printed path should be under C:\Users\RemoteSwitch, never C:\Users\admin.
Now preserve the broken environment as a backup and recreate it. This does not touch the repository, the R3 source change, the Pico firmware, or any Switch data:
Set-Location C:\RemoteSwitch\switch-pico-sdl2
$backup = ".venv-admin-bound-" + (Get-Date -Format "yyyyMMdd-HHmmss")
if (Test-Path .venv) {
Rename-Item .venv $backup
}
pymanager exec -V:3.13 -m venv .venv
.\.venv\Scripts\python.exe -m pip install --upgrade pip
.\.venv\Scripts\python.exe -m pip install -e .
.\.venv\Scripts\python.exe -m pip install "PySDL2-dll==2.32.10"
.\.venv\Scripts\python.exe -c "import sys; print('Virtual environment:', sys.executable); print('Base Python:', sys._base_executable)"
Select-String -Path .\src\switch_pico_bridge\controller_uart_bridge.py -Pattern "RIGHTSTICK","ctx.uart.close","handle_device_removed\(event, pairing, contexts, uarts, console\)"
The final two commands must show a base Python belonging to RemoteSwitch and all three patched lines:
sdl2.SDL_CONTROLLER_BUTTON_RIGHTSTICK: SwitchButton.HOME,
ctx.uart.close()
handle_device_removed(event, pairing, contexts, uarts, console)
Retest the launcher
Stop every manual or scheduled bridge.
Archive the mixed old log so the new test is unambiguous:
$log = "C:\RemoteSwitch\logs\switch-pico-bridge.log" if (Test-Path $log) { $archive = "C:\RemoteSwitch\logs\switch-pico-bridge-before-venv-fix-{0}.log" -f (Get-Date -Format "yyyyMMdd-HHmmss") Move-Item $log $archive }Double-click
C:\RemoteSwitch\start-switch-bridge.cmdfrom theRemoteSwitchdesktop.Have the sister connect through Parsec and press A.
Test the Switch, including R3 opening HOME.
Close the launcher with Ctrl+C.
Open the new log:
Get-Content C:\RemoteSwitch\logs\switch-pico-bridge.log -Tail 40
The new log should identify the RemoteSwitch account and its base Python, then show the Xbox 360 controller paired to COM3. Do not schedule the launcher until this exact test works without elevation.
14.3 Create the Task Scheduler entry
- Open Task Scheduler.
- Select Create Task, not Create Basic Task.
- On General:
- Name:
Remote Switch - Controller Bridge - User: the same
RemoteSwitchaccount shown bywhoamiin Step 14.2 - Select Run only when user is logged on
- Leave Run with highest privileges cleared
- Name:
- On Triggers:
- New
- Begin the task: At log on
- Specific user: the remote-play Windows account
- Delay task for: 15 seconds
- On Actions:
- Program:
C:\Windows\System32\cmd.exe - Arguments:
/c "C:\RemoteSwitch\start-switch-bridge.cmd" - Start in:
C:\RemoteSwitch
- Program:
- On Conditions:
- Clear Start the task only if the computer is on AC power if shown.
- On Settings:
- Enable Allow task to be run on demand.
- Enable If the task fails, restart every 1 minute.
- Set restart attempts to
999. - Clear Stop the task if it runs longer than.
- If already running: Do not start a new instance.
- Save.
The task may start before a Parsec controller exists. That is fine after the Step 12.9 COM-release fix: the bridge waits for hot-plug events and safely releases COM3 when Parsec replaces a controller instance.
14.4 Test the scheduled task without rebooting
Run:
Start-ScheduledTask -TaskName "Remote Switch - Controller Bridge"
Start-Sleep -Seconds 3
Get-ScheduledTaskInfo -TaskName "Remote Switch - Controller Bridge"
Get-Content C:\RemoteSwitch\logs\switch-pico-bridge.log -Tail 40
Then connect through Parsec, press A, and confirm the Switch responds. Press R3 and confirm it opens HOME.
Disconnect Parsec, reconnect, press A, and test again without restarting the task. The log should show the old controller removed, Released UART COM3 back to pool, and the new controller paired to COM3 without PermissionError(13).
Only one process may own COM3. Do not leave a manual bridge running when starting the scheduled task.
15. Make OBS return to the clean Switch view
15.1 Install the automatic full-screen projector script
OBS's Save projectors on exit setting works after a clean OBS exit, but Windows sign-out or reboot can close OBS before it persists or restores the projector. OBS also has no projector launch parameter. Use its built-in scripting API to open the Switch scene projector five seconds after every OBS launch.
Create the script from normal PowerShell:
@'
obs = obslua
local timer_armed = false
local function open_remote_switch_projector()
obs.timer_remove(open_remote_switch_projector)
timer_armed = false
obs.obs_frontend_open_projector("Scene", 0, "", "Switch")
end
function script_description()
return "Opens the Remote Switch scene as a fullscreen projector after OBS starts."
end
function script_load(settings)
timer_armed = true
obs.timer_add(open_remote_switch_projector, 5000)
end
function script_unload()
if timer_armed then
obs.timer_remove(open_remote_switch_projector)
timer_armed = false
end
end
'@ | Set-Content C:\RemoteSwitch\obs-auto-projector.lua -Encoding utf8
The same file is available as obs-auto-projector.lua.
Now configure OBS:
- Open File > Settings > General.
- In Projectors:
- Clear Save projectors on exit so the script cannot create a duplicate.
- Enable Hide cursor over projectors.
- Enable Always on top if available.
- Open Tools > Scripts.
- Select the + button.
- Choose
C:\RemoteSwitch\obs-auto-projector.lua. - Close the Scripts window.
- If the projector opened while you were adding the script, press Esc once.
- Exit OBS with File > Exit and reopen it.
- Wait five seconds and confirm the
Switchscene automatically fills the display.
The script uses monitor index 0, which is correct for the tested single-monitor host. If it opens on the wrong PC display, edit the 0 in obs_frontend_open_projector("Scene", 0, "", "Switch") to 1, reload the script in Tools > Scripts, and retest.
Press Esc on the host to leave projector mode temporarily. That does not disable the script; it opens a fresh projector the next time OBS starts.
15.2 Create the OBS scheduled task
Create another Task Scheduler task:
- Name:
Remote Switch - OBS - Run only when the remote-play Windows user is logged on
- Trigger: At log on, delayed 25 seconds
- Program:
C:\Program Files\obs-studio\bin\64bit\obs64.exe
- Arguments:
--profile "Remote Switch" --collection "Remote Switch" --scene "Switch" --disable-missing-files-check
- Start in:
C:\Program Files\obs-studio\bin\64bit
Use the same retry and no-time-limit settings as the bridge task.
The scheduled task starts OBS. The loaded Lua script—not the command line—opens the full-screen scene projector five seconds later.
15.3 Reconfirm audio after the reboot
In OBS:
- Confirm the
Game Capture Neoaudio meter moves. - Open Advanced Audio Properties.
- Set the Neo to Monitor Only (mute output).
- Open Settings > Audio > Advanced.
- Set Monitoring Device to CABLE Input (VB-Audio Virtual Cable).
In Parsec Host settings, Audio must also be CABLE Input.
The TV still receives audio independently through the Neo's HDMI OUT.
16. Finish unattended Windows recovery
16.1 Confirm Parsec is installed per-computer
The per-computer installation lets the Parsec service start before a user signs in.
In PowerShell:
Get-CimInstance Win32_Service |
Where-Object {
$_.Name -match "parsec" -or
$_.DisplayName -match "parsec"
} |
Format-Table Name, DisplayName, State, StartMode
At least one Parsec service should be Running with an automatic start mode.
If no service appears, reinstall Parsec using its Per Computer option, restart Windows, and recheck.
16.2 Automatic sign-in decision
OBS and the controller bridge need an interactive Windows session.
For a dedicated, physically secure host:
- Download Microsoft Sysinternals Autologon.
- Run
Autologon64.exeas administrator. - Enter the dedicated remote-play Windows account and password.
- Select Enable.
- Restart and verify the account signs in.
Use BitLocker and keep the machine physically secure. A local administrator can recover the stored autologon credential.
If you do not enable autologon, Parsec can reach the Windows sign-in screen, but someone must sign in before OBS and the user-level scheduled bridge become available.
16.3 Power-loss settings
- In BIOS/UEFI, set Restore on AC Power Loss to Power On.
- Keep Windows sleep disabled.
- Disable USB selective suspend for this host.
- Keep Windows Update active, but configure Active Hours around likely play times.
- Prefer a small UPS for the PC, network equipment, dock, and capture card.
- A smart plug is useful for the dock or capture hardware; it is not a safe routine way to restart Windows.
17. Staged acceptance test after the reboot
Pass each stage before moving to the next.
Stage A — Local HDMI and OBS
- TV shows Switch video and audio through Neo passthrough.
- OBS shows 1080p60 Switch video.
- OBS audio meter moves.
- Fullscreen Scene Projector fills the host display.
Stage B — Parsec video and audio
- Sister connects.
- She sees only the clean Switch scene.
- She hears one clean stereo feed.
- Parsec reports hardware decode and stable network timing.
Stage C — Parsec virtual controller
- Client Gamepad State responds correctly.
- After connecting and pressing A, host
joy.cplshows Xbox 360 Controller. - Its Test page shows correct controls, including R3 as an ordinary right-stick click.
Stage D — Bridge
- Scheduled task is running.
- Log shows controller 0 paired to
COM3. - Idle input is neutral.
- Every control follows the mapping table in Step 12.11.
- R3 opens HOME; the Xbox logo is not required.
- Disconnecting and reconnecting Parsec releases and re-pairs COM3 without an access-denied loop.
Stage E — Animal Crossing
- Press R3 to return to the Switch HOME Menu.
- Select the sister's existing Switch user.
- Launch Animal Crossing.
- Confirm her existing resident and house load on the family island.
- Walk, open inventory, use the NookPhone, enter and leave a building, and save normally.
Stage F — True unattended restart
- Disconnect Parsec.
- Restart Windows normally.
- Wait three to five minutes.
- Confirm the host comes back online.
- Reconnect.
- Press A once to create the virtual controller.
- Verify video, audio, Plus, Minus, R3-to-HOME, ABXY, and both stick axes.
- Disconnect and reconnect Parsec once without restarting Windows or the bridge, then verify the controller again.
- Repeat the full restart test a second time.
Do not call the build unattended until it passes Stage F twice.
18. Normal daily use
Sister
- Connect or wake the Xbox controller on the Mac.
- Open Parsec.
- Verify the controller under Settings > Gamepad if anything changed.
- Connect to the home PC.
- Press A once to create the host virtual controller.
- Wait a few seconds for the bridge to pair it to
COM3. - Play normally.
- Choose her existing Switch user when launching Animal Crossing.
- Save and press R3 to return to the HOME Menu when finished.
- Disconnect from Parsec.
- Do not put the Switch to sleep.
Family at home
- Use the TV normally through the Neo's HDMI passthrough.
- Avoid using another controller during a remote session.
- If controller order becomes confused, open Controllers > Change Grip/Order and reconnect only the intended controller.
- Leave the Pico on the dock and Debug Probe on the PC.
19. Troubleshooting
The bridge says No controllers detected after boot
This is normal before a remote player connects.
- Connect the sister through Parsec.
- Press a face button.
- Confirm the host USB-connected sound.
- Check
joy.cpl. - The already-running bridge should hot-detect and pair it.
joy.cpl sees no controller
The problem is before switch-pico:
- Verify the controller in the Mac's Parsec Gamepad tab.
- Confirm the guest has gamepad permission.
- Press A after connecting.
- Restart host Parsec and reconnect.
- Reinstall the Parsec Virtual USB driver if needed.
The log says pythoncore...python.exe: Access is denied
This is not the COM3 access error. It means .venv was created from a base Python installed inside another Windows user's profile—typically an elevated admin account—but the launcher is now running as RemoteSwitch.
- Do not run the daily bridge as Administrator.
- Open normal PowerShell as
RemoteSwitch. - Run
whoamiand inspect.venv\pyvenv.cfg. - Follow Repair an admin-bound
.venvin Step 14.2. - Keep the Task Scheduler user set to that same
RemoteSwitchaccount.
The presence of .venv\Scripts\python.exe does not prove the environment is usable; it still depends on the base interpreter recorded when the virtual environment was created.
The bridge reports Access is denied for COM3
Windows allows only one process or stale serial object to own COM3. In the pinned SDL2 revision, this can also happen when Parsec removes a virtual controller and the unpatched removal handler fails to close that controller's UART object.
First verify the Step 12.9 hot-plug patch:
$bridge = "C:\RemoteSwitch\switch-pico-sdl2\src\switch_pico_bridge\controller_uart_bridge.py"
Select-String -Path $bridge -Pattern "ctx.uart.close","handle_device_removed\(event, pairing, contexts, uarts, console\)"
Both lines must appear. If either is absent, repeat Step 12.9.
For immediate recovery after applying or verifying the patch:
Stop-ScheduledTask -TaskName "Remote Switch - Controller Bridge" -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2
Start-ScheduledTask -TaskName "Remote Switch - Controller Bridge"
Start-Sleep -Seconds 5
Get-Content C:\RemoteSwitch\logs\switch-pico-bridge.log -Tail 50
If access-denied messages continue, stop or close every other possible owner:
- Any manual bridge window.
debug-bridge.py.test-pico.py.- A serial terminal.
- Another scheduled or Startup-folder copy of the launcher.
List likely duplicate processes with:
Get-CimInstance Win32_Process |
Where-Object {
$_.CommandLine -match "controller_uart_bridge|start-switch-bridge|debug-bridge|test-pico"
} |
Format-List ProcessId, ParentProcessId, Name, CommandLine
Then start exactly one scheduled bridge. A healthy Parsec disconnect/reconnect logs Released UART COM3 back to pool followed by a new paired to COM3 line.
The bridge uses the wrong controller
Disconnect all local host controllers. In Parsec, reset virtual gamepads, reconnect only the sister, and press her A button first.
ABXY letters are reversed
The final SDL2 command must not contain:
--swap-abxy
Confirm the scheduled script points to switch-pico-sdl2, not the newer switch-pico folder. Stop and restart the task after correcting it.
Menu or View does not produce Plus or Minus
On the client:
- Open Parsec Settings > Gamepad.
- Confirm Menu reports Start.
- Confirm View reports Back.
- Remap them if necessary.
- Reconnect so Parsec recreates the host controller.
The pinned bridge already maps Start to Plus and Back to Minus.
Right-stick click does not produce Home
- On the Mac, open Parsec > Settings > Gamepad.
- Confirm physical R3 reports ordinary Right Stick, not Guide.
- On the Windows host, open
joy.cpland confirm R3 registers on the virtual Xbox 360 controller. - Verify the host source mapping:
Select-String -Path C:\RemoteSwitch\switch-pico-sdl2\src\switch_pico_bridge\controller_uart_bridge.py -Pattern "RIGHTSTICK"
It must show:
sdl2.SDL_CONTROLLER_BUTTON_RIGHTSTICK: SwitchButton.HOME,
If it says SwitchButton.RCLICK, repeat Step 12.9. Then stop every bridge process and restart exactly one corrected bridge.
If the line is correct and R3 registers in joy.cpl, stop every bridge process and send HOME directly over UART:
Set-Location C:\RemoteSwitch\switch-pico-sdl2
@'
import time
from switch_pico_bridge import SwitchUARTClient, SwitchButton
with SwitchUARTClient("COM3", send_interval=1/500, auto_send=True) as controller:
controller.neutral()
time.sleep(0.5)
controller.press_for(0.5, SwitchButton.HOME)
time.sleep(0.5)
'@ | .\.venv\Scripts\python.exe -
No terminal output is expected. If the direct test opens HOME, the firmware and UART path are healthy; restart the corrected bridge and check that no stale manual or scheduled bridge is still running.
Share does not produce Capture
Capture is optional. Some client/virtual-controller combinations do not expose a distinct Misc/Share button. Do not remap R3 away from ordinary Right Stick to recover Capture.
Buttons again become directions or Up is stuck
That was the old Switch-facing Pro Controller report failure.
- Stop the bridge.
- Confirm the Pico was not reflashed with the original rollback UF2.
- Reflash
switch-pico-hori-compat-rp2040.uf2. - Restart with the SDL2 command from Step 12.10.
Do not troubleshoot this symptom by swapping physical controllers.
Stick movement continues at idle
- Release both sticks before connecting.
- Keep
--zero-sticks. - Keep
--deadzone 0.12. - Disconnect and reconnect the Parsec controller so neutral is captured again.
- If needed, increase to
--deadzone 0.15.
Scheduled task runs but the Switch does nothing
Check:
Get-ScheduledTaskInfo -TaskName "Remote Switch - Controller Bridge"
Get-Content C:\RemoteSwitch\logs\switch-pico-bridge.log -Tail 80
Then verify:
- Task user is the same
RemoteSwitchaccount that owns.venv. - The log's
Account:andBase Python:lines both identify that account. - Correct COM port.
- Pico USB is on the Switch dock.
- Debug Probe USB is on the Windows host.
- Probe UART U port is used.
- GPIO4 → probe RX, GPIO5 → probe TX, and GND → GND.
- Pro Controller Wired Communication is On.
- Step 12.9's COM-release handler is present.
- Exactly one bridge process is running.
Parsec shows the desktop or OBS mixer instead of the Switch
- In OBS, open Tools > Scripts and confirm
obs-auto-projector.luais loaded. - Confirm its scene name is exactly
Switch. - Reload the script or restart OBS and wait five seconds.
- If the projector opens on the wrong PC display, change its monitor index from
0to1. - For immediate recovery, right-click
Switchand choose Fullscreen Projector (Scene) on the display Parsec captures.
Press Esc locally to exit projector mode temporarily.
Sister hears silence
- OBS Neo meter must move.
- OBS Monitoring Device must be CABLE Input.
- Neo monitoring must be Monitor Only.
- Parsec Host Audio must be CABLE Input.
- Reconnect the Parsec session after changing audio devices.
Switch is asleep
The HORI compatibility controller cannot wake it. Use a local power-button press or a tested dock-power workaround, then reconfirm docked auto-sleep is Never.
OBS shows an unclean-shutdown prompt
Connect through Parsec with mouse permission, choose Run Normally, and select the Remote Switch profile and collection. Confirm obs-auto-projector.lua remains loaded; it should open the full-screen scene projector five seconds after the next normal OBS launch.
20. Recovery ladder
Use the least disruptive step that works:
- Disconnect and reconnect the Parsec session.
- Restart Parsec on the client.
- Reset Parsec gamepads on the host, reconnect, and press A.
- Stop and start
Remote Switch - Controller Bridgein Task Scheduler. - Inspect the bridge log.
- Deactivate and reactivate the Neo source in OBS.
- Restart OBS and reopen its projector.
- Restart Windows normally.
- Power-cycle the dock/Neo HDMI chain if HDMI negotiation is stuck.
- Cut PC power only as a last resort.
Do not expose RDP, OBS WebSocket, serial ports, or ad-hoc web panels directly to the public Internet.
21. Expected experience
Image and sound
- Switch output: 1920×1080, up to 60 fps.
- Neo capture: 1080p60 SDR.
- OBS: 1080p60 NV12, Rec. 709, Limited/Partial.
- Parsec: H.264 hardware-encoded 1080p60 at up to 30 Mbps.
- Audio: HDMI stereo → Neo → OBS → VB-CABLE → Parsec.
- Family TV: independent HDMI passthrough.
Animal Crossing should look very good. Heavy rain, foliage, or rapid full-screen motion can show some compression.
Latency
A realistic healthy interstate target is approximately 70–140 ms of added button-to-image latency:
- Controller forwarding, bridge, UART: a few milliseconds.
- USB capture and OBS preview: commonly two to four 60 Hz frames.
- Parsec encode, network, and decode: commonly another 20–60 ms.
This is comfortable for Animal Crossing and Jackbox. It is not a competitive fighting-game or rhythm-game target.
22. Final acceptance checklist
- Pico still runs the HORI/Pokkén compatibility firmware.
- Host bridge path is
C:\RemoteSwitch\switch-pico-sdl2. - Pinned SDL2 commit begins with
e13ba50. -
.venvbase Python belongs to the sameRemoteSwitchaccount used by Task Scheduler. - Debug Probe COM port is correct in the launcher.
- Parsec host emulates Xbox 360.
- Xbox A/B/X/Y map to Switch A/B/X/Y by label.
- Menu maps to Plus.
- View maps to Minus.
- R3 maps to Home through the host source change.
- The patched removal handler closes the old UART before returning COM3 to the pairing pool.
- Xbox logo is not required.
- Share maps to Capture when the client exposes it.
- Sticks remain neutral when released.
- Scheduled bridge starts at logon and survives one Parsec disconnect/reconnect.
-
obs-auto-projector.luaopens the clean fullscreen Switch scene after OBS starts. - Sister receives one clean stereo audio feed.
- Family TV retains normal video and audio.
- Animal Crossing loads the sister's original user and resident.
- Two unattended Windows restart tests pass.
Once every required item passes, the remote-play system is ready for normal use.
23. Primary references
- Pinned switch-pico SDL2 bridge source
- Current switch-pico bridge source with close-before-reuse cleanup
- Parsec: Set up a gamepad
- Parsec app for Windows
- Parsec app for macOS
- Apple: Connect and customize a wireless game controller
- Apple: Supported Xbox controllers
- Python: Using Python on Windows
- Python: Creating and recreating virtual environments
- OBS: Power of Projectors
- OBS: Frontend projector API
- Microsoft Sysinternals Autologon