@trillboards/edge-sdk 0.2.4 → 0.2.7

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.
Files changed (204) hide show
  1. package/README.md +4 -3
  2. package/deploy/docker/Dockerfile.cloud-sense +117 -0
  3. package/deploy/docker/Dockerfile.cpu +30 -21
  4. package/deploy/docker/Dockerfile.cuda +30 -21
  5. package/deploy/docker/Dockerfile.openvino +30 -20
  6. package/deploy/docker/README.md +9 -5
  7. package/deploy/docker/connector-entrypoint.sh +122 -0
  8. package/deploy/helm/README.md +1 -1
  9. package/deploy/k8s/daemonset.yaml +1 -1
  10. package/deploy/macos/README.md +161 -0
  11. package/deploy/macos/TrillboardsSetupApp.m +603 -0
  12. package/deploy/macos/build-macos-pkg.sh +588 -0
  13. package/deploy/macos/com.trillboards.edge.plist +62 -0
  14. package/deploy/macos/com.trillboards.litertlm.plist +45 -0
  15. package/deploy/macos/pair.command +519 -0
  16. package/deploy/macos/scripts/ensure-litertlm.sh +62 -0
  17. package/deploy/macos/scripts/postinstall +153 -0
  18. package/deploy/macos/scripts/setup-litertlm.sh +272 -0
  19. package/deploy/macos/test-install-pair.sh +383 -0
  20. package/deploy/macos/test-setup-litertlm-reinstall.sh +109 -0
  21. package/deploy/systemd/install.sh +25 -15
  22. package/deploy/systemd/trillboards-edge.service +4 -1
  23. package/deploy/windows/README.md +70 -0
  24. package/deploy/windows/build-windows.ps1 +225 -0
  25. package/deploy/windows/bundle-windows-installer.ps1 +153 -0
  26. package/deploy/windows/check-feeds.js +26 -0
  27. package/deploy/windows/e2e/fixtures/person.jpg +0 -0
  28. package/deploy/windows/e2e/test-local-sense.ps1 +185 -0
  29. package/deploy/windows/install-service.ps1 +6 -1
  30. package/deploy/windows/install-update.ps1 +101 -0
  31. package/deploy/windows/installer.nsi +138 -0
  32. package/deploy/windows/register-litertlm-task.ps1 +42 -0
  33. package/deploy/windows/register-task.ps1 +44 -0
  34. package/deploy/windows/run-connector.ps1 +69 -0
  35. package/deploy/windows/setup-litertlm.ps1 +361 -0
  36. package/deploy/windows/stop-existing.ps1 +72 -0
  37. package/deploy/windows/trillboards-edge-task.vbs +25 -0
  38. package/deploy/windows/trillboards-edge.cmd +33 -0
  39. package/deploy/windows/trillboards-pair.cmd +276 -0
  40. package/deploy/windows/trillboards-setup.ps1 +456 -0
  41. package/deploy/windows/trillboards-setup.vbs +13 -0
  42. package/dist/AudienceAnalyzeClient.d.ts +136 -0
  43. package/dist/AudienceAnalyzeClient.d.ts.map +1 -0
  44. package/dist/AudienceAnalyzeClient.js +246 -0
  45. package/dist/AudienceAnalyzeClient.js.map +1 -0
  46. package/dist/ChannelProber.d.ts +150 -0
  47. package/dist/ChannelProber.d.ts.map +1 -0
  48. package/dist/ChannelProber.js +887 -0
  49. package/dist/ChannelProber.js.map +1 -0
  50. package/dist/CloudEscalationPolicy.d.ts +143 -0
  51. package/dist/CloudEscalationPolicy.d.ts.map +1 -0
  52. package/dist/CloudEscalationPolicy.js +137 -0
  53. package/dist/CloudEscalationPolicy.js.map +1 -0
  54. package/dist/CloudSensingManager.d.ts +557 -0
  55. package/dist/CloudSensingManager.d.ts.map +1 -0
  56. package/dist/CloudSensingManager.js +1267 -0
  57. package/dist/CloudSensingManager.js.map +1 -0
  58. package/dist/DiscoveryReportClient.d.ts +66 -0
  59. package/dist/DiscoveryReportClient.d.ts.map +1 -0
  60. package/dist/DiscoveryReportClient.js +85 -0
  61. package/dist/DiscoveryReportClient.js.map +1 -0
  62. package/dist/EdgeAgent.d.ts +12 -2
  63. package/dist/EdgeAgent.d.ts.map +1 -1
  64. package/dist/EdgeAgent.js +55 -13
  65. package/dist/EdgeAgent.js.map +1 -1
  66. package/dist/LiteRtVlmProvider.d.ts +193 -0
  67. package/dist/LiteRtVlmProvider.d.ts.map +1 -0
  68. package/dist/LiteRtVlmProvider.js +436 -0
  69. package/dist/LiteRtVlmProvider.js.map +1 -0
  70. package/dist/MosaicSource.d.ts +108 -0
  71. package/dist/MosaicSource.d.ts.map +1 -0
  72. package/dist/MosaicSource.js +274 -0
  73. package/dist/MosaicSource.js.map +1 -0
  74. package/dist/OnvifClient.d.ts +83 -0
  75. package/dist/OnvifClient.d.ts.map +1 -0
  76. package/dist/OnvifClient.js +209 -0
  77. package/dist/OnvifClient.js.map +1 -0
  78. package/dist/PairClient.d.ts +106 -0
  79. package/dist/PairClient.d.ts.map +1 -0
  80. package/dist/PairClient.js +186 -0
  81. package/dist/PairClient.js.map +1 -0
  82. package/dist/RecorderAddressRecovery.d.ts +53 -0
  83. package/dist/RecorderAddressRecovery.d.ts.map +1 -0
  84. package/dist/RecorderAddressRecovery.js +359 -0
  85. package/dist/RecorderAddressRecovery.js.map +1 -0
  86. package/dist/SceneChangeDetector.d.ts +291 -0
  87. package/dist/SceneChangeDetector.d.ts.map +1 -0
  88. package/dist/SceneChangeDetector.js +682 -0
  89. package/dist/SceneChangeDetector.js.map +1 -0
  90. package/dist/SensingProfileClient.d.ts +59 -0
  91. package/dist/SensingProfileClient.d.ts.map +1 -0
  92. package/dist/SensingProfileClient.js +78 -0
  93. package/dist/SensingProfileClient.js.map +1 -0
  94. package/dist/SetupSessionClient.d.ts +114 -0
  95. package/dist/SetupSessionClient.d.ts.map +1 -0
  96. package/dist/SetupSessionClient.js +95 -0
  97. package/dist/SetupSessionClient.js.map +1 -0
  98. package/dist/VlmAdmissionGate.d.ts +126 -0
  99. package/dist/VlmAdmissionGate.d.ts.map +1 -0
  100. package/dist/VlmAdmissionGate.js +166 -0
  101. package/dist/VlmAdmissionGate.js.map +1 -0
  102. package/dist/autoSense.d.ts +266 -0
  103. package/dist/autoSense.d.ts.map +1 -0
  104. package/dist/autoSense.js +606 -0
  105. package/dist/autoSense.js.map +1 -0
  106. package/dist/cli.d.ts +27 -1
  107. package/dist/cli.d.ts.map +1 -1
  108. package/dist/cli.js +526 -27
  109. package/dist/cli.js.map +1 -1
  110. package/dist/combinedSense.d.ts +128 -0
  111. package/dist/combinedSense.d.ts.map +1 -0
  112. package/dist/combinedSense.js +261 -0
  113. package/dist/combinedSense.js.map +1 -0
  114. package/dist/config.d.ts +351 -0
  115. package/dist/config.d.ts.map +1 -1
  116. package/dist/config.js +99 -2
  117. package/dist/config.js.map +1 -1
  118. package/dist/connectorCadence.d.ts +135 -0
  119. package/dist/connectorCadence.d.ts.map +1 -0
  120. package/dist/connectorCadence.js +151 -0
  121. package/dist/connectorCadence.js.map +1 -0
  122. package/dist/dataDir.d.ts +33 -0
  123. package/dist/dataDir.d.ts.map +1 -0
  124. package/dist/dataDir.js +76 -0
  125. package/dist/dataDir.js.map +1 -0
  126. package/dist/deployedCadence.d.ts +167 -0
  127. package/dist/deployedCadence.d.ts.map +1 -0
  128. package/dist/deployedCadence.js +209 -0
  129. package/dist/deployedCadence.js.map +1 -0
  130. package/dist/deployedCadenceSource.d.ts +48 -0
  131. package/dist/deployedCadenceSource.d.ts.map +1 -0
  132. package/dist/deployedCadenceSource.js +74 -0
  133. package/dist/deployedCadenceSource.js.map +1 -0
  134. package/dist/diagnose.d.ts +1 -1
  135. package/dist/diagnose.d.ts.map +1 -1
  136. package/dist/diagnose.js +15 -13
  137. package/dist/diagnose.js.map +1 -1
  138. package/dist/discovery.d.ts +131 -0
  139. package/dist/discovery.d.ts.map +1 -0
  140. package/dist/discovery.js +499 -0
  141. package/dist/discovery.js.map +1 -0
  142. package/dist/gemmaModelOta.d.ts +118 -0
  143. package/dist/gemmaModelOta.d.ts.map +1 -0
  144. package/dist/gemmaModelOta.js +291 -0
  145. package/dist/gemmaModelOta.js.map +1 -0
  146. package/dist/index.d.ts +16 -1
  147. package/dist/index.d.ts.map +1 -1
  148. package/dist/index.js +40 -1
  149. package/dist/index.js.map +1 -1
  150. package/dist/init.d.ts.map +1 -1
  151. package/dist/init.js +25 -87
  152. package/dist/init.js.map +1 -1
  153. package/dist/keepAwake.d.ts +33 -0
  154. package/dist/keepAwake.d.ts.map +1 -0
  155. package/dist/keepAwake.js +54 -0
  156. package/dist/keepAwake.js.map +1 -0
  157. package/dist/legacyRecorderProfile.d.ts +20 -0
  158. package/dist/legacyRecorderProfile.d.ts.map +1 -0
  159. package/dist/legacyRecorderProfile.js +27 -0
  160. package/dist/legacyRecorderProfile.js.map +1 -0
  161. package/dist/localInferenceSchema.d.ts +132 -0
  162. package/dist/localInferenceSchema.d.ts.map +1 -0
  163. package/dist/localInferenceSchema.js +274 -0
  164. package/dist/localInferenceSchema.js.map +1 -0
  165. package/dist/localSenseE2eHarness.d.ts +101 -0
  166. package/dist/localSenseE2eHarness.d.ts.map +1 -0
  167. package/dist/localSenseE2eHarness.js +279 -0
  168. package/dist/localSenseE2eHarness.js.map +1 -0
  169. package/dist/localSensing.d.ts +284 -0
  170. package/dist/localSensing.d.ts.map +1 -0
  171. package/dist/localSensing.js +512 -0
  172. package/dist/localSensing.js.map +1 -0
  173. package/dist/mcp-server.d.ts.map +1 -1
  174. package/dist/mcp-server.js +3 -1
  175. package/dist/mcp-server.js.map +1 -1
  176. package/dist/modelIntegrity.d.ts +80 -0
  177. package/dist/modelIntegrity.d.ts.map +1 -0
  178. package/dist/modelIntegrity.js +194 -0
  179. package/dist/modelIntegrity.js.map +1 -0
  180. package/dist/pairVerify.d.ts +100 -0
  181. package/dist/pairVerify.d.ts.map +1 -0
  182. package/dist/pairVerify.js +296 -0
  183. package/dist/pairVerify.js.map +1 -0
  184. package/dist/qrScanner.d.ts +33 -0
  185. package/dist/qrScanner.d.ts.map +1 -0
  186. package/dist/qrScanner.js +103 -0
  187. package/dist/qrScanner.js.map +1 -0
  188. package/dist/runtimePaths.d.ts +10 -0
  189. package/dist/runtimePaths.d.ts.map +1 -0
  190. package/dist/runtimePaths.js +79 -0
  191. package/dist/runtimePaths.js.map +1 -0
  192. package/dist/sensingRunMode.d.ts +86 -0
  193. package/dist/sensingRunMode.d.ts.map +1 -0
  194. package/dist/sensingRunMode.js +346 -0
  195. package/dist/sensingRunMode.js.map +1 -0
  196. package/dist/setupMode.d.ts +98 -0
  197. package/dist/setupMode.d.ts.map +1 -0
  198. package/dist/setupMode.js +828 -0
  199. package/dist/setupMode.js.map +1 -0
  200. package/dist/setupProgress.d.ts +31 -0
  201. package/dist/setupProgress.d.ts.map +1 -0
  202. package/dist/setupProgress.js +173 -0
  203. package/dist/setupProgress.js.map +1 -0
  204. package/package.json +13 -4
