Decode Nintendo HD-rumble words once in firmware into conventional low/high magnitudes and normalize stripped control SET_REPORT vs complete interrupt OUT reports through one path.
130 lines
3.7 KiB
Python
130 lines
3.7 KiB
Python
"""Regression tests for SDL sensor buffering in the UART bridge."""
|
|
|
|
from argparse import Namespace
|
|
from io import StringIO
|
|
from types import SimpleNamespace
|
|
from typing import Protocol, cast
|
|
|
|
import sdl3
|
|
from rich.console import Console
|
|
|
|
import switch_pico_bridge.controller_uart_bridge as bridge
|
|
from switch_pico_bridge.switch_pico_uart import (
|
|
IMUSample,
|
|
PicoUART,
|
|
SENSOR_ACCEL,
|
|
SENSOR_GYRO,
|
|
SwitchButton,
|
|
SwitchReport,
|
|
UART_BAUD,
|
|
)
|
|
|
|
|
|
class MonkeyPatch(Protocol):
|
|
def setattr(self, target: object, name: str, value: object) -> None: ...
|
|
|
|
|
|
class RecordingUART:
|
|
def __init__(self) -> None:
|
|
self.sent_imu: list[tuple[IMUSample, ...]] = []
|
|
|
|
def send_report(self, report: SwitchReport) -> None:
|
|
self.sent_imu.append(tuple(report.imu_samples))
|
|
|
|
def read_rumble(self) -> tuple[float, float] | None:
|
|
return None
|
|
|
|
|
|
def make_config() -> bridge.BridgeConfig:
|
|
return bridge.BridgeConfig(
|
|
interval=0.002,
|
|
deadzone_raw=0,
|
|
trigger_threshold=0,
|
|
zero_sticks=False,
|
|
zero_hotkey="",
|
|
swap_hotkey="",
|
|
button_map_default={},
|
|
button_map_swapped={},
|
|
swap_abxy_indices=set(),
|
|
swap_abxy_ids=set(),
|
|
swap_abxy_global=False,
|
|
)
|
|
|
|
|
|
def test_sensor_buffer_retains_latest_three_samples() -> None:
|
|
controller = cast(sdl3.SDL_Gamepad, object())
|
|
ctx = bridge.ControllerContext(controller, 7, 0, "dualsense", None, None)
|
|
ctx.sensors_enabled = True
|
|
ctx.gyro_bias_locked = True
|
|
contexts = {ctx.instance_id: ctx}
|
|
config = make_config()
|
|
|
|
accel_event = cast(
|
|
sdl3.SDL_Event,
|
|
cast(
|
|
object,
|
|
SimpleNamespace(
|
|
gsensor=SimpleNamespace(
|
|
which=ctx.instance_id,
|
|
sensor=SENSOR_ACCEL,
|
|
data=(0.0, 9.80665, 0.0),
|
|
)
|
|
),
|
|
),
|
|
)
|
|
bridge.handle_sensor_update(accel_event, contexts, config)
|
|
|
|
for gyro_x in (1.0, 2.0, 3.0, 4.0):
|
|
gyro_event = cast(
|
|
sdl3.SDL_Event,
|
|
cast(
|
|
object,
|
|
SimpleNamespace(
|
|
gsensor=SimpleNamespace(
|
|
which=ctx.instance_id,
|
|
sensor=SENSOR_GYRO,
|
|
data=(gyro_x, 0.0, 0.0),
|
|
)
|
|
),
|
|
),
|
|
)
|
|
bridge.handle_sensor_update(gyro_event, contexts, config)
|
|
|
|
assert len(ctx.imu_samples) == 3
|
|
assert [sample.gyro_y for sample in ctx.imu_samples] == [
|
|
bridge.convert_gyro_to_raw(-2.0),
|
|
bridge.convert_gyro_to_raw(-3.0),
|
|
bridge.convert_gyro_to_raw(-4.0),
|
|
]
|
|
|
|
|
|
def test_service_republishes_latest_imu_window(monkeypatch: MonkeyPatch) -> None:
|
|
uart = RecordingUART()
|
|
controller = cast(sdl3.SDL_Gamepad, object())
|
|
ctx = bridge.ControllerContext(
|
|
controller, 7, 0, "dualsense", "/dev/null", cast(PicoUART, cast(object, uart))
|
|
)
|
|
ctx.sensors_enabled = True
|
|
samples = [
|
|
IMUSample(1, 2, 3, 4, 5, 6),
|
|
IMUSample(7, 8, 9, 10, 11, 12),
|
|
IMUSample(13, 14, 15, 16, 17, 18),
|
|
]
|
|
ctx.imu_samples = samples.copy()
|
|
contexts = {ctx.instance_id: ctx}
|
|
config = make_config()
|
|
|
|
def ignore_poll(
|
|
_ctx: bridge.ControllerContext, _button_map: dict[int, SwitchButton]
|
|
) -> None:
|
|
return None
|
|
|
|
monkeypatch.setattr(bridge, "poll_controller_buttons", ignore_poll)
|
|
|
|
args = Namespace(baud=UART_BAUD)
|
|
console = Console(file=StringIO())
|
|
bridge.service_contexts(1.0, args, config, contexts, [], console)
|
|
bridge.service_contexts(2.0, args, config, contexts, [], console)
|
|
|
|
assert uart.sent_imu == [tuple(samples), tuple(samples)]
|
|
assert ctx.imu_samples == samples
|