Queue correlated, volatile wake requests for the BTstack owner and expose read-only completion state. Keep the controller chord, captured identity, pairings, profiles, HD settings and existing wake burst unchanged. Add Python API and wake CLI with bounded waits, explicit failure outcomes and no automatic broadcast retries. Avoid importing SDL just to obtain fixed sensor IDs so USB automation has clean output and no SDL dependency. Windows libusb cannot control hub roots. Expose only INFO/WAKE on each native child's existing vendor Interface 1 with isolated control state; retain hub/HID drivers, descriptors and identities. Add safe sibling-aware Windows discovery and a packaged Windows libusb runtime. Document manual WinUSB binding to Interface 1 only and migrate examples to find_wake_pico. Validate with 713 tests, 10 firmware/probe builds, Windows x64 wheel resolution on Python 3.9/3.11, and a real child-interface wake burst on Linux (2.03 s, no controller, no failures). Device/configuration descriptors, serials and persistent state matched before/after. Physical Windows hardware/driver operation remains unverified on this Linux workstation.
211 lines
7.7 KiB
Python
211 lines
7.7 KiB
Python
from __future__ import annotations
|
|
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
|
|
def test_bluepad32_backend_lifecycle_native(tmp_path: Path) -> None:
|
|
root = Path(__file__).resolve().parents[1]
|
|
compiler = shutil.which("c++") or shutil.which("g++")
|
|
assert compiler is not None, "a host C++ compiler is required"
|
|
|
|
for bluetooth_mode, native, wii_bridge in (
|
|
("mixed", False, False),
|
|
("mixed", True, False),
|
|
("ble", False, False),
|
|
("classic", False, False),
|
|
("mixed", False, True),
|
|
):
|
|
suffix = "_wii_bridge" if wii_bridge else "_native32" if native else ""
|
|
executable = (
|
|
tmp_path / f"bluepad32_backend_lifecycle_test_{bluetooth_mode}{suffix}"
|
|
)
|
|
command = [
|
|
compiler,
|
|
"-std=c++17",
|
|
"-Wall",
|
|
"-Wextra",
|
|
"-Werror",
|
|
"-pedantic",
|
|
"-DSWITCH_PICO_HID_INSTANCE_COUNT=4",
|
|
"-DSWITCH_PICO_USB_OUTPUT_MODES=1",
|
|
f"-DSWITCH_PICO_ENABLE_BLE={int(bluetooth_mode != 'classic')}",
|
|
f"-DSWITCH_PICO_ENABLE_CLASSIC={int(bluetooth_mode != 'ble')}",
|
|
]
|
|
if wii_bridge:
|
|
command.append("-DSWITCH2_BRIDGE_WII_INPUT=1")
|
|
if native:
|
|
command.extend(
|
|
[
|
|
"-DSWITCH_PICO_HAPTICS_EXPERIMENT=1",
|
|
"-DSWITCH_PICO_HD_RUMBLE=1",
|
|
"-DSWITCH_PICO_HAPTICS_EXPERIMENT_RAM=0",
|
|
"-DSWITCH_PICO_HD_PACKET_FRAMES=32",
|
|
str(root / "src" / "firmware" / "input" / "haptics_experiment.cpp"),
|
|
str(
|
|
root
|
|
/ "src"
|
|
/ "firmware"
|
|
/ "input"
|
|
/ "native_output_scheduler.cpp"
|
|
),
|
|
str(
|
|
root
|
|
/ "src"
|
|
/ "firmware"
|
|
/ "input"
|
|
/ "switch_hd_rumble_synth.cpp"
|
|
),
|
|
str(
|
|
root
|
|
/ "src"
|
|
/ "firmware"
|
|
/ "usb"
|
|
/ "switch"
|
|
/ "switch_haptics.cpp"
|
|
),
|
|
]
|
|
)
|
|
if native:
|
|
command.extend(
|
|
[
|
|
"-DSWITCH_PICO_CYW43_PACKET_READ=1",
|
|
"-DSWITCH_PICO_HCI_CREDIT_BATCH=1",
|
|
"-DSWITCH_PICO_SYS_CLOCK_MHZ=300",
|
|
]
|
|
)
|
|
command.extend(
|
|
[
|
|
f"-I{root / 'tests' / 'bluepad32_native_stubs'}",
|
|
f"-I{root / 'src' / 'firmware'}",
|
|
f"-I{root / 'bluepad32_config'}",
|
|
str(root / "tests" / "bluepad32_backend_lifecycle_test.cpp"),
|
|
str(root / "src" / "firmware" / "profile" / "controller_profile.cpp"),
|
|
str(
|
|
root
|
|
/ "src"
|
|
/ "firmware"
|
|
/ "profile"
|
|
/ "controller_profile_transform.cpp"
|
|
),
|
|
str(
|
|
root
|
|
/ "src"
|
|
/ "firmware"
|
|
/ "profile"
|
|
/ "controller_synthetic_input.cpp"
|
|
),
|
|
str(
|
|
root
|
|
/ "src"
|
|
/ "firmware"
|
|
/ "profile"
|
|
/ "controller_profile_runtime.cpp"
|
|
),
|
|
str(root / "src" / "firmware" / "profile" / "profile_storage.cpp"),
|
|
str(root / "src" / "firmware" / "input" / "wii_swing.cpp"),
|
|
str(root / "bluepad32_config" / "parser" / "uni_switch2_haptics.c"),
|
|
str(
|
|
root / "src" / "firmware" / "input" / "controller_macro_capture.cpp"
|
|
),
|
|
"-o",
|
|
str(executable),
|
|
]
|
|
)
|
|
subprocess.run(command, check=True, cwd=root)
|
|
subprocess.run([str(executable), "usb-wake"], check=True, cwd=root)
|
|
if wii_bridge:
|
|
for scenario in (
|
|
"wii-bridge-sensors",
|
|
"wii-bridge-cues",
|
|
"wii-bridge-cue-races",
|
|
):
|
|
subprocess.run([str(executable), scenario], check=True, cwd=root)
|
|
continue
|
|
subprocess.run([str(executable), "transport-policy"], check=True, cwd=root)
|
|
if bluetooth_mode != "classic":
|
|
subprocess.run(
|
|
[str(executable), "transport-background"], check=True, cwd=root
|
|
)
|
|
# The existing lifecycle matrix intentionally exercises both transports.
|
|
# Single-radio builds exercise their admission/radio policy above.
|
|
if bluetooth_mode != "mixed":
|
|
continue
|
|
subprocess.run([str(executable), "xbox-rumble"], check=True, cwd=root)
|
|
for scenario in (
|
|
"usb-wake-chord",
|
|
"wii-orientation",
|
|
"wii-orientation-races",
|
|
"wii-accelerometers",
|
|
"switch2-individual-core-start",
|
|
"switch2-individual-forward",
|
|
"switch2-individual-reverse",
|
|
"switch2-mode-forward",
|
|
"switch2-mode-reverse",
|
|
"switch2-mode-two-pairs",
|
|
"switch2-mode-seed-failure",
|
|
"switch2-mode-identity-failure",
|
|
"switch2-gesture-timing",
|
|
"switch2-gesture-slot-left-first",
|
|
"switch2-gesture-slot-right-first",
|
|
"switch2-gesture-slot-occupied",
|
|
"switch2-gesture-stale",
|
|
"switch2-gesture-clock-wrap",
|
|
"switch2-gesture-masking-epochs",
|
|
"switch2-gesture-override-lifetime",
|
|
"switch2-gesture-two-pairs",
|
|
"switch2-gesture-ambiguous",
|
|
"switch2-gesture-seed-failure",
|
|
"switch2-gesture-device-scope",
|
|
"switch2-forward",
|
|
"switch2-reverse",
|
|
"switch2-multiple-pairs",
|
|
"switch2-pair-failure-left",
|
|
"switch2-pair-failure-right",
|
|
"switch2-pair-replacement",
|
|
"switch2-admission",
|
|
"switch2-radio-policy",
|
|
"switch2-radio-individual",
|
|
"switch2-radio-settling",
|
|
"switch2-mate-reconnect",
|
|
"switch2-mate-pending",
|
|
"switch2-mate-pairing-window",
|
|
"switch2-pairing-inventory",
|
|
"switch2-hd-pro",
|
|
"switch2-hd-solo-left",
|
|
"switch2-hd-solo-right",
|
|
"switch2-hd-pair",
|
|
"switch2-hd-overflow",
|
|
"switch2-hd-epochs",
|
|
"switch2-hd-feedback",
|
|
):
|
|
subprocess.run([str(executable), scenario], check=True, cwd=root)
|
|
if native:
|
|
subprocess.run([str(executable), "native-stateful"], check=True, cwd=root)
|
|
subprocess.run(
|
|
[str(executable), "native-second-slot"], check=True, cwd=root
|
|
)
|
|
continue
|
|
|
|
for scenario in (
|
|
"ready-forward",
|
|
"ready-reverse",
|
|
"rejections",
|
|
"lifecycle",
|
|
"pairing-policy",
|
|
"slot-lighting",
|
|
"profile-chord-raw",
|
|
"profile-feedback",
|
|
"stateful-rumble",
|
|
"motion-hotkey",
|
|
"analog-state",
|
|
"rumble-mode",
|
|
"clear-pairings",
|
|
"configuration-timer",
|
|
"flash-core-start",
|
|
"wake-identity-gate",
|
|
"system-wake",
|
|
"flash-core-failure",
|
|
):
|
|
subprocess.run([str(executable), scenario], check=True, cwd=root)
|