"""A minimal caller: what using the node's services looks like. Run by `dataflow.yml`. It sends a motion with `wait: false`, follows it on `motion_state`, and then sends a second one that preempts it. That is the whole convention: - a request carries a `request_id` you choose, and gets exactly one `response` under that id — an acknowledgement when `wait` is false, the outcome when it is true; - a motion's progress is on `motion_state`, keyed by the same request_id, so a non-blocking caller can tell "mine has not started" from "mine has finished" without a race; - a new motion replaces the one in play, because the arm is one resource. """ import pyarrow as pa from dora import Node def main() -> None: """Send a non-blocking motion, follow it, preempt it, and exit.""" node = Node("caller") sent_first = False sent_second = False for event in node: if event["type"] != "INPUT": continue if event["id"] == "tick" and not sent_first: sent_first = True _move(node, "move-1", z=200.0) elif event["id"] == "response": row = event["value"][0].as_py() print( f"response to {event['metadata'].get('request_id')}: " f"ok={row['ok']} {row['message']}", flush=True, ) elif event["id"] == "motion_state": row = event["value"][0].as_py() print(f"{row['request_id']} is {row['state']}", flush=True) # Once the first one is really under way, change our mind. if row["request_id"] == "move-1" and row["state"] == "moving" and not sent_second: sent_second = True _move(node, "move-2", z=120.0) elif row["request_id"] == "move-2" and row["state"] in ("done", "failed"): break def _move(node: Node, request_id: str, z: float) -> None: """Ask for a move and come straight back, rather than waiting it out.""" node.send_output( "request", pa.array([{"service": "move_to", "x": 250.0, "y": 0.0, "z": z, "wait": False}]), metadata={"request_id": request_id}, ) if __name__ == "__main__": main()