Files
dora-teleop/tests/test_ui.py
T
cristhian 9f56b2b334 add the translation to and from the web console
The console's telemetry keys are a contract: app.js reads arm_state, owner,
estop, vacuum, freedrive, position and joints by name, so renaming one breaks
the page silently. They are reproduced rather than improved for that reason,
vacuum included, which is the console's name for whatever tool is bolted on.

The two vocabularies do not line up and this is where they are reconciled. The
arm node reports a driver's lifecycle; the console draws an operator's screen.
The ordering in arm_state is the ordering of what an operator most needs to
know: a latched e-stop outranks everything, because the arm may well read ready
and saying so would invite somebody to push it.

A message this node does not recognise is dropped rather than refused. The
console and the node version independently, and a page from a newer build
saying something we have not learned yet must not take the arm down.
2026-09-15 15:43:00 -05:00

139 lines
4.4 KiB
Python

"""Translation both ways: what the console asks for, and what it is shown."""
from dora_teleop import ui
from dora_teleop.arbiter import GAMEPAD, WEB
# --- what the console asks for ---
def test_a_jog_carries_its_six_numbers():
command = ui.command_of({"type": "jog", "velocity": [1, 2, 3, 4, 5, 6]})
assert command.type == "jog"
assert command.velocity == [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
def test_a_jog_of_the_wrong_length_is_not_a_jog():
assert ui.command_of({"type": "jog", "velocity": [1, 2, 3]}) is None
def test_a_jog_carrying_something_that_is_not_a_number_is_not_a_jog():
assert ui.command_of({"type": "jog", "velocity": [1, 2, 3, 4, 5, "fast"]}) is None
def test_a_message_we_have_never_heard_of_is_dropped_not_refused():
# The console and this node version independently; a page from a newer build
# must not take the arm down by saying something we have not learned yet.
assert ui.command_of({"type": "hologram"}) is None
assert ui.command_of({}) is None
assert ui.command_of("not even an object") is None
def test_taking_control_carries_whether_it_may_preempt():
command = ui.command_of({"type": "control", "action": "take", "preempt": True})
assert (command.action, command.preempt) == ("take", True)
def test_the_gripper_button_says_which_way():
assert ui.command_of({"type": "gripper", "vacuum": True}).enabled is True
assert ui.command_of({"type": "gripper", "vacuum": False}).enabled is False
def test_a_move_needs_a_position():
assert ui.command_of({"type": "move", "x": 1.0, "y": 2.0}) is None
command = ui.command_of({"type": "move", "x": 250.0, "y": 0.0, "z": 200.0, "speed": 40})
assert command.row == {"x": 250.0, "y": 0.0, "z": 200.0, "speed": 40.0}
# --- what the console is shown ---
def test_an_arm_that_cannot_be_reached_reads_as_disconnected():
view = ui.ArmView(state=ui.UNAVAILABLE)
assert ui.arm_state(view, jogging=False) == ui.UI_DISCONNECTED
def test_an_arm_still_starting_up_reads_as_disconnected():
view = ui.ArmView(state=ui.CONNECTING)
assert ui.arm_state(view, jogging=False) == ui.UI_DISCONNECTED
def test_a_latched_estop_outranks_everything_else():
# The arm may well read ready, and saying so would invite somebody to push it.
view = ui.ArmView(state=ui.READY, estop=True, freedrive_on=True)
assert ui.arm_state(view, jogging=True) == ui.UI_ESTOP
def test_freedrive_outranks_idle():
# An arm that can be pushed by hand is not one holding station.
view = ui.ArmView(state=ui.READY, freedrive_on=True)
assert ui.arm_state(view, jogging=False) == ui.UI_FREEDRIVE
def test_a_moving_arm_we_are_jogging_reads_as_jogging():
view = ui.ArmView(state=ui.MOVING)
assert ui.arm_state(view, jogging=True) == ui.UI_JOGGING
assert ui.arm_state(view, jogging=False) == ui.UI_MOVING
def test_a_recovering_arm_is_there_but_will_not_move():
view = ui.ArmView(state=ui.RECOVERING)
assert ui.arm_state(view, jogging=False) == ui.UI_STOPPED
def test_a_state_we_do_not_know_reads_as_disconnected_rather_than_as_ready():
view = ui.ArmView(state="dancing")
assert ui.arm_state(view, jogging=False) == ui.UI_DISCONNECTED
def test_the_snapshot_carries_every_key_the_page_reads_by_name():
view = ui.ArmView(state=ui.READY, pose=[1, 2, 3, 4, 5, 6], joints=[0] * 6, connected=True)
snapshot = ui.snapshot(view, WEB, jogging=False, timestamp=123.0)
for key in (
"type",
"ts",
"connected",
"ready",
"arm_state",
"owner",
"estop",
"vacuum",
"freedrive",
"position",
"joints",
):
assert key in snapshot
assert snapshot["position"] == {"x": 1, "y": 2, "z": 3, "roll": 4, "pitch": 5, "yaw": 6}
def test_an_arm_with_no_pose_yet_reports_none_rather_than_a_stale_one():
snapshot = ui.snapshot(ui.ArmView(), None, jogging=False, timestamp=0.0)
assert snapshot["position"] is None
assert snapshot["owner"] == "none"
def test_the_owner_is_named_so_the_page_can_gate_its_jog_pad():
assert ui.snapshot(ui.ArmView(), GAMEPAD, False, 0.0)["owner"] == "gamepad"
assert ui.snapshot(ui.ArmView(), WEB, False, 0.0)["owner"] == "web"
def test_get_state_fills_in_what_telemetry_does_not_carry():
view = ui.ArmView()
view.take_get_state({"connected": True, "ready": True, "tool_engaged": True, "freedrive": True})
assert (view.connected, view.ready, view.tool_engaged, view.freedrive_on) == (True, True, True, True)