@@ -0,0 +1,346 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SENSING_HEALTH_LOG_INTERVAL_MS = exports.NO_FEEDS_MESSAGE = exports.consoleIo = void 0;
4
+ exports.formatSensingHealth = formatSensingHealth;
5
+ exports.buildCloudSensingManager = buildCloudSensingManager;
6
+ exports.buildLocalSensingManager = buildLocalSensingManager;
7
+ const CloudSensingManager_1 = require("./CloudSensingManager");
8
+ const localSensing_1 = require("./localSensing");
9
+ const SensingProfileClient_1 = require("./SensingProfileClient");
10
+ const deployedCadenceSource_1 = require("./deployedCadenceSource");
11
+ const RecorderAddressRecovery_1 = require("./RecorderAddressRecovery");
12
+ /**
13
+ * Shared construction of the cloud- and local-sensing managers.
14
+ *
15
+ * Both the explicit `cloud-sense` / `local-sense` CLI entrypoints AND the
16
+ * `auto-sense` supervisor build their managers through here, so there is exactly
17
+ * ONE place that wires an EdgeAgentConfig → a real `CloudSensingManager` (loops +
18
+ * cadence + event logging). The supervisor starts/stops the SAME real managers
19
+ * the explicit modes use — no parallel sensing implementation.
20
+ */
21
+ /**
22
+ * One profile client per feed, so the connector can ask what cadence its
23
+ * operator actually deployed. Each feed's device token binds to its own screen,
24
+ * so this is N clients, not one — the same shape local sensing already uses to
25
+ * fetch each screen's custom fields.
26
+ *
27
+ * Returns undefined when nothing can be asked (no feed carries a device token),
28
+ * which leaves the manager on its constructor cadence exactly as before.
29
+ */
30
+ function cadenceSourceFor(config, feeds, mode) {
31
+ const profileClients = feeds
32
+ .filter((f) => !!f.deviceToken)
33
+ .map((f) => new SensingProfileClient_1.SensingProfileClient({
34
+ apiBaseUrl: config.apiBaseUrl,
35
+ deviceToken: f.deviceToken,
36
+ }));
37
+ return (0, deployedCadenceSource_1.buildDeployedCadenceSource)({
38
+ profileClients,
39
+ mode,
40
+ feedCount: feeds.length,
41
+ monthlyBudgetUsdPerCamera: config.cloudSensing?.monthlyBudgetUsdPerCamera,
42
+ });
43
+ }
44
+ const DEFAULT_LITERT_LM_BASE_URL = 'http://localhost:9379';
45
+ const DEFAULT_LITERT_LM_MODEL = 'gemma-4-E2B-it.litertlm';
46
+ /** Default sink: the real console (what the CLI entrypoints use). */
47
+ exports.consoleIo = {
48
+ log: (m) => console.log(m),
49
+ warn: (m) => console.warn(m),
50
+ error: (m) => console.error(m),
51
+ };
52
+ /**
53
+ * The error logged (and `null` returned) when a build is attempted with no
54
+ * camera feeds configured. Exported so the supervisor + CLI surface the SAME
55
+ * message and so tests can assert on it.
56
+ */
57
+ exports.NO_FEEDS_MESSAGE = 'No camera feeds configured. Set `cameras: [{ name, screenId, deviceToken, rtspUrl }]` ' +
58
+ 'for multi-channel, or `camera.rtspUrl` + `deviceToken` for a single feed.';
59
+ /**
60
+ * How often the connector prints the one line that answers "why is my
61
+ * 16-camera store only sampling every N seconds?". Long enough not to spam a
62
+ * 24/7 log, short enough that an operator watching an install sees it.
63
+ */
64
+ exports.SENSING_HEALTH_LOG_INTERVAL_MS = 5 * 60 * 1000;
65
+ const CADENCE_CLAMP_WORDING = {
66
+ no_request: '',
67
+ operator_request: '',
68
+ cloud_cost_budget: 'cloud spend budget',
69
+ local_inference_budget: 'this machine\'s single inference slot',
70
+ look_interval: 'the connector never looks that often',
71
+ cadence_ceiling: 'a capture loop cannot run a cadence that slow',
72
+ };
73
+ /**
74
+ * The deployed-cadence fragment of the capacity line.
75
+ *
76
+ * `resolveDeployedCadence` exists to make a clamp legible instead of silent, and
77
+ * `getSensingHealth()` duly returns the whole verdict — but this formatter, the
78
+ * ONLY thing in the connector that a human ever reads, dropped it. An operator
79
+ * who set "every 30 seconds" in the portal and is being run at 303 saw a line
80
+ * about analyses/minute and nothing at all about the 10x gap or its reason. A
81
+ * clamp nobody is told about is a silent clamp however carefully the verdict
82
+ * that produced it was modelled.
83
+ *
84
+ * The projected bill rides along whenever the cloud path knows it, clamped or
85
+ * not: "what is this camera costing me" is the other question this number is
86
+ * the only answer to.
87
+ */
88
+ function formatDeployedCadence(cadence) {
89
+ if (!cadence || cadence.intervalMs === null)
90
+ return [];
91
+ const secs = (ms) => `${(ms / 1000).toFixed(0)}s`;
92
+ const parts = [];
93
+ if (cadence.clamped && cadence.requestedMs !== null) {
94
+ parts.push(`deployed ${secs(cadence.requestedMs)} → running ${secs(cadence.intervalMs)} ` +
95
+ `(${CADENCE_CLAMP_WORDING[cadence.reason] || cadence.reason})`);
96
+ }
97
+ else {
98
+ parts.push(`deployed cadence ${secs(cadence.intervalMs)}`);
99
+ }
100
+ if (cadence.estimatedMonthlyUsdPerCamera !== null) {
101
+ parts.push(`~$${cadence.estimatedMonthlyUsdPerCamera.toFixed(0)}/camera/month`);
102
+ }
103
+ return parts;
104
+ }
105
+ /**
106
+ * The operator-facing degradation line. A machine that cannot analyze 16
107
+ * cameras every 30 s is not a bug to hide — it is a capacity fact to state, in
108
+ * the terms an operator can act on: how often each camera is actually looked
109
+ * at, how many analyses/minute this machine sustains, and where the rest went.
110
+ */
111
+ function formatSensingHealth(health) {
112
+ const perCamera = health.effectiveSampleIntervalMs === null
113
+ ? 'no analyses yet'
114
+ : `every ${(health.effectiveSampleIntervalMs / 1000).toFixed(0)}s per camera`;
115
+ const parts = [
116
+ `${health.loops} feed${health.loops === 1 ? '' : 's'}`,
117
+ `sampling ${perCamera}`,
118
+ `sustained ${health.analysesPerMinute.toFixed(1)} analyses/min (cap ${health.maxConcurrentAnalyses} concurrent)`,
119
+ ];
120
+ if (health.motionSkipped > 0)
121
+ parts.push(`${health.motionSkipped} skipped (no change)`);
122
+ if (health.admissionDenied > 0)
123
+ parts.push(`${health.admissionDenied} deferred (model busy)`);
124
+ parts.push(...formatDeployedCadence(health.deployedCadence));
125
+ // Frames that LEFT the premises for cloud confirmation. Printed whenever
126
+ // escalation is on — including at zero — because "how many frames left" is a
127
+ // question an operator must be able to answer from the log, and an absent
128
+ // line reads as "none" whether or not the feature is even running.
129
+ if (health.cloudEscalationEnabled) {
130
+ parts.push(`${health.cloudEscalated} escalated to cloud (confirming alerts)`);
131
+ }
132
+ return `[sensing] ${parts.join(' · ')}`;
133
+ }
134
+ /**
135
+ * Wire the manager's lifecycle events to the IO sink — identical for both modes
136
+ * except the per-frame "analyzed" line (cloud says `analyze →`, local says
137
+ * `local→preAnalyzed →`).
138
+ *
139
+ * The cascade's per-frame outcomes (`motionSkipped`, `admissionDenied`) are
140
+ * deliberately NOT logged per event: on a saturated 16-camera connector they
141
+ * would be tens of thousands of lines a day and would bury the events that
142
+ * need a human. They stay on the EventEmitter for embedders, and reach the
143
+ * operator as counts on the periodic capacity line below.
144
+ */
145
+ function wireSensingLogs(manager, io, analyzedLine) {
146
+ manager.on('analyzed', ({ name, response }) => {
147
+ io.log(analyzedLine(name, response));
148
+ });
149
+ manager.on('reconnecting', ({ name, delayMs }) => {
150
+ io.warn(`[${name}] RTSP down — re-opening (next retry in ${delayMs}ms)…`);
151
+ });
152
+ manager.on('reconnected', ({ name }) => {
153
+ io.log(`[${name}] RTSP reconnected — sensing resumed`);
154
+ });
155
+ manager.on('error', ({ name, error }) => {
156
+ io.error(`[${name}] error: ${error.message}`);
157
+ });
158
+ // Periodic capacity line. `unref()` so it can never hold the process open.
159
+ const timer = setInterval(() => {
160
+ if (!manager.isRunning())
161
+ return;
162
+ io.log(formatSensingHealth(manager.getSensingHealth()));
163
+ }, exports.SENSING_HEALTH_LOG_INTERVAL_MS);
164
+ if (typeof timer.unref === 'function')
165
+ timer.unref();
166
+ }
167
+ /** Print the per-feed startup banner (the lines the CLI used to print inline). */
168
+ function logFeedBanner(io, header, feeds) {
169
+ for (const line of header)
170
+ io.log(line);
171
+ io.log(` Feeds: ${feeds.length}`);
172
+ for (const f of feeds) {
173
+ io.log(` - ${f.name}${f.screenId ? ` (screen ${f.screenId})` : ''}`);
174
+ }
175
+ io.log('');
176
+ }
177
+ /**
178
+ * Build (but do NOT start) the thin cloud-sensing manager: RTSP frame → POST
179
+ * /v2/earner/audience-analyze (cloud VLM does the inference). Returns `null`
180
+ * after logging NO_FEEDS_MESSAGE when no feeds are configured — the caller
181
+ * decides how to surface that (the CLI + the supervisor both exit 1).
182
+ */
183
+ function buildCloudSensingManager(config, io = exports.consoleIo, opts) {
184
+ const feeds = (0, CloudSensingManager_1.resolveFeeds)(config);
185
+ if (feeds.length === 0) {
186
+ io.error(exports.NO_FEEDS_MESSAGE);
187
+ return null;
188
+ }
189
+ logFeedBanner(io, [` API: ${config.apiBaseUrl}`], feeds);
190
+ const addressRecovery = opts?.streamUrlResolver
191
+ ? null
192
+ : new RecorderAddressRecovery_1.RecorderAddressRecovery(config, {
193
+ configPath: opts?.configPath,
194
+ log: (message) => io.warn(message),
195
+ });
196
+ const loops = (0, CloudSensingManager_1.buildSensingLoops)(config, {
197
+ sharedSources: opts?.sharedSources,
198
+ streamUrlResolver: (feed, currentUrl, error) => opts?.streamUrlResolver?.(feed, currentUrl, error) ??
199
+ addressRecovery.recover(feed, currentUrl, error),
200
+ });
201
+ const manager = new CloudSensingManager_1.CloudSensingManager({
202
+ loops,
203
+ periodicIntervalMs: config.cloudSensing?.periodicIntervalMs,
204
+ faceTriggeredIntervalMs: config.cloudSensing?.faceTriggeredIntervalMs,
205
+ jpegQuality: config.cloudSensing?.jpegQuality,
206
+ // The cadence the operator deployed from the portal, re-asked on the profile
207
+ // TTL so it reaches a RUNNING connector. Clamped by the cloud spend budget —
208
+ // see deployedCadence. Before this the cloud path made NO config-bearing
209
+ // server call after start(), so a portal cadence change reached it never.
210
+ cadenceSource: cadenceSourceFor(config, feeds, 'cloud'),
211
+ // Thin mode: no local face detector drives face state, so POST on the
212
+ // periodic cadence (the cloud VLM does the inference). Honor an explicit
213
+ // opt-in to face-gating if the operator wires an edge motion gate.
214
+ skipPostWhenNoFaces: config.cloudSensing?.skipPostWhenNoFaces ?? false,
215
+ width: config.camera?.width,
216
+ height: config.camera?.height,
217
+ // Self-recover from RTSP drops (retry forever, bounded exponential backoff).
218
+ reconnectInitialDelayMs: config.cloudSensing?.reconnectInitialDelayMs,
219
+ reconnectMaxDelayMs: config.cloudSensing?.reconnectMaxDelayMs,
220
+ // Phase multi-camera loops apart so one connector does not stampede an NVR.
221
+ feedStaggerMs: config.cloudSensing?.feedStaggerMs,
222
+ maxFeedStaggerMs: config.cloudSensing?.maxFeedStaggerMs,
223
+ cameraHealthFailureGraceCount: config.cloudSensing?.cameraHealthFailureGraceCount,
224
+ // The cloud VLM is genuinely concurrent, so admission stays wide open
225
+ // unless the operator narrows it — no behavior change for cloud sensing.
226
+ maxConcurrentAnalyses: config.cloudSensing?.maxConcurrentAnalyses,
227
+ maxAnalysisInFlightMs: config.cloudSensing?.maxAnalysisInFlightMs,
228
+ admissionRetryDelayMs: config.cloudSensing?.admissionRetryDelayMs,
229
+ // Motion cascade is OPT-IN here: cloud has no single-instance ceiling to
230
+ // relieve, and looking more often would raise the per-call bill instead of
231
+ // throughput. Local sensing enables it by default (see below).
232
+ motionGate: resolveMotionGateOptions(config, false),
233
+ });
234
+ wireSensingLogs(manager, io, (name, response) => `[${name}] analyze → HTTP ${response.statusCode}${response.ok ? '' : ' (NOT OK)'}`);
235
+ return manager;
236
+ }
237
+ /**
238
+ * Resolve the motion-cascade options for a run mode, or `undefined` when the
239
+ * cascade is off. `enabledByDefault` differs by mode; an explicit
240
+ * `cloudSensing.motionGate.enabled` always wins.
241
+ */
242
+ function resolveMotionGateOptions(config, enabledByDefault) {
243
+ const configured = config.cloudSensing?.motionGate;
244
+ const enabled = configured?.enabled ?? enabledByDefault;
245
+ if (!enabled)
246
+ return undefined;
247
+ return {
248
+ enabled: true,
249
+ ...(configured?.scanIntervalMs !== undefined
250
+ ? { scanIntervalMs: configured.scanIntervalMs }
251
+ : {}),
252
+ ...(configured?.heartbeatIntervalMs !== undefined
253
+ ? { heartbeatIntervalMs: configured.heartbeatIntervalMs }
254
+ : {}),
255
+ ...(configured?.minAnalyzeIntervalMs !== undefined
256
+ ? { minAnalyzeIntervalMs: configured.minAnalyzeIntervalMs }
257
+ : {}),
258
+ ...(configured?.frameHeight !== undefined ? { frameHeight: configured.frameHeight } : {}),
259
+ ...(configured?.threshold !== undefined ? { threshold: configured.threshold } : {}),
260
+ ...(configured?.contourArea !== undefined ? { contourArea: configured.contourArea } : {}),
261
+ ...(configured?.lightningThreshold !== undefined
262
+ ? { lightningThreshold: configured.lightningThreshold }
263
+ : {}),
264
+ ...(configured?.stationaryReanalyzeEvery !== undefined
265
+ ? { stationaryReanalyzeEvery: configured.stationaryReanalyzeEvery }
266
+ : {}),
267
+ };
268
+ }
269
+ /**
270
+ * Build (but do NOT start) the on-device LOCAL-sensing manager: RTSP frame →
271
+ * LOCAL VLM (LiteRT-LM serving Gemma) → POST the STRUCTURED result as
272
+ * `preAnalyzed` (skips the cloud VLM → no Vertex cost). Returns `null` after
273
+ * logging NO_FEEDS_MESSAGE when no feeds are configured.
274
+ */
275
+ function buildLocalSensingManager(config, io = exports.consoleIo, opts) {
276
+ const feeds = (0, CloudSensingManager_1.resolveFeeds)(config);
277
+ if (feeds.length === 0) {
278
+ io.error(exports.NO_FEEDS_MESSAGE);
279
+ return null;
280
+ }
281
+ const litertLm = config.localSensing?.litertLmBaseUrl || DEFAULT_LITERT_LM_BASE_URL;
282
+ const model = config.localSensing?.model || DEFAULT_LITERT_LM_MODEL;
283
+ // The delegate is resolved by LiteRtVlmProvider at first inference (it reads
284
+ // /v1/models and prefers the `,gpu` variant) and logged there — the banner
285
+ // reports the POLICY so a pinned 'cpu' is visible at startup.
286
+ const accelerator = config.localSensing?.accelerator || 'auto';
287
+ // State the egress posture at startup, in both directions. "Nothing leaves
288
+ // your premises" is only true of the on-device path, so an operator must see
289
+ // which one they are running BEFORE the first frame, not discover it in a log
290
+ // line an hour later.
291
+ // isCloudEscalationEnabled is the SAME expression LocalSensingClient enforces
292
+ // (it also honors attachEvidenceFrame:false), so this line can never announce
293
+ // an egress posture the connector is not actually running.
294
+ const escalation = config.localSensing?.cloudEscalation;
295
+ const escalationLine = (0, localSensing_1.isCloudEscalationEnabled)(config.localSensing)
296
+ ? ` Escalation: ON — a flagged frame is sent to the cloud VLM to confirm an alert ` +
297
+ `(max ${escalation?.maxPerHour ?? 12}/hour per camera)`
298
+ : ' Escalation: off — every frame is analyzed on this machine';
299
+ logFeedBanner(io, [
300
+ ` API: ${config.apiBaseUrl}`,
301
+ ` LiteRT-LM: ${litertLm} (model ${model}, accelerator ${accelerator})`,
302
+ escalationLine,
303
+ ], feeds);
304
+ const addressRecovery = opts?.streamUrlResolver
305
+ ? null
306
+ : new RecorderAddressRecovery_1.RecorderAddressRecovery(config, {
307
+ configPath: opts?.configPath,
308
+ log: (message) => io.warn(message),
309
+ });
310
+ const loops = (0, localSensing_1.buildLocalSensingLoops)(config, {
311
+ sharedSources: opts?.sharedSources,
312
+ streamUrlResolver: (feed, currentUrl, error) => opts?.streamUrlResolver?.(feed, currentUrl, error) ??
313
+ addressRecovery.recover(feed, currentUrl, error),
314
+ });
315
+ const manager = new CloudSensingManager_1.CloudSensingManager({
316
+ loops,
317
+ periodicIntervalMs: config.localSensing?.captureIntervalMs ?? config.cloudSensing?.periodicIntervalMs,
318
+ faceTriggeredIntervalMs: config.cloudSensing?.faceTriggeredIntervalMs,
319
+ // Same deployed cadence, clamped instead by the single local inference slot
320
+ // (connectorCadence's measured 7.30 s/frame ceiling) rather than by money.
321
+ cadenceSource: cadenceSourceFor(config, feeds, 'local'),
322
+ // Local inference has no cloud cost → POST every periodic frame.
323
+ skipPostWhenNoFaces: false,
324
+ width: config.camera?.width,
325
+ height: config.camera?.height,
326
+ // Self-recover from RTSP drops (retry forever, bounded exponential backoff).
327
+ reconnectInitialDelayMs: config.cloudSensing?.reconnectInitialDelayMs,
328
+ reconnectMaxDelayMs: config.cloudSensing?.reconnectMaxDelayMs,
329
+ // Same RTSP/NVR friendliness as cloud mode; local VLM only changes analysis.
330
+ feedStaggerMs: config.cloudSensing?.feedStaggerMs,
331
+ maxFeedStaggerMs: config.cloudSensing?.maxFeedStaggerMs,
332
+ cameraHealthFailureGraceCount: config.cloudSensing?.cameraHealthFailureGraceCount,
333
+ // `litert-lm serve` takes ONE conversation at a time. Without this gate N
334
+ // feeds pile onto it, frames age against the provider's 120 s timeout, and
335
+ // each failure burns retries — 16 cameras produce ~17x the work the server
336
+ // can do. One slot, shared by every feed, drop-not-queue.
337
+ maxConcurrentAnalyses: config.cloudSensing?.maxConcurrentAnalyses ?? 1,
338
+ maxAnalysisInFlightMs: config.cloudSensing?.maxAnalysisInFlightMs,
339
+ admissionRetryDelayMs: config.cloudSensing?.admissionRetryDelayMs,
340
+ // …and don't spend that one slot on an empty aisle: the cascade decides.
341
+ motionGate: resolveMotionGateOptions(config, true),
342
+ });
343
+ wireSensingLogs(manager, io, (name, response) => `[${name}] local→preAnalyzed → HTTP ${response.statusCode}${response.ok ? '' : ' (NOT OK)'}`);
344
+ return manager;
345
+ }
346
+ //# sourceMappingURL=sensingRunMode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sensingRunMode.js","sourceRoot":"","sources":["../src/sensingRunMode.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ import { SetupSessionClient } from './SetupSessionClient';
2
+ import { probeHost, type ChannelProbeResult } from './ChannelProber';
3
+ import { discoverOnvifHosts, sweepLanForRecorders } from './discovery';
4
+ import type { SetupProgressReporter } from './setupProgress';
5
+ /**
6
+ * setupMode — `trillboards-edge setup`: the connector side of the
7
+ * validate-first onboarding flow (direction-inverted pairing).
8
+ *
9
+ * 1. start a SETUP SESSION and DISPLAY the short code (the owner claims it
10
+ * in the portal — the ad-screen direction: device shows, owner types)
11
+ * 2. poll; once claimed, run LAN discovery (ONVIF multicast + a TCP
12
+ * port-sweep for the huge no-ONVIF analog-DVR installed base) and report
13
+ * 3. execute probe/discover commands (ChannelProber — NVR-safe: auth
14
+ * circuit-breaker, session-slot caps, real enumeration), streaming
15
+ * per-channel results + thumbnails as they settle
16
+ * 4. when the owner finalizes, receive the config (screens + device tokens +
17
+ * credential-free connection recipes), reassemble full rtsp:// URLs from
18
+ * the LOCALLY-held credentials (they never round-trip through the
19
+ * server), write trillboards.config.json, and exit 0 — the platform
20
+ * wrapper then starts + verifies the background sensing service exactly
21
+ * as in the honest-pairing flow.
22
+ *
23
+ * NVR credentials live ONLY in memory here (delivered per probe command) and
24
+ * are persisted solely into the local config file (0600) at finalize.
25
+ */
26
+ export interface SetupModeOptions {
27
+ apiBaseUrl: string;
28
+ /** Portal opened for the automatic signed-in browser handoff. */
29
+ portalBaseUrl?: string;
30
+ configPath: string;
31
+ connectorVersion: string;
32
+ ffmpegPath?: string;
33
+ /** Overall wait for the owner to complete the wizard. Default 2h. */
34
+ maxWaitMs?: number;
35
+ client?: SetupSessionClient;
36
+ log?: (line: string) => void;
37
+ /** Injection points for tests. */
38
+ discover?: typeof discoverOnvifHosts;
39
+ sweep?: typeof sweepLanForRecorders;
40
+ probe?: typeof probeHost;
41
+ sleep?: (ms: number) => Promise<void>;
42
+ /** Injectable browser opener; false keeps the displayed-code fallback. */
43
+ openPortal?: (url: string) => Promise<boolean>;
44
+ /** Customer-safe structured progress for native installer applications. */
45
+ progress?: SetupProgressReporter;
46
+ /** Stable LAN identity fallback for recorders without ONVIF serials. */
47
+ lookupMac?: (host: string) => Promise<string | null>;
48
+ }
49
+ /** Stable, non-PII connector fingerprint (same derivation as cli.ts pairing). */
50
+ export declare function setupHostFingerprint(): string;
51
+ /**
52
+ * Build the one-time browser handoff without sending the setup code to the web
53
+ * server. URL fragments stay client-side; the portal consumes and removes it.
54
+ */
55
+ export declare function buildSetupPortalUrl(displayCode: string, portalBaseUrl?: string): string;
56
+ /** Open the setup handoff in the owner's default browser. Never uses a shell. */
57
+ export declare function openSetupPortal(url: string, platform?: NodeJS.Platform): Promise<boolean>;
58
+ export declare function normalizeDirectStreamUrl(input: string): string;
59
+ export declare function mergeDiscoveredHosts(onvifHosts: unknown[], sweptHosts: unknown[]): unknown[];
60
+ /**
61
+ * Merge a finalized setup into the local config.
62
+ *
63
+ * The finalized channel set is authoritative for each recorder touched by this
64
+ * setup. That prevents deleted/replaced portal cameras from remaining as hidden
65
+ * local sensing loops, while feeds from other recorders and direct RTSPS links
66
+ * remain intact.
67
+ */
68
+ export declare function mergeConfigFile(existingRaw: string | null, incoming: {
69
+ apiBaseUrl: string;
70
+ cloudSensing: {
71
+ enabled: boolean;
72
+ periodicIntervalMs?: number;
73
+ };
74
+ localSensing?: {
75
+ enabled: boolean;
76
+ allowCloudFallback?: boolean;
77
+ };
78
+ cameras: Array<{
79
+ name: string;
80
+ screenId: string;
81
+ deviceToken: string;
82
+ channelKey: string;
83
+ host?: string;
84
+ port?: number;
85
+ pathTemplate?: string;
86
+ transport?: 'tcp' | 'udp';
87
+ sourceType?: 'recorder' | 'direct_url';
88
+ }>;
89
+ }, credsByHost: Map<string, {
90
+ username: string;
91
+ password: string;
92
+ }>, directUrlsByChannelKey?: Map<string, string>, probedByChannelKey?: Map<string, ChannelProbeResult>, macByHost?: Map<string, string>): Record<string, unknown>;
93
+ /**
94
+ * Run the interactive setup session. Returns 0 on config-written, 2 on
95
+ * session-expired (restart for a fresh code), 1 on hard errors.
96
+ */
97
+ export declare function runSetupMode(opts: SetupModeOptions): Promise<number>;
98
+ //# sourceMappingURL=setupMode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setupMode.d.ts","sourceRoot":"","sources":["../src/setupMode.ts"],"names":[],"mappings":"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"}