Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,12 @@ the standard `edgecommons` envelope, owned by the canonical schema and not redec

- Southbound routing/availability error codes are the standardized `BAD_ARGS` / `NO_SUCH_INSTANCE` /
`DEVICE_UNAVAILABLE` (SOUTHBOUND.md §2.2); domain codes (`CAPTURE_*`, `PTZ_*`, …) are camera-specific.
- Instance routing is D-EIP-13/D-U28: body `instance`, optional iff exactly one camera is configured;
an instance-addressed command topic routes by its token, which is authoritative (SOUTHBOUND.md §2.2).
- Instance routing is D-EIP-13/D-U28 with declared verb scope (SOUTHBOUND.md §2.2 / D-SC-2): every
verb — deferred captures included — registers a `CommandScope` (`Component`/`Instance`/`Both`) and
the library enforces the addressing before dispatch (conflict-first `BAD_ARGS`; a `Component` verb
refuses any instance addressing). The topic token is authoritative; a body `instance` is optional
iff exactly one camera is configured, and that default plus the `NO_SUCH_INSTANCE` existence check
stay adapter-side (D-SC-4).
- Builders/facades are the construction path (`app()`, `events()`, `commands()`, `MetricBuilder`) —
never hand-built topics or envelopes.
- Runtime artifacts (durable state DBs, captured images, TLS fixtures, logs, build output) stay out of
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ native-all = ["genicam", "rtsp"]
capacity-harness = []

[dependencies]
# Pinned to a published rev (rust-lib/v0.4.0) so CI (a standalone repo) can resolve it. Local dev builds against
# Pinned to a published rev (rust-lib/v0.5.0) so CI (a standalone repo) can resolve it. Local dev builds against
# the sibling checkout via the gitignored `.cargo/config.toml` [patch] override, which replaces
# this source outright. Same pattern as file-replicator.
edgecommons = { git = "https://github.com/edgecommons/edgecommons.git", rev = "ef4c6248eec9cabc0acfc88eb0698528aa83a3ab", default-features = false, features = ["credentials"] }
edgecommons = { git = "https://github.com/edgecommons/edgecommons.git", rev = "a14a3285573ef2bb6a531e1e1936c6dc40a85ef4", default-features = false, features = ["credentials"] }
anyhow = "1"
async-trait = "0.1"
bytes = "1"
Expand Down Expand Up @@ -101,7 +101,7 @@ rustls-pemfile = { version = "2", optional = true }
tokio-rustls = { version = "0.26", optional = true }

[dev-dependencies]
edgecommons = { git = "https://github.com/edgecommons/edgecommons.git", rev = "ef4c6248eec9cabc0acfc88eb0698528aa83a3ab", default-features = false, features = ["credentials", "standalone"] }
edgecommons = { git = "https://github.com/edgecommons/edgecommons.git", rev = "a14a3285573ef2bb6a531e1e1936c6dc40a85ef4", default-features = false, features = ["credentials", "standalone"] }
proptest = "1"
tokio = { version = "1", features = ["io-util", "macros", "net", "rt-multi-thread", "test-util"] }

Expand Down
28 changes: 16 additions & 12 deletions DESIGN.md

Large diffs are not rendered by default.

29 changes: 19 additions & 10 deletions docs/reference/messaging-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ ecv1/{device}/camera-adapter/{instance}/cmd/sb/{verb} (instance-addressed)

