Files
dora-teleop/tests/test_config.py
cristhian bde8582de3 add the gamepad reader and the node's settings
The reader owns the pad on a thread of its own, and nothing outside that file
may touch pygame: everything else sees a plain immutable Sample. That is what
lets the rest of the node be tested with no hardware, and what keeps SDL's
quirks out of the arm logic.

Sample.present is the field that matters. A pad that has been unplugged reads as
absent rather than as centred, so "the operator let go" and "the pad fell off
the desk" stay distinguishable — they call for the same stop, but only one of
them is worth logging.

Only the display and joystick subsystems are initialised. pygame.init() also
opens audio, which on a headless robot fills stderr with ALSA errors for a
device this node never uses; the display is needed despite there being no
window, because SDL refreshes joystick state during the event pump.

Settings are parsed once at startup so a deadzone that would silently make the
pad inert stops the node before it joins the dataflow.
2026-09-15 15:42:48 -05:00

68 lines
2.0 KiB
Python

"""Settings: parsed once at startup, and refused there rather than mid-motion."""
import pytest
from dora_teleop.config import Config
def test_the_defaults_need_no_environment():
config = Config.from_env()
assert config.speed_mm_s == 60.0
assert config.speed_deg_s == 30.0
assert config.joystick_index == 0
def test_the_deadman_is_shorter_than_the_arms_own_expiry():
# The arm node's VELOCITY_COMMAND_TTL_S defaults to 0.5 s and is the
# backstop. Ours has to fire first, or the backstop backs up nothing.
assert Config.from_env().deadman.jog_ttl_s < 0.5
def test_a_blank_variable_falls_back_to_the_default(monkeypatch):
monkeypatch.setenv("SPEED_MM_S", " ")
assert Config.from_env().speed_mm_s == 60.0
def test_a_speed_that_is_not_a_number_names_itself(monkeypatch):
monkeypatch.setenv("SPEED_MM_S", "brisk")
with pytest.raises(ValueError, match="SPEED_MM_S"):
Config.from_env()
def test_a_joystick_index_that_is_not_whole_names_itself(monkeypatch):
monkeypatch.setenv("JOYSTICK_INDEX", "1.5")
with pytest.raises(ValueError, match="JOYSTICK_INDEX"):
Config.from_env()
@pytest.mark.parametrize("value", ["1.0", "-0.1", "2"])
def test_a_deadzone_outside_the_stick_is_refused_at_startup(monkeypatch, value):
# A deadzone of 1 would silently make the pad inert; catching it here beats
# finding out when somebody picks it up.
monkeypatch.setenv("DEADZONE", value)
with pytest.raises(ValueError, match="DEADZONE"):
Config.from_env()
@pytest.mark.parametrize("value", ["-0.1", "1.1"])
def test_an_expo_outside_the_curve_is_refused_at_startup(monkeypatch, value):
monkeypatch.setenv("EXPO", value)
with pytest.raises(ValueError, match="EXPO"):
Config.from_env()
def test_the_timings_can_be_tuned_without_touching_the_speeds(monkeypatch):
monkeypatch.setenv("JOG_TTL_S", "0.25")
monkeypatch.setenv("GAMEPAD_IDLE_RELEASE_S", "5")
config = Config.from_env()
assert config.deadman.jog_ttl_s == 0.25
assert config.deadman.gamepad_idle_release_s == 5.0