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.
139 lines
4.4 KiB
Python
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)
|