Files
dora-ulite6/examples/caller.py
T
cristhian a2bf59065d add dora-node.yml manifest and a service example dataflow
The manifest is the only contract a consumer has, since no source package
is shared between node repositories, and dora validate checks a dataflow
against it without running the node — so CI needs no robot.

request and response are deliberately untyped. They carry request_id, and
dora defines that as the marker for a pattern-correlated port where
several Arrow schemas legitimately flow, one per service; the runtime
skips type checks on those messages, so annotating them would claim a
contract neither side keeps.

examples/ carries its own manifest because dora resolves a node's
manifest by walking up from its path, and a manifest applies only when
<manifest dir>/<entrypoint> names the same file as the dataflow's path.
Without it the example caller would be matched against the arm node's
manifest and strict validation would fail on the entrypoint mismatch.
2026-09-12 11:47:06 -03:00

65 lines
2.2 KiB
Python

"""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()