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.
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user