@trillboards/edge-sdk 0.2.39 → 0.2.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/deploy/docker/{Dockerfile.cloud-sense → Dockerfile.connector} +52 -39
- package/deploy/docker/README.md +73 -1
- package/deploy/docker/connector-entrypoint.sh +46 -80
- package/deploy/macos/README.md +1 -1
- package/deploy/macos/test-install-pair.sh +9 -9
- package/deploy/systemd/install.sh +16 -1
- package/deploy/systemd/setup-litertlm.sh +201 -0
- package/deploy/systemd/trillboards-edge.service +8 -4
- package/deploy/systemd/trillboards-litertlm.service +46 -0
- package/deploy/windows/register-task.ps1 +6 -3
- package/deploy/windows/run-connector.ps1 +1 -1
- package/deploy/windows/trillboards-edge-task.vbs +5 -1
- package/dist/CloudSensingManager.d.ts +45 -19
- package/dist/CloudSensingManager.d.ts.map +1 -1
- package/dist/CloudSensingManager.js +89 -46
- package/dist/CloudSensingManager.js.map +1 -1
- package/dist/EdgeAgent.js +1 -1
- package/dist/SensingProfileClient.d.ts +9 -0
- package/dist/SensingProfileClient.d.ts.map +1 -1
- package/dist/SensingProfileClient.js +4 -0
- package/dist/SensingProfileClient.js.map +1 -1
- package/dist/SetupSessionClient.d.ts +0 -1
- package/dist/SetupSessionClient.d.ts.map +1 -1
- package/dist/autoSense.d.ts +42 -31
- package/dist/autoSense.d.ts.map +1 -1
- package/dist/autoSense.js +127 -99
- package/dist/autoSense.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +25 -84
- package/dist/cli.js.map +1 -1
- package/dist/combinedSense.d.ts +1 -3
- package/dist/combinedSense.d.ts.map +1 -1
- package/dist/combinedSense.js +0 -1
- package/dist/combinedSense.js.map +1 -1
- package/dist/config.d.ts +17 -64
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -10
- package/dist/config.js.map +1 -1
- package/dist/deployedCadence.d.ts +37 -75
- package/dist/deployedCadence.d.ts.map +1 -1
- package/dist/deployedCadence.js +45 -105
- package/dist/deployedCadence.js.map +1 -1
- package/dist/deployedCadenceSource.d.ts +0 -2
- package/dist/deployedCadenceSource.d.ts.map +1 -1
- package/dist/deployedCadenceSource.js +0 -1
- package/dist/deployedCadenceSource.js.map +1 -1
- package/dist/diagnose.d.ts +1 -1
- package/dist/diagnose.js +1 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -8
- package/dist/index.js.map +1 -1
- package/dist/localInferenceSchema.d.ts +19 -0
- package/dist/localInferenceSchema.d.ts.map +1 -1
- package/dist/localInferenceSchema.js +23 -0
- package/dist/localInferenceSchema.js.map +1 -1
- package/dist/localSensing.d.ts +48 -107
- package/dist/localSensing.d.ts.map +1 -1
- package/dist/localSensing.js +80 -171
- package/dist/localSensing.js.map +1 -1
- package/dist/sensingCatchRate.d.ts +246 -0
- package/dist/sensingCatchRate.d.ts.map +1 -0
- package/dist/sensingCatchRate.js +514 -0
- package/dist/sensingCatchRate.js.map +1 -0
- package/dist/sensingRunMode.d.ts +14 -11
- package/dist/sensingRunMode.d.ts.map +1 -1
- package/dist/sensingRunMode.js +9 -92
- package/dist/sensingRunMode.js.map +1 -1
- package/dist/setupMode.d.ts +0 -1
- package/dist/setupMode.d.ts.map +1 -1
- package/dist/setupMode.js +5 -3
- package/dist/setupMode.js.map +1 -1
- package/dist/startupDiscovery.d.ts +43 -0
- package/dist/startupDiscovery.d.ts.map +1 -0
- package/dist/startupDiscovery.js +72 -0
- package/dist/startupDiscovery.js.map +1 -0
- package/package.json +1 -1
- package/dist/CloudEscalationPolicy.d.ts +0 -143
- package/dist/CloudEscalationPolicy.d.ts.map +0 -1
- package/dist/CloudEscalationPolicy.js +0 -137
- package/dist/CloudEscalationPolicy.js.map +0 -1
package/README.md
CHANGED
|
@@ -249,7 +249,7 @@ Or register via the [Trillboards API](https://api.trillboards.com/developer).
|
|
|
249
249
|
| [docs/CLI.md](../../docs/CLI.md) | Full CLI reference with all commands, flags, and configuration |
|
|
250
250
|
| [docs/MCP.md](../../docs/MCP.md) | MCP server reference for AI agent integration |
|
|
251
251
|
| [docs/STATUS_API.md](../../docs/STATUS_API.md) | Status endpoint API reference |
|
|
252
|
-
| [deploy/macos/README.md](deploy/macos/README.md) | macOS one-click `.pkg` installer (
|
|
252
|
+
| [deploy/macos/README.md](deploy/macos/README.md) | macOS one-click `.pkg` installer (local-inference CCTV connector) |
|
|
253
253
|
| [deploy/docker/README.md](../../deploy/docker/README.md) | Docker deployment guide |
|
|
254
254
|
| [deploy/helm/README.md](../../deploy/helm/README.md) | Helm chart deployment guide |
|
|
255
255
|
| [create-trillboards-edge](../../../create-trillboards-edge/README.md) | Project scaffolding tool |
|
|
@@ -1,37 +1,46 @@
|
|
|
1
|
-
# Trillboards Edge AI SDK — BYO-CCTV connector container
|
|
1
|
+
# Trillboards Edge AI SDK — BYO-CCTV connector container (LOCAL INFERENCE ONLY)
|
|
2
2
|
#
|
|
3
|
-
# No
|
|
4
|
-
#
|
|
5
|
-
#
|
|
3
|
+
# No models bundled, so this image never downloads models and is much lighter
|
|
4
|
+
# than the cpu/cuda/openvino variants. It pairs with a short-lived claim code
|
|
5
|
+
# (the device token is delivered at pair, NEVER baked into the image) then
|
|
6
6
|
# senses. Same connector + same CLI as the macOS/Windows installers — ONE image,
|
|
7
7
|
# not a parallel one.
|
|
8
8
|
#
|
|
9
|
-
#
|
|
10
|
-
# :auto-sense (SENSE_MODE=auto-sense, the default and the recommended tag)
|
|
11
|
-
# probes localSensing.litertLmBaseUrl at startup and commits to
|
|
12
|
-
# on-device inference if a healthy litert-lm is hosting the model.
|
|
13
|
-
# Because this image bundles NO model, the entrypoint also asserts
|
|
14
|
-
# localSensing.allowCloudFallback=true — auto-sense otherwise
|
|
15
|
-
# treats cloud as opt-in and would WAIT forever for a runtime that
|
|
16
|
-
# is never going to appear here. With that assert, a plain
|
|
17
|
-
# `docker run` behaves exactly like :cloud-sense (frames to the
|
|
18
|
-
# cloud VLM) and additionally AUTO-UPGRADES to on-device with no
|
|
19
|
-
# restart the moment a litert-lm becomes reachable.
|
|
20
|
-
# :cloud-sense (SENSE_MODE=cloud-sense) forces the cloud VLM. Kept because the
|
|
21
|
-
# portal one-liner and existing installs reference it.
|
|
22
|
-
# TRILLBOARDS_SENSE_MODE overrides either at run time.
|
|
9
|
+
# ── This image no longer has a cloud path ──────────────────────────────────
|
|
23
10
|
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
11
|
+
# It was `Dockerfile.cloud-sense` and it published TWO tags, `:auto-sense` and
|
|
12
|
+
# `:cloud-sense`, and the entrypoint asserted `localSensing.allowCloudFallback:
|
|
13
|
+
# true` into the config on every start — so BOTH tags sent every analyzed frame
|
|
14
|
+
# to the cloud VLM until a litert-lm happened to appear.
|
|
28
15
|
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
16
|
+
# WHAT THAT COST. Measured 2026-08-12 over 255,812 image-bearing cloud VLM calls:
|
|
17
|
+
# $0.007008 per analyzed frame. At the 30s cadence that is ~$605/camera/month, so
|
|
18
|
+
# a 16-camera store was ~$9,688/month against a $99-500 subscription — roughly
|
|
19
|
+
# 10x underwater. On-device inference has no per-frame cost. The cloud path is
|
|
20
|
+
# gone from the connector entirely; there is no flag that brings it back.
|
|
21
|
+
#
|
|
22
|
+
# ── Therefore: this image REQUIRES a litert-lm you run ─────────────────────
|
|
23
|
+
#
|
|
24
|
+
# It bundles no model (the 2.4 GB Gemma weights have their own OTA channel; see
|
|
25
|
+
# cctv-connector-macos-readiness.md §7a for why baking them into a shipping
|
|
26
|
+
# artifact is waste). Point the connector at a serve on the host:
|
|
27
|
+
#
|
|
28
|
+
# docker run --network host ... # serve on localhost:9379
|
|
29
|
+
# # or set localSensing.litertLmBaseUrl in the config:
|
|
30
|
+
# # http://host.docker.internal:9379
|
|
31
|
+
#
|
|
32
|
+
# Without one, `auto-sense` WAITS: it analyzes nothing and uploads no FRAMES.
|
|
33
|
+
# It is not dark, though — it runs the same manager in health-only mode, so the
|
|
34
|
+
# cameras still open, video is still proven by a decode-only probe, and camera
|
|
35
|
+
# health still reports on the heartbeat with sensingState='unavailable'. The
|
|
36
|
+
# portal shows "connector alive, sensing unavailable" rather than something
|
|
37
|
+
# indistinguishable from a dead machine.
|
|
38
|
+
#
|
|
39
|
+
# Build: docker build -f packages/edge-sdk/deploy/docker/Dockerfile.connector \
|
|
40
|
+
# -t trillboards/edge-sdk:connector . # context = trillboards-edge-sdk
|
|
41
|
+
# Run: docker run --network host -e TRILLBOARDS_CLAIM_CODE=XXXX-XXXX \
|
|
33
42
|
# -v trillboards:/app/data \
|
|
34
|
-
# ghcr.io/trillboards/edge-sdk:
|
|
43
|
+
# ghcr.io/trillboards/edge-sdk:connector
|
|
35
44
|
|
|
36
45
|
FROM node:20-slim AS builder
|
|
37
46
|
|
|
@@ -58,7 +67,7 @@ FROM node:20-slim
|
|
|
58
67
|
|
|
59
68
|
LABEL maintainer="Trillboards <engineering@trillboards.com>"
|
|
60
69
|
ARG SENSE_MODE=auto-sense
|
|
61
|
-
LABEL description="Trillboards Edge AI SDK — BYO-CCTV connector (no bundled models)"
|
|
70
|
+
LABEL description="Trillboards Edge AI SDK — BYO-CCTV connector (local inference only, no bundled models)"
|
|
62
71
|
|
|
63
72
|
# ffmpeg = RTSP frame capture; ca-certificates/curl = TLS + healthcheck.
|
|
64
73
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
@@ -69,8 +78,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
69
78
|
|
|
70
79
|
WORKDIR /app
|
|
71
80
|
|
|
72
|
-
# Built packages
|
|
73
|
-
#
|
|
81
|
+
# Built packages. The workspace deps must resolve even for the paths this image
|
|
82
|
+
# does not take.
|
|
74
83
|
COPY --from=builder /build/packages/edge-core/dist packages/edge-core/dist
|
|
75
84
|
COPY --from=builder /build/packages/edge-core/package.json packages/edge-core/
|
|
76
85
|
COPY --from=builder /build/packages/edge-sensing/dist packages/edge-sensing/dist
|
|
@@ -88,8 +97,8 @@ COPY --from=builder /build/packages/edge-platform-linux/package.json packages/ed
|
|
|
88
97
|
COPY --from=builder /build/packages/edge-platform-rtsp/dist packages/edge-platform-rtsp/dist
|
|
89
98
|
COPY --from=builder /build/packages/edge-platform-rtsp/package.json packages/edge-platform-rtsp/
|
|
90
99
|
|
|
91
|
-
# Production deps only (NO model download — inference
|
|
92
|
-
#
|
|
100
|
+
# Production deps only (NO model download — inference runs on a litert-lm the
|
|
101
|
+
# operator runs and points this at).
|
|
93
102
|
COPY --from=builder /build/package.json /build/package-lock.json* ./
|
|
94
103
|
RUN npm ci --legacy-peer-deps --omit=dev 2>/dev/null || npm install --legacy-peer-deps --omit=dev
|
|
95
104
|
|
|
@@ -104,14 +113,18 @@ RUN chmod +x /usr/local/bin/connector-entrypoint.sh
|
|
|
104
113
|
ENV NODE_ENV=production
|
|
105
114
|
ENV TRILLBOARDS_API_BASE="https://api.trillboards.com"
|
|
106
115
|
ENV TRILLBOARDS_CONFIG="/app/data/trillboards.config.json"
|
|
107
|
-
# Baked
|
|
116
|
+
# Baked by the publish matrix; overridable per run with auto-sense|local-sense.
|
|
117
|
+
# There is no cloud-sense value any more — the entrypoint rejects it by name so
|
|
118
|
+
# an existing `-e TRILLBOARDS_SENSE_MODE=cloud-sense` fails loudly at start
|
|
119
|
+
# instead of silently meaning something else.
|
|
108
120
|
ENV TRILLBOARDS_SENSE_MODE=${SENSE_MODE}
|
|
109
121
|
|
|
110
|
-
# No HEALTHCHECK / status-server port: the sensing CLI paths build
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
#
|
|
122
|
+
# No HEALTHCHECK / status-server port: the sensing CLI paths build the sensing
|
|
123
|
+
# manager directly and never start the StatusServer (that wiring is only in
|
|
124
|
+
# EdgeAgent.start), so a /health:9090 probe would never respond and Docker would
|
|
125
|
+
# mark a perfectly-streaming container unhealthy (Codex P2 on #7730). Liveness
|
|
126
|
+
# for this streaming worker is observed server-side via the connector beacon —
|
|
127
|
+
# which now also fires while `auto-sense` is WAITING for its model, so a stalled
|
|
128
|
+
# container is distinguishable from a dead one without a local probe.
|
|
116
129
|
|
|
117
130
|
ENTRYPOINT ["/usr/local/bin/connector-entrypoint.sh"]
|
package/deploy/docker/README.md
CHANGED
|
@@ -4,13 +4,72 @@ This guide covers deploying the Trillboards Edge AI agent as a Docker container
|
|
|
4
4
|
|
|
5
5
|
## Image Variants
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Four pre-built images. The first three are the **DOOH screen agent** (on-device ONNX,
|
|
8
|
+
bundled models) and target different hardware profiles. The fourth is the **BYO-CCTV
|
|
9
|
+
connector**, which is a different product on different hardware — see below.
|
|
8
10
|
|
|
9
11
|
| Tag | Base Image | Execution Provider | Architecture | Use Case |
|
|
10
12
|
|-----|-----------|-------------------|--------------|----------|
|
|
11
13
|
| `latest` | `node:20-slim` | CPU (WASM) | `linux/amd64`, `linux/arm64` | Generic Linux devices, ARM SBCs, Raspberry Pi 4+ |
|
|
12
14
|
| `cuda` | `nvidia/cuda:12.3.2-runtime-ubuntu22.04` | NVIDIA CUDA | `linux/amd64` | NVIDIA Jetson, GPU-equipped signage players |
|
|
13
15
|
| `openvino` | `openvino/ubuntu22_runtime:2024.0.0` | Intel OpenVINO | `linux/amd64` | Backpackster, Intel NUC, mini-PCs with Intel HD 620+ |
|
|
16
|
+
| `auto-sense` | `node:20-slim` | **LiteRT-LM you run** (see below) | `linux/amd64`, `linux/arm64` | **BYO-CCTV connector** — pull frames from an existing NVR/DVR over RTSP |
|
|
17
|
+
|
|
18
|
+
## The BYO-CCTV connector image (`:auto-sense`)
|
|
19
|
+
|
|
20
|
+
Built from `Dockerfile.connector`. It pairs with a short-lived claim code from the
|
|
21
|
+
portal (the device token is delivered at pair, **never baked into the image**), then
|
|
22
|
+
senses. Same connector and same CLI as the macOS/Windows installers — one image, not
|
|
23
|
+
a parallel product.
|
|
24
|
+
|
|
25
|
+
### It is LOCAL-INFERENCE ONLY, and it bundles no model
|
|
26
|
+
|
|
27
|
+
The connector runs Gemma **on the machine** via LiteRT-LM. There is no cloud VLM path
|
|
28
|
+
in it, no `allowCloudFallback`, and no `cloud-sense` mode. This image ships no model,
|
|
29
|
+
so you must give it a `litert-lm serve` it can reach:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# serve on the host's localhost:9379
|
|
33
|
+
docker run --network host \
|
|
34
|
+
-e TRILLBOARDS_CLAIM_CODE=XXXX-XXXX \
|
|
35
|
+
-v trillboards:/app/data \
|
|
36
|
+
ghcr.io/trillboards/edge-sdk:auto-sense
|
|
37
|
+
|
|
38
|
+
# …or point it at one explicitly, via localSensing.litertLmBaseUrl in the config:
|
|
39
|
+
# "litertLmBaseUrl": "http://host.docker.internal:9379"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Without a reachable serve the connector **waits**: it analyzes nothing and uploads no
|
|
43
|
+
frames, but it still opens the cameras and reports their health (`sensingState:
|
|
44
|
+
'unavailable'`), so the portal shows a live connector that cannot sense yet rather
|
|
45
|
+
than something indistinguishable from a dead machine. It starts sensing automatically
|
|
46
|
+
— no restart — the moment a serve appears.
|
|
47
|
+
|
|
48
|
+
Mount `/app/data` on a volume so the paired config survives restarts; re-pairing needs
|
|
49
|
+
a fresh code from the portal.
|
|
50
|
+
|
|
51
|
+
### `TRILLBOARDS_SENSE_MODE`
|
|
52
|
+
|
|
53
|
+
| Value | Behaviour |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `auto-sense` (default) | Probe for a local runtime; run on-device if healthy, otherwise wait and report health. |
|
|
56
|
+
| `local-sense` | Require the local runtime; no probe loop. |
|
|
57
|
+
| `cloud-sense` | **Removed.** Rejected by name at start with a message, rather than ignored — existing units and container args pin this verb, and a silent behaviour change would be worse than a hard stop. |
|
|
58
|
+
|
|
59
|
+
### Why there is no cloud tag any more
|
|
60
|
+
|
|
61
|
+
There used to be a second tag, `:cloud-sense`, and the entrypoint asserted
|
|
62
|
+
`localSensing.allowCloudFallback: true` on every start — so **both** tags sent every
|
|
63
|
+
analyzed frame to the cloud VLM. Measured 2026-08-12 over 255,812 image-bearing cloud
|
|
64
|
+
VLM calls, that cost **$0.007008 per analyzed frame**: ~$605/camera/month at a 30s
|
|
65
|
+
cadence, so a 16-camera store ran ~$9,688/month against a $99-500 subscription. The
|
|
66
|
+
path was removed rather than rate-limited. On-device inference has no per-frame cost.
|
|
67
|
+
|
|
68
|
+
The tag NAME `:auto-sense` is deliberately unchanged, so existing `docker pull`
|
|
69
|
+
one-liners in the portal and runbooks keep resolving.
|
|
70
|
+
|
|
71
|
+
> **Note:** this says nothing about the DOOH **screen** fleet, whose cloud vision is
|
|
72
|
+
> untouched — different product, different hardware, and it runs server-side.
|
|
14
73
|
|
|
15
74
|
## Registry Locations
|
|
16
75
|
|
|
@@ -22,6 +81,7 @@ Images are published to two registries. Use whichever is reachable from your dep
|
|
|
22
81
|
docker pull ghcr.io/trillboards/edge-sdk:latest
|
|
23
82
|
docker pull ghcr.io/trillboards/edge-sdk:cuda
|
|
24
83
|
docker pull ghcr.io/trillboards/edge-sdk:openvino
|
|
84
|
+
docker pull ghcr.io/trillboards/edge-sdk:auto-sense # BYO-CCTV connector
|
|
25
85
|
```
|
|
26
86
|
|
|
27
87
|
**AWS ECR (private, for production fleet):**
|
|
@@ -253,6 +313,18 @@ docker build -f packages/edge-sdk/deploy/docker/Dockerfile.openvino \
|
|
|
253
313
|
-t trillboards/edge-sdk:openvino .
|
|
254
314
|
```
|
|
255
315
|
|
|
316
|
+
**BYO-CCTV connector image:**
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
cd trillboards-edge-sdk
|
|
320
|
+
docker build -f packages/edge-sdk/deploy/docker/Dockerfile.connector \
|
|
321
|
+
--build-arg SENSE_MODE=auto-sense \
|
|
322
|
+
-t trillboards/edge-sdk:auto-sense .
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
(The file was `Dockerfile.cloud-sense` until edge-sdk 0.2.41. It was renamed because a
|
|
326
|
+
file named after a mode the connector no longer has is a trap for the next reader.)
|
|
327
|
+
|
|
256
328
|
### Build Arguments
|
|
257
329
|
|
|
258
330
|
The Dockerfiles do not expose build arguments by default. To customize the build (for example, to pin a specific Node.js version or add additional system packages), edit the relevant Dockerfile directly.
|
|
@@ -5,50 +5,60 @@
|
|
|
5
5
|
# thereafter it senses. Mount /app/data on a volume so the paired config survives
|
|
6
6
|
# restarts — re-pairing needs a fresh code from the portal.
|
|
7
7
|
#
|
|
8
|
-
# ──
|
|
8
|
+
# ── LOCAL INFERENCE ONLY ───────────────────────────────────────────────────
|
|
9
9
|
#
|
|
10
|
-
# This entrypoint used to `
|
|
11
|
-
#
|
|
12
|
-
#
|
|
10
|
+
# This entrypoint used to assert `localSensing.allowCloudFallback: true` into the
|
|
11
|
+
# paired config on EVERY start, so a plain `docker run` sent every analyzed frame
|
|
12
|
+
# to the cloud VLM. It was one of three places that forced the cloud path (the
|
|
13
|
+
# systemd unit hardcoded `cloud-sense`; `TRILLBOARDS_SENSE_MODE` overrode at run
|
|
14
|
+
# time). All three are gone.
|
|
13
15
|
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
# on-device path from this SAME image, at no per-frame cloud cost.
|
|
16
|
+
# WHY. Measured 2026-08-12 over 255,812 image-bearing cloud VLM calls: $0.007008
|
|
17
|
+
# per analyzed frame — ~$605/camera/month at a 30s cadence, ~$9,688/month for a
|
|
18
|
+
# 16-camera store against a $99-500 subscription. The product was ~10x underwater
|
|
19
|
+
# at scale. On-device inference has no per-frame cost.
|
|
19
20
|
#
|
|
20
|
-
#
|
|
21
|
+
# This SUPERSEDES the escalation architecture from #11971 ("local Gemma decides
|
|
22
|
+
# *when* to look, the cloud decides *what* it saw") and the frequency-based cost
|
|
23
|
+
# control in plans/cctv-byo-sensing-plan.md:26. Cost is no longer managed by
|
|
24
|
+
# looking less often; the metered path is removed.
|
|
21
25
|
#
|
|
22
|
-
#
|
|
23
|
-
# unset it probes, fails, and WAITS — uploading nothing — because on a laptop or
|
|
24
|
-
# a mini-PC that is downloading a 2.4 GB model, idling briefly is better than
|
|
25
|
-
# silently starting a meter. That is right for the macOS/Windows installers,
|
|
26
|
-
# which provision a local model.
|
|
26
|
+
# ── What this image needs from you ────────────────────────────────────────
|
|
27
27
|
#
|
|
28
|
-
# It
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# So the entrypoint asserts `localSensing.allowCloudFallback: true` into the
|
|
32
|
-
# paired config for auto-sense. Net behaviour for a plain run is identical to
|
|
33
|
-
# the old `:cloud-sense` — frames go to the cloud VLM — except the connector now
|
|
34
|
-
# AUTO-UPGRADES to on-device with no restart the moment a litert-lm becomes
|
|
35
|
-
# reachable.
|
|
28
|
+
# It bundles no model, so it needs a litert-lm serve it can reach:
|
|
29
|
+
# docker run --network host ... # serve on localhost:9379
|
|
30
|
+
# # or set localSensing.litertLmBaseUrl to http://host.docker.internal:9379
|
|
36
31
|
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
32
|
+
# Without one, `auto-sense` probes and WAITS: it analyzes nothing and uploads no
|
|
33
|
+
# FRAMES. It is not dark, though — it runs the same manager in health-only mode,
|
|
34
|
+
# so the cameras open, video is proven, and camera health reports on the
|
|
35
|
+
# heartbeat with sensingState='unavailable'. The portal sees a live connector
|
|
36
|
+
# that cannot sense yet, rather than one indistinguishable from a dead machine.
|
|
37
|
+
#
|
|
38
|
+
# TRILLBOARDS_SENSE_MODE selects auto-sense (probe, then local-or-wait) or
|
|
39
|
+
# local-sense (require local, no probe loop). `cloud-sense` is rejected BY NAME
|
|
40
|
+
# rather than ignored, so an existing deployment pinning it stops with a message
|
|
41
|
+
# instead of silently getting different behaviour.
|
|
39
42
|
set -eu
|
|
40
43
|
|
|
41
44
|
CONFIG="${TRILLBOARDS_CONFIG:-/app/data/trillboards.config.json}"
|
|
42
45
|
API_BASE="${TRILLBOARDS_API_BASE:-https://api.trillboards.com}"
|
|
43
46
|
CLI="node packages/edge-sdk/dist/cli.js"
|
|
44
|
-
# Baked per image variant (:auto-sense -> auto-sense, :cloud-sense -> cloud-sense),
|
|
45
|
-
# overridable per run.
|
|
46
47
|
MODE="${TRILLBOARDS_SENSE_MODE:-auto-sense}"
|
|
47
48
|
|
|
48
49
|
case "$MODE" in
|
|
49
|
-
auto-sense|
|
|
50
|
+
auto-sense|local-sense) ;;
|
|
51
|
+
cloud-sense)
|
|
52
|
+
echo "[connector] TRILLBOARDS_SENSE_MODE=cloud-sense is no longer supported." >&2
|
|
53
|
+
echo "[connector] The connector is local-inference only: cloud VLM inference cost" >&2
|
|
54
|
+
echo "[connector] \$0.007008/frame (~\$9,688/month for a 16-camera store), so the path" >&2
|
|
55
|
+
echo "[connector] was removed rather than rate-limited." >&2
|
|
56
|
+
echo "[connector] Use auto-sense (the default) and give the container a litert-lm:" >&2
|
|
57
|
+
echo "[connector] docker run --network host ... # serve on localhost:9379" >&2
|
|
58
|
+
exit 1
|
|
59
|
+
;;
|
|
50
60
|
*)
|
|
51
|
-
echo "[connector] Unknown TRILLBOARDS_SENSE_MODE '$MODE' (want auto-sense|
|
|
61
|
+
echo "[connector] Unknown TRILLBOARDS_SENSE_MODE '$MODE' (want auto-sense|local-sense)." >&2
|
|
52
62
|
exit 1
|
|
53
63
|
;;
|
|
54
64
|
esac
|
|
@@ -57,64 +67,20 @@ if [ ! -f "$CONFIG" ]; then
|
|
|
57
67
|
if [ -z "${TRILLBOARDS_CLAIM_CODE:-}" ]; then
|
|
58
68
|
echo "[connector] No paired config and no TRILLBOARDS_CLAIM_CODE." >&2
|
|
59
69
|
echo "[connector] Get a code from the Trillboards portal (Add a camera -> Install), then:" >&2
|
|
60
|
-
echo "[connector] docker run -e TRILLBOARDS_CLAIM_CODE=XXXX-XXXX -v trillboards:/app/data ghcr.io/trillboards/edge-sdk:
|
|
70
|
+
echo "[connector] docker run --network host -e TRILLBOARDS_CLAIM_CODE=XXXX-XXXX -v trillboards:/app/data ghcr.io/trillboards/edge-sdk:connector" >&2
|
|
61
71
|
exit 1
|
|
62
72
|
fi
|
|
63
73
|
echo "[connector] Pairing with claim code (token delivered at pair, never baked)..."
|
|
64
74
|
# shellcheck disable=SC2086
|
|
65
|
-
$CLI pair --code "$TRILLBOARDS_CLAIM_CODE" --config "$CONFIG"
|
|
66
|
-
fi
|
|
67
|
-
|
|
68
|
-
if [ "$MODE" = "auto-sense" ]; then
|
|
69
|
-
# This image ships no local model, so auto-sense MUST be allowed to fall back
|
|
70
|
-
# or it waits forever for something that will never arrive. Idempotent, and it
|
|
71
|
-
# never touches a value the operator set themselves.
|
|
72
|
-
#
|
|
73
|
-
# The assert WRITES, and a config file is very often mounted read-only
|
|
74
|
-
# (-v ./trillboards.config.json:/app/data/trillboards.config.json:ro)
|
|
75
|
-
# which is a perfectly reasonable way to run this image and skips pairing
|
|
76
|
-
# entirely. Under `set -e` an unguarded writeFileSync there killed the
|
|
77
|
-
# container on every single start, with a raw stack trace, because a DEFAULT
|
|
78
|
-
# could not be persisted. So a failed write degrades to exactly the behaviour
|
|
79
|
-
# the write was there to guarantee — cloud inference this run — and says so.
|
|
80
|
-
set +e
|
|
81
|
-
node -e '
|
|
82
|
-
const fs = require("fs");
|
|
83
|
-
const p = process.argv[1];
|
|
84
|
-
let c;
|
|
85
|
-
try { c = JSON.parse(fs.readFileSync(p, "utf-8")); } catch (e) {
|
|
86
|
-
console.error("[connector] cannot read config for fallback assert: " + e.message);
|
|
87
|
-
process.exit(0);
|
|
88
|
-
}
|
|
89
|
-
c.localSensing = c.localSensing || {};
|
|
90
|
-
if (c.localSensing.allowCloudFallback === true) process.exit(0);
|
|
91
|
-
if (c.localSensing.allowCloudFallback === false) {
|
|
92
|
-
console.error("[connector] localSensing.allowCloudFallback is explicitly false — honoring it; this connector will WAIT for a local runtime and upload nothing until one is reachable.");
|
|
93
|
-
process.exit(0);
|
|
94
|
-
}
|
|
95
|
-
c.localSensing.allowCloudFallback = true;
|
|
96
|
-
try {
|
|
97
|
-
fs.writeFileSync(p, JSON.stringify(c, null, 2));
|
|
98
|
-
} catch (e) {
|
|
99
|
-
console.error("[connector] cannot persist localSensing.allowCloudFallback to " + p + ": " + e.message);
|
|
100
|
-
process.exit(3);
|
|
101
|
-
}
|
|
102
|
-
console.error("[connector] enabled localSensing.allowCloudFallback (this image bundles no local model).");
|
|
103
|
-
' "$CONFIG"
|
|
104
|
-
FALLBACK_ASSERT_RC=$?
|
|
105
|
-
set -e
|
|
106
|
-
if [ "$FALLBACK_ASSERT_RC" = "3" ]; then
|
|
107
|
-
echo "[connector] config is not writable, so auto-sense cannot record its cloud-fallback policy." >&2
|
|
108
|
-
echo "[connector] Running cloud-sense for this process instead — cameras still report. To get the" >&2
|
|
109
|
-
echo "[connector] on-device auto-upgrade, mount the config writable or set localSensing.allowCloudFallback." >&2
|
|
110
|
-
MODE=cloud-sense
|
|
111
|
-
fi
|
|
75
|
+
$CLI pair --code "$TRILLBOARDS_CLAIM_CODE" --config "$CONFIG" --api-base "$API_BASE"
|
|
112
76
|
fi
|
|
113
77
|
|
|
114
78
|
if [ "$MODE" = "auto-sense" ]; then
|
|
115
|
-
echo "[connector] Starting $MODE (API $API_BASE) — probing for on-device inference
|
|
116
|
-
echo "[connector]
|
|
117
|
-
echo "[connector]
|
|
79
|
+
echo "[connector] Starting $MODE (API $API_BASE) — probing for on-device inference." >&2
|
|
80
|
+
echo "[connector] This image bundles no model. If no litert-lm is reachable the" >&2
|
|
81
|
+
echo "[connector] connector analyzes nothing and uploads no frames — but it still" >&2
|
|
82
|
+
echo "[connector] opens the cameras and reports their health, so the portal shows it" >&2
|
|
83
|
+
echo "[connector] alive. It starts sensing automatically once a litert-lm appears." >&2
|
|
118
84
|
else
|
|
119
85
|
echo "[connector] Starting explicitly selected $MODE (API $API_BASE)..." >&2
|
|
120
86
|
fi
|
package/deploy/macos/README.md
CHANGED
|
@@ -97,7 +97,7 @@ bash packages/edge-sdk/deploy/macos/test-install-pair.sh
|
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
No sudo. It builds the pkg, installs it home-domain, starts a local stub
|
|
100
|
-
pair+analyze server, pairs headlessly, runs `
|
|
100
|
+
pair+analyze server, pairs headlessly, runs `auto-sense` against a **synthetic
|
|
101
101
|
ffmpeg source** (a generated clip via `file:` — no RTSP server needed), asserts a
|
|
102
102
|
**real JPEG frame** is produced by the bundled ffmpeg and POSTed with the paired
|
|
103
103
|
`X-Device-Token`, verifies the LaunchAgent bootstraps and senses autonomously,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
#
|
|
3
3
|
# test-install-pair.sh — local, no-sudo, self-cleaning end-to-end proof that the
|
|
4
|
-
# Trillboards
|
|
4
|
+
# Trillboards CCTV connector .pkg builds, installs home-domain,
|
|
5
5
|
# pairs, and actually senses (real bundled-ffmpeg JPEG capture → POST) on THIS
|
|
6
6
|
# Mac.
|
|
7
7
|
#
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
# path runs without an RTSP-server rabbit-hole.
|
|
17
17
|
# 4. Pair headlessly (trillboards-edge pair --code … --api-base …). Assert
|
|
18
18
|
# exit 0, config written 0600 with the camera + deviceToken.
|
|
19
|
-
# 5. Start the connector from that config (
|
|
19
|
+
# 5. Start the connector from that config (auto-sense) and assert it produces
|
|
20
20
|
# a real JPEG frame via the BUNDLED ffmpeg and POSTs it to
|
|
21
21
|
# /v2/earner/audience-analyze carrying the paired X-Device-Token. Also
|
|
22
22
|
# assert the per-user LaunchAgent bootstraps + senses end-to-end.
|
|
@@ -50,7 +50,7 @@ SAMPLE_MP4="$SCRATCH/cctv_sample.mp4"
|
|
|
50
50
|
STUB_JS="$SCRATCH/stub_server.js"
|
|
51
51
|
STUB_OUT="$SCRATCH/stub.out"
|
|
52
52
|
TEST_CONFIG="$SCRATCH/paired.config.json"
|
|
53
|
-
CS_OUT="$SCRATCH/
|
|
53
|
+
CS_OUT="$SCRATCH/auto-sense.out"
|
|
54
54
|
STUB_PID=""
|
|
55
55
|
CS_PID=""
|
|
56
56
|
|
|
@@ -160,7 +160,7 @@ if printf '%s' "$NODE_VER" | grep -q '^v'; then ok "bundled node runs ($NODE_VER
|
|
|
160
160
|
if "$EDGE_ROOT/bin/ffmpeg" -version >/dev/null 2>&1; then ok "bundled ffmpeg runs"; else nok "bundled ffmpeg did not run"; fi
|
|
161
161
|
|
|
162
162
|
# the connector runs through the bundled launcher (this also proves the lazy
|
|
163
|
-
# require keeps the
|
|
163
|
+
# require keeps the pair/sense entrypoint free of the ONNX stack — the bundle
|
|
164
164
|
# ships NO onnxruntime-node).
|
|
165
165
|
LAUNCH_VER="$("$EDGE_ROOT/bin/trillboards-edge" version 2>&1 || true)"
|
|
166
166
|
if printf '%s' "$LAUNCH_VER" | grep -q 'edge-sdk v'; then ok "trillboards-edge version works via bundled node ($LAUNCH_VER)"; else nok "trillboards-edge version failed" "$LAUNCH_VER"; fi
|
|
@@ -277,9 +277,9 @@ else
|
|
|
277
277
|
fi
|
|
278
278
|
|
|
279
279
|
# ─── STEP 5a: connector starts from the config + senses (foreground, bounded) ─
|
|
280
|
-
diag "STEP 5a —
|
|
280
|
+
diag "STEP 5a — auto-sense from config: real bundled-ffmpeg JPEG → POST"
|
|
281
281
|
if [ -f "$TEST_CONFIG" ]; then
|
|
282
|
-
"$EDGE_ROOT/bin/trillboards-edge"
|
|
282
|
+
"$EDGE_ROOT/bin/trillboards-edge" auto-sense --config "$TEST_CONFIG" >"$CS_OUT" 2>&1 &
|
|
283
283
|
CS_PID=$!
|
|
284
284
|
# Wait until we see at least one analyze POST (cadence is 1.5s) — up to ~12s.
|
|
285
285
|
SENSED=0
|
|
@@ -293,9 +293,9 @@ if [ -f "$TEST_CONFIG" ]; then
|
|
|
293
293
|
CS_PID=""
|
|
294
294
|
|
|
295
295
|
if [ "$SENSED" -eq 1 ]; then
|
|
296
|
-
ok "
|
|
296
|
+
ok "auto-sense captured + POSTed a frame (HTTP 200 in connector log)"
|
|
297
297
|
else
|
|
298
|
-
nok "
|
|
298
|
+
nok "auto-sense did not produce a successful POST" "$(tail -8 "$CS_OUT" 2>/dev/null)"
|
|
299
299
|
fi
|
|
300
300
|
|
|
301
301
|
# Inspect what the stub actually received.
|
|
@@ -310,7 +310,7 @@ if [ -f "$TEST_CONFIG" ]; then
|
|
|
310
310
|
nok "stub stats file missing — cannot verify POST contents"
|
|
311
311
|
fi
|
|
312
312
|
else
|
|
313
|
-
nok "skipped
|
|
313
|
+
nok "skipped auto-sense (no config)"
|
|
314
314
|
fi
|
|
315
315
|
|
|
316
316
|
# ─── STEP 5b: LaunchAgent bootstrap + autonomous sensing ─────────────────────
|
|
@@ -39,7 +39,19 @@ cd /opt/trillboards
|
|
|
39
39
|
sudo -u trillboards npm init -y --silent 2>/dev/null
|
|
40
40
|
sudo -u trillboards npm install @trillboards/edge-sdk @trillboards/edge-platform-linux @trillboards/edge-platform-rtsp
|
|
41
41
|
|
|
42
|
-
#
|
|
42
|
+
# LOCAL INFERENCE ONLY. The unit runs `auto-sense`, which is local-or-wait — it
|
|
43
|
+
# never uploads a frame to a cloud VLM. So the on-device runtime is not optional
|
|
44
|
+
# on this platform any more: provision LiteRT-LM + Gemma before the service can
|
|
45
|
+
# do anything. ~2.4 GB, public HF repo, home-domain (no sudo pip).
|
|
46
|
+
echo "Provisioning the on-device AI runtime (LiteRT-LM + Gemma, ~2.4 GB)..."
|
|
47
|
+
sudo cp node_modules/@trillboards/edge-sdk/deploy/systemd/trillboards-litertlm.service /etc/systemd/system/
|
|
48
|
+
sudo systemctl daemon-reload
|
|
49
|
+
sudo systemctl enable trillboards-litertlm
|
|
50
|
+
sudo -u trillboards HOME=/opt/trillboards bash \
|
|
51
|
+
node_modules/@trillboards/edge-sdk/deploy/systemd/setup-litertlm.sh \
|
|
52
|
+
|| echo " *** Local AI provisioning did not finish. The connector will run and report
|
|
53
|
+
*** itself as 'waiting for the on-device model' until it does — it will NOT
|
|
54
|
+
*** fall back to paid cloud inference. Re-run install.sh to retry."
|
|
43
55
|
|
|
44
56
|
# Pair with a claim code from the portal (Add a camera -> Install). Pairing writes
|
|
45
57
|
# /etc/trillboards/config.json (device token + feeds) — the token is delivered at
|
|
@@ -74,5 +86,8 @@ echo "Next steps:"
|
|
|
74
86
|
echo " 1. (If not paired above) sudo -u trillboards npx trillboards-edge pair --code <CODE> --config /etc/trillboards/config.json"
|
|
75
87
|
echo " 2. Start: sudo systemctl start trillboards-edge"
|
|
76
88
|
echo " 3. Logs: sudo journalctl -u trillboards-edge -f"
|
|
89
|
+
echo " 4. Local AI: sudo systemctl status trillboards-litertlm"
|
|
90
|
+
echo " (the connector runs ON-DEVICE only; if the serve is down it waits and"
|
|
91
|
+
echo " reports 'waiting' in the portal rather than billing cloud inference)"
|
|
77
92
|
echo " 4. Status: sudo systemctl status trillboards-edge"
|
|
78
93
|
echo ""
|