add a dora-node.yml manifest and a dora validate CI gate
No source package is shared between node repositories, so the manifest and its type URNs are the only contract a consumer has. Declaring them makes a wiring mistake fail at validate time with the producer and consumer URNs, instead of at runtime. Every payload this node sends is a raw buffer whose shape and dtype live in the metadata rather than an Arrow struct, so the URNs name the element type: UInt8 for image_bgr, Float32 for depth, camera_info and point_cloud, String for camera_state. tick is UInt64, matching how dora types timer inputs. dataflow.yml now builds the node from the checkout with path: + build:. A git:-sourced node does not pick up an adjacent manifest, so validating the previous form checked nothing; with path: the CLI reports "injected 6 contract type(s)" and a deliberately mismatched annotation exits non-zero under strict_types. The README keeps the tag: form for consumers. The workflow needs no camera and no ZED SDK, since validation never runs the node. Also ignores the git/ and out/ working directories dora build creates, and drops the ignore rules for the deleted test assets. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y7g2cBJ5e3r15ZbJ9WZn8c
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# Checks the dataflow against the typed contract in dora-node.yml. This runs no
|
||||
# node, so it needs neither a camera nor the ZED SDK.
|
||||
name: validate
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
DORA_VERSION: v1.0.1
|
||||
DORA_TARGET: x86_64-unknown-linux-gnu
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install the dora CLI
|
||||
run: |
|
||||
curl -fsSL -o dora-cli.tar.gz \
|
||||
"https://github.com/dora-rs/dora/releases/download/${DORA_VERSION}/dora-cli-${DORA_TARGET}.tar.gz"
|
||||
tar xzf dora-cli.tar.gz
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
install -m 755 "dora-cli-${DORA_TARGET}/dora" "$HOME/.local/bin/dora"
|
||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||
rm -rf dora-cli.tar.gz "dora-cli-${DORA_TARGET}"
|
||||
|
||||
- run: dora --version
|
||||
|
||||
# strict_types: true in dataflow.yml turns a type warning into a non-zero
|
||||
# exit, so a mismatched annotation fails here rather than at runtime.
|
||||
- name: Validate the dataflow
|
||||
run: dora validate dataflow.yml
|
||||
+4
-3
@@ -1,6 +1,7 @@
|
||||
build/
|
||||
bin/
|
||||
tmp/
|
||||
test/assets/*.png
|
||||
test/assets/*.jpg
|
||||
test/assets/*_result.*
|
||||
|
||||
# dora build working directories
|
||||
git/
|
||||
out/
|
||||
|
||||
@@ -41,7 +41,9 @@ Camera behavior is configured entirely through environment variables (see README
|
||||
- **`src/main.cc`** — Dora event loop. Parses `ZED_PC_MODE` (`FULL`/`HALF`) once before the loop. On any input event (intended to be driven by a tick timer; the id is not inspected): calls `camera.grab()`, then sends `image_bgr` and `depth`, plus `point_cloud` when `ZED_POINT_CLOUD` is on and `camera_info` on the first frame of a camera session (`camera_info_pending`, re-armed on reconnection). `pack_depth` copies the depth map into a tightly packed `[H, W]` float buffer (sl::Mat rows can be padded) and clears `inf`/`nan` to `0`. `set_intrinsics` and `set_frame_stamps` stamp every output of a frame with the same intrinsics, `frame_id` and the two timestamps (`device_timestamp_ns`, `host_timestamp_ns`; both Unix-epoch nanoseconds). Point cloud output varies by mode: `FULL` sends the raw `sl::Mat` pointer (no copy); `HALF` downsamples by 2 in each dimension. Recovery is a state machine over ticks rather than a blocking retry: the camera is opened from inside the loop, `begin_recovery` closes (first attempt) or resets (later ones) and sets a backoff deadline, and `camera_state` is published on each transition. Nothing sleeps, so `Stop` is honoured while the camera is down.
|
||||
- **`DoraTargets.cmake`** — Handles building or fetching dora-rs and generating the CXX bridge files. Two paths: local (`DORA_ROOT_DIR`) or git clone. Produces `dora_node_api_cxx` library and the `node_bridge.cc` glue.
|
||||
- **`scripts/build.sh`** — Configure/compile/install in one shell-free-callable step. Referenced by `dataflow.yml`, the `Dockerfile` and the README.
|
||||
- **`dataflow.yml`** — Example dora dataflow definition using the `git:` source format, pinned with `tag:` — dora 1.0 pins a source with the mutually exclusive `tag:`, `rev:` or `branch:`.
|
||||
- **`dora-node.yml`** — Node manifest (`apiVersion: 1`): typed input/output contract, entrypoint, build command. `dora validate` injects these types into any dataflow that reaches the node by `path:`; a `git:`-sourced node does **not** pick it up, which is why `dataflow.yml` builds from the checkout. Type URNs name the element type (`std/core/v1/UInt8`, `.../Float32`, `.../String`) because every payload is a raw buffer described by its metadata, not an Arrow struct.
|
||||
- **`dataflow.yml`** — Example dora dataflow, built from this checkout with `path:` + `build:` and `strict_types: true`. A consumer wires the node from a tag instead (see README) — dora 1.0 pins a source with the mutually exclusive `tag:`, `rev:` or `branch:`.
|
||||
- **`.gitea/workflows/validate.yml`** — CI gate running `dora validate dataflow.yml` on push and pull request. Needs no camera and no ZED SDK.
|
||||
- **`Dockerfile`** — Ubuntu 24.04 x86-64 build environment (CUDA 12.8, ZED SDK 5.2, Rust). No ARM64/Jetson image; build natively there. No camera needed; run the compile with `--gpus all`. Sets `NVIDIA_DRIVER_CAPABILITIES=compute,utility,video` — the `video` capability is what makes the container runtime mount NVDEC/NVENC, which `libsl_zed.so` links against. Rust lives at `/usr/local/{rustup,cargo}` rather than `/root` so the build can run as the invoking user. `docker build -t dora-zed-build . && docker run --rm --gpus all --user "$(id -u):$(id -g)" -v "$PWD:/src" -v dora-zed-cargo:/usr/local/cargo/registry dora-zed-build`.
|
||||
|
||||
## Key APIs
|
||||
|
||||
@@ -23,7 +23,33 @@ nodes:
|
||||
- camera_state
|
||||
```
|
||||
|
||||
See [`dataflow.yml`](dataflow.yml) for a complete runnable example with a camera display node.
|
||||
See [`dataflow.yml`](dataflow.yml) for a complete runnable example with a camera display
|
||||
node. That file builds the node from the checkout (`path:` + `build:`) rather than from a
|
||||
tag, which is what lets `dora validate` pick up the manifest described below.
|
||||
|
||||
### Node manifest
|
||||
|
||||
[`dora-node.yml`](dora-node.yml) declares the node's inputs and outputs with type URNs.
|
||||
No source package is shared between node repositories, so it is the only contract a
|
||||
consumer has, and it is what makes a wiring mistake fail at build time instead of at
|
||||
runtime:
|
||||
|
||||
```
|
||||
$ dora validate dataflow.yml
|
||||
node "zed-camera": injected 6 contract type(s) from .../dora-node.yml
|
||||
All type annotations OK.
|
||||
```
|
||||
|
||||
Types are `std/core/v1/UInt8` for `image_bgr`, `std/core/v1/Float32` for `depth`,
|
||||
`camera_info` and `point_cloud`, and `std/core/v1/String` for `camera_state`; every
|
||||
payload is a raw buffer whose shape and dtype are in the metadata, so the URN names the
|
||||
element type rather than a struct. The `tick` input is `std/core/v1/UInt64`, matching how
|
||||
dora types timer inputs.
|
||||
|
||||
`strict_types: true` in `dataflow.yml` makes a type warning a non-zero exit.
|
||||
[`.gitea/workflows/validate.yml`](.gitea/workflows/validate.yml) runs
|
||||
`dora validate dataflow.yml` on every push and pull request; it needs no camera and no
|
||||
ZED SDK, since validation never runs the node.
|
||||
|
||||
## Dependencies
|
||||
|
||||
|
||||
+6
-3
@@ -1,9 +1,12 @@
|
||||
# The repository's own example: builds and runs the node from this checkout, so
|
||||
# `dora validate` picks up the typed contract in dora-node.yml next to it.
|
||||
# A consumer wires the node from a tag instead — see README.md.
|
||||
strict_types: true
|
||||
|
||||
nodes:
|
||||
- id: zed-camera
|
||||
git: ssh://git@gitea.liria.cl:5001/liria/dora-zed.git
|
||||
tag: v0.2.0
|
||||
build: scripts/build.sh
|
||||
path: bin/dora_zed
|
||||
build: scripts/build.sh
|
||||
inputs:
|
||||
tick: dora/timer/millis/100
|
||||
outputs:
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# Node manifest: the typed contract a consumer wires against.
|
||||
#
|
||||
# No source package is shared between node repositories, so this file and its
|
||||
# type URNs are the only contract a consumer has. `dora validate` checks a
|
||||
# dataflow against it without running the node, so CI needs no camera and no
|
||||
# ZED SDK.
|
||||
apiVersion: 1
|
||||
name: dora-zed
|
||||
namespace: liria
|
||||
description: ZED stereo camera source publishing a BGR image, a depth map, intrinsics and an optional point cloud
|
||||
categories: [sensor]
|
||||
keywords: [zed, stereolabs, camera, depth, stereo, jetson]
|
||||
|
||||
runtime: cpp
|
||||
entrypoint: bin/dora_zed
|
||||
build: scripts/build.sh
|
||||
|
||||
inputs:
|
||||
tick:
|
||||
type: std/core/v1/UInt64
|
||||
required: true
|
||||
description: Each tick triggers one camera grab. Connect to dora/timer/millis/N.
|
||||
|
||||
outputs:
|
||||
image_bgr:
|
||||
type: std/core/v1/UInt8
|
||||
description: BGR image bytes, [H, W, 3]. Shape and encoding are in the metadata.
|
||||
depth:
|
||||
type: std/core/v1/Float32
|
||||
description: Per-pixel depth in millimetres, [H, W]. Missing depth is 0, never inf or nan.
|
||||
camera_info:
|
||||
type: std/core/v1/Float32
|
||||
description: Intrinsics [fx, fy, cx, cy], distortion in the metadata. Published once per camera session.
|
||||
point_cloud:
|
||||
type: std/core/v1/Float32
|
||||
description: XYZRGBA point cloud. Published only when ZED_POINT_CLOUD is on.
|
||||
camera_state:
|
||||
type: std/core/v1/String
|
||||
description: One of opening, streaming, recovering. Published on every change.
|
||||
Reference in New Issue
Block a user