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/
|
build/
|
||||||
bin/
|
bin/
|
||||||
tmp/
|
tmp/
|
||||||
test/assets/*.png
|
|
||||||
test/assets/*.jpg
|
# dora build working directories
|
||||||
test/assets/*_result.*
|
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.
|
- **`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.
|
- **`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.
|
- **`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`.
|
- **`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
|
## Key APIs
|
||||||
|
|||||||
@@ -23,7 +23,33 @@ nodes:
|
|||||||
- camera_state
|
- 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
|
## 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:
|
nodes:
|
||||||
- id: zed-camera
|
- 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
|
path: bin/dora_zed
|
||||||
|
build: scripts/build.sh
|
||||||
inputs:
|
inputs:
|
||||||
tick: dora/timer/millis/100
|
tick: dora/timer/millis/100
|
||||||
outputs:
|
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