@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.
- package/README.md +4 -3
- package/deploy/docker/Dockerfile.cloud-sense +117 -0
- package/deploy/docker/Dockerfile.cpu +30 -21
- package/deploy/docker/Dockerfile.cuda +30 -21
- package/deploy/docker/Dockerfile.openvino +30 -20
- package/deploy/docker/README.md +9 -5
- package/deploy/docker/connector-entrypoint.sh +122 -0
- package/deploy/helm/README.md +1 -1
- package/deploy/k8s/daemonset.yaml +1 -1
- package/deploy/macos/README.md +161 -0
- package/deploy/macos/TrillboardsSetupApp.m +603 -0
- package/deploy/macos/build-macos-pkg.sh +588 -0
- package/deploy/macos/com.trillboards.edge.plist +62 -0
- package/deploy/macos/com.trillboards.litertlm.plist +45 -0
- package/deploy/macos/pair.command +519 -0
- package/deploy/macos/scripts/ensure-litertlm.sh +62 -0
- package/deploy/macos/scripts/postinstall +153 -0
- package/deploy/macos/scripts/setup-litertlm.sh +272 -0
- package/deploy/macos/test-install-pair.sh +383 -0
- package/deploy/macos/test-setup-litertlm-reinstall.sh +109 -0
- package/deploy/systemd/install.sh +25 -15
- package/deploy/systemd/trillboards-edge.service +4 -1
- package/deploy/windows/README.md +70 -0
- package/deploy/windows/build-windows.ps1 +225 -0
- package/deploy/windows/bundle-windows-installer.ps1 +153 -0
- package/deploy/windows/check-feeds.js +26 -0
- package/deploy/windows/e2e/fixtures/person.jpg +0 -0
- package/deploy/windows/e2e/test-local-sense.ps1 +185 -0
- package/deploy/windows/install-service.ps1 +6 -1
- package/deploy/windows/install-update.ps1 +101 -0
- package/deploy/windows/installer.nsi +138 -0
- package/deploy/windows/register-litertlm-task.ps1 +42 -0
- package/deploy/windows/register-task.ps1 +44 -0
- package/deploy/windows/run-connector.ps1 +69 -0
- package/deploy/windows/setup-litertlm.ps1 +361 -0
- package/deploy/windows/stop-existing.ps1 +72 -0
- package/deploy/windows/trillboards-edge-task.vbs +25 -0
- package/deploy/windows/trillboards-edge.cmd +33 -0
- package/deploy/windows/trillboards-pair.cmd +276 -0
- package/deploy/windows/trillboards-setup.ps1 +456 -0
- package/deploy/windows/trillboards-setup.vbs +13 -0
- package/dist/AudienceAnalyzeClient.d.ts +136 -0
- package/dist/AudienceAnalyzeClient.d.ts.map +1 -0
- package/dist/AudienceAnalyzeClient.js +246 -0
- package/dist/AudienceAnalyzeClient.js.map +1 -0
- package/dist/ChannelProber.d.ts +150 -0
- package/dist/ChannelProber.d.ts.map +1 -0
- package/dist/ChannelProber.js +887 -0
- package/dist/ChannelProber.js.map +1 -0
- package/dist/CloudEscalationPolicy.d.ts +143 -0
- package/dist/CloudEscalationPolicy.d.ts.map +1 -0
- package/dist/CloudEscalationPolicy.js +137 -0
- package/dist/CloudEscalationPolicy.js.map +1 -0
- package/dist/CloudSensingManager.d.ts +557 -0
- package/dist/CloudSensingManager.d.ts.map +1 -0
- package/dist/CloudSensingManager.js +1267 -0
- package/dist/CloudSensingManager.js.map +1 -0
- package/dist/DiscoveryReportClient.d.ts +66 -0
- package/dist/DiscoveryReportClient.d.ts.map +1 -0
- package/dist/DiscoveryReportClient.js +85 -0
- package/dist/DiscoveryReportClient.js.map +1 -0
- package/dist/EdgeAgent.d.ts +12 -2
- package/dist/EdgeAgent.d.ts.map +1 -1
- package/dist/EdgeAgent.js +55 -13
- package/dist/EdgeAgent.js.map +1 -1
- package/dist/LiteRtVlmProvider.d.ts +193 -0
- package/dist/LiteRtVlmProvider.d.ts.map +1 -0
- package/dist/LiteRtVlmProvider.js +436 -0
- package/dist/LiteRtVlmProvider.js.map +1 -0
- package/dist/MosaicSource.d.ts +108 -0
- package/dist/MosaicSource.d.ts.map +1 -0
- package/dist/MosaicSource.js +274 -0
- package/dist/MosaicSource.js.map +1 -0
- package/dist/OnvifClient.d.ts +83 -0
- package/dist/OnvifClient.d.ts.map +1 -0
- package/dist/OnvifClient.js +209 -0
- package/dist/OnvifClient.js.map +1 -0
- package/dist/PairClient.d.ts +106 -0
- package/dist/PairClient.d.ts.map +1 -0
- package/dist/PairClient.js +186 -0
- package/dist/PairClient.js.map +1 -0
- package/dist/RecorderAddressRecovery.d.ts +53 -0
- package/dist/RecorderAddressRecovery.d.ts.map +1 -0
- package/dist/RecorderAddressRecovery.js +359 -0
- package/dist/RecorderAddressRecovery.js.map +1 -0
- package/dist/SceneChangeDetector.d.ts +291 -0
- package/dist/SceneChangeDetector.d.ts.map +1 -0
- package/dist/SceneChangeDetector.js +682 -0
- package/dist/SceneChangeDetector.js.map +1 -0
- package/dist/SensingProfileClient.d.ts +59 -0
- package/dist/SensingProfileClient.d.ts.map +1 -0
- package/dist/SensingProfileClient.js +78 -0
- package/dist/SensingProfileClient.js.map +1 -0
- package/dist/SetupSessionClient.d.ts +114 -0
- package/dist/SetupSessionClient.d.ts.map +1 -0
- package/dist/SetupSessionClient.js +95 -0
- package/dist/SetupSessionClient.js.map +1 -0
- package/dist/VlmAdmissionGate.d.ts +126 -0
- package/dist/VlmAdmissionGate.d.ts.map +1 -0
- package/dist/VlmAdmissionGate.js +166 -0
- package/dist/VlmAdmissionGate.js.map +1 -0
- package/dist/autoSense.d.ts +266 -0
- package/dist/autoSense.d.ts.map +1 -0
- package/dist/autoSense.js +606 -0
- package/dist/autoSense.js.map +1 -0
- package/dist/cli.d.ts +27 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +526 -27
- package/dist/cli.js.map +1 -1
- package/dist/combinedSense.d.ts +128 -0
- package/dist/combinedSense.d.ts.map +1 -0
- package/dist/combinedSense.js +261 -0
- package/dist/combinedSense.js.map +1 -0
- package/dist/config.d.ts +351 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +99 -2
- package/dist/config.js.map +1 -1
- package/dist/connectorCadence.d.ts +135 -0
- package/dist/connectorCadence.d.ts.map +1 -0
- package/dist/connectorCadence.js +151 -0
- package/dist/connectorCadence.js.map +1 -0
- package/dist/dataDir.d.ts +33 -0
- package/dist/dataDir.d.ts.map +1 -0
- package/dist/dataDir.js +76 -0
- package/dist/dataDir.js.map +1 -0
- package/dist/deployedCadence.d.ts +167 -0
- package/dist/deployedCadence.d.ts.map +1 -0
- package/dist/deployedCadence.js +209 -0
- package/dist/deployedCadence.js.map +1 -0
- package/dist/deployedCadenceSource.d.ts +48 -0
- package/dist/deployedCadenceSource.d.ts.map +1 -0
- package/dist/deployedCadenceSource.js +74 -0
- package/dist/deployedCadenceSource.js.map +1 -0
- package/dist/diagnose.d.ts +1 -1
- package/dist/diagnose.d.ts.map +1 -1
- package/dist/diagnose.js +15 -13
- package/dist/diagnose.js.map +1 -1
- package/dist/discovery.d.ts +131 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +499 -0
- package/dist/discovery.js.map +1 -0
- package/dist/gemmaModelOta.d.ts +118 -0
- package/dist/gemmaModelOta.d.ts.map +1 -0
- package/dist/gemmaModelOta.js +291 -0
- package/dist/gemmaModelOta.js.map +1 -0
- package/dist/index.d.ts +16 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -1
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +25 -87
- package/dist/init.js.map +1 -1
- package/dist/keepAwake.d.ts +33 -0
- package/dist/keepAwake.d.ts.map +1 -0
- package/dist/keepAwake.js +54 -0
- package/dist/keepAwake.js.map +1 -0
- package/dist/legacyRecorderProfile.d.ts +20 -0
- package/dist/legacyRecorderProfile.d.ts.map +1 -0
- package/dist/legacyRecorderProfile.js +27 -0
- package/dist/legacyRecorderProfile.js.map +1 -0
- package/dist/localInferenceSchema.d.ts +132 -0
- package/dist/localInferenceSchema.d.ts.map +1 -0
- package/dist/localInferenceSchema.js +274 -0
- package/dist/localInferenceSchema.js.map +1 -0
- package/dist/localSenseE2eHarness.d.ts +101 -0
- package/dist/localSenseE2eHarness.d.ts.map +1 -0
- package/dist/localSenseE2eHarness.js +279 -0
- package/dist/localSenseE2eHarness.js.map +1 -0
- package/dist/localSensing.d.ts +284 -0
- package/dist/localSensing.d.ts.map +1 -0
- package/dist/localSensing.js +512 -0
- package/dist/localSensing.js.map +1 -0
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +3 -1
- package/dist/mcp-server.js.map +1 -1
- package/dist/modelIntegrity.d.ts +80 -0
- package/dist/modelIntegrity.d.ts.map +1 -0
- package/dist/modelIntegrity.js +194 -0
- package/dist/modelIntegrity.js.map +1 -0
- package/dist/pairVerify.d.ts +100 -0
- package/dist/pairVerify.d.ts.map +1 -0
- package/dist/pairVerify.js +296 -0
- package/dist/pairVerify.js.map +1 -0
- package/dist/qrScanner.d.ts +33 -0
- package/dist/qrScanner.d.ts.map +1 -0
- package/dist/qrScanner.js +103 -0
- package/dist/qrScanner.js.map +1 -0
- package/dist/runtimePaths.d.ts +10 -0
- package/dist/runtimePaths.d.ts.map +1 -0
- package/dist/runtimePaths.js +79 -0
- package/dist/runtimePaths.js.map +1 -0
- package/dist/sensingRunMode.d.ts +86 -0
- package/dist/sensingRunMode.d.ts.map +1 -0
- package/dist/sensingRunMode.js +346 -0
- package/dist/sensingRunMode.js.map +1 -0
- package/dist/setupMode.d.ts +98 -0
- package/dist/setupMode.d.ts.map +1 -0
- package/dist/setupMode.js +828 -0
- package/dist/setupMode.js.map +1 -0
- package/dist/setupProgress.d.ts +31 -0
- package/dist/setupProgress.d.ts.map +1 -0
- package/dist/setupProgress.js +173 -0
- package/dist/setupProgress.js.map +1 -0
- 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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"}
|