On the component-scope topic, select a camera with the JSON body field `instance`. On an
instance-addressed topic the topic's `{instance}` token is authoritative: it routes the command, and a
body `instance` that disagrees with it is refused with `BAD_ARGS`. The reply is correlated with the
incoming envelope. Normal capture *completion* is a separate terminal application message, not the
command reply — see [Terminal application messages](#terminal-application-messages).
body `instance` that disagrees with it is refused with `BAD_ARGS`. Every verb declares a **scope** —
`component`, `instance`, or `both` — advertised in the built-in `describe` verb's per-command `scope`
field and enforced before the verb's handler runs. The reply is correlated with the incoming
envelope. Normal capture *completion* is a separate terminal application message, not the command
reply — see [Terminal application messages](#terminal-application-messages).

## Conventions

Expand All @@ -24,12 +26,19 @@ These rules apply to every verb below.
one camera, omission is `BAD_ARGS`. An unknown name is `NO_SUCH_INSTANCE`; a disabled camera is
`CAMERA_DISABLED`. An `instance` token is non-empty, ≤128 bytes, ASCII
letters/digits/`.`/`_`/`-`.
- **Instance-addressed topics.** A verb that takes an `instance` selector also accepts the
instance-addressed topic form: the topic token routes it (no body `instance` needed), and a
conflicting body `instance` is `BAD_ARGS`. The component-scoped verbs `sb/list`, `sb/discover`,
`sb/capture-group-submit`, and `sb/capture-cancel` refuse an instance-addressed delivery with
`BAD_ARGS`. The two deferred verbs, `sb/capture` and `sb/capture-group`, select their target(s)
from the body only — the topic token does not route them; send them to the component-scope topic.
- **Command scope.** Each verb's declared scope decides which addressing it accepts:

| Scope | Verbs | Addressing |
|---|---|---|
| `component` | `sb/list`, `sb/discover`, `sb/capture-group`, `sb/capture-group-submit`, `sb/capture-cancel` | Component-scope topic only. Any instance addressing — a topic `{instance}` token or a body `instance` — is refused with `BAD_ARGS`. |
| `instance` | `sb/capture`, `sb/capture-submit`, `sb/reconnect`, `sb/ptz`, `sb/ptz-presets`, `sb/pause`, `sb/resume` | Targets one camera: the topic token, else the body `instance`, else the single-camera omission rule. |
| `both` | `sb/status`, `sb/capture-status`, `sb/queue-status`, `sb/queue-clear` | An addressed camera narrows the answer; no addressing at all means the whole component (every camera / the whole fleet). |

- **Instance addressing.** A topic `{instance}` token and a body `instance` that are both present
and different are refused with `BAD_ARGS` — checked first, for every scope. At an `instance` or
`both` verb the topic token is authoritative and routes the command with no body `instance`
needed; an unknown addressed camera is `NO_SUCH_INSTANCE`. This applies to the deferred verbs
(`sb/capture`) exactly as to the immediate ones.
- **Idempotency.** Every *mutating* verb requires a caller-owned `requestId` (1–256 bytes, no control
characters). A retry with the same `requestId` and the same arguments returns the original outcome; a
reused `requestId` with **different** arguments is `IDEMPOTENCY_CONFLICT`; an operation whose outcome
Expand Down Expand Up @@ -60,7 +69,7 @@ reaches a terminal state, then settles with the full terminal body.

| Field | Type | Required | Meaning |
|---|---|---|---|
| `instance` | string | optional* | Target camera (*single-camera omission rule). |
| `instance` | string | optional* | Target camera (*single-camera omission rule). On an instance-addressed topic the topic token selects the camera and no body `instance` is needed. |
| `requestId` | string | **yes** | Durable idempotency key, 1–256 bytes. |
| `captureProfile` | string | optional | Named profile (≤128 bytes); defaults to the camera's `defaultCaptureProfile`. Unknown → `UNKNOWN_CAPTURE_PROFILE`. |
| `timeoutMs` | u64 | optional | 1000–1800000. Defaults to the profile's `timeoutMs`, else `global.timeouts.jobTerminalMs`. |
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ camera has dropped from the keepalive rather than by polling `sb/list` or `sb/st
|---|---|
| `instance` | The camera ID. |
| `connected` | True only while the camera's protocol session is online. The normalized flag any consumer can act on. |
| `state` | The camera's own condition token: `ONLINE`, `CONNECTING`, `BACKOFF`, `OFFLINE`, `DEGRADED`, `DISABLED`, `STOPPING`. `BACKOFF` and `CONNECTING` are both `connected: false`, and they call for different responses. |
| `state` | The camera's condition token: `ONLINE`, `CONNECTING`, `BACKOFF`, `PAUSED`, `OFFLINE`, `DEGRADED`, `DISABLED`, `STOPPING`. `BACKOFF` and `CONNECTING` are both `connected: false`, and they call for different responses. A camera paused with `sb/pause` reports `PAUSED` — deliberately quiet, not stale — while `connected` keeps reporting reachability, because pause suspends capture workload, not the session. The token comes from the same state model that answers `sb/status`, so the pushed and the pulled answer cannot disagree. |
| `detail` | Why the camera is down, in its own words, when it has reported an error. A healthy camera carries none. |
| `attributes` | Camera-specific data: `backend`, the connection `generation`, and `lastErrorCode` when an error is known. |

Expand Down
Loading
Loading