@camstack/addon-pipeline 1.2.229 → 1.2.231
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/dist/stream-broker/index.js +147 -6
- package/dist/stream-broker/index.mjs +147 -6
- package/package.json +1 -1
- package/python/__pycache__/inference_pool.cpython-312.pyc +0 -0
- package/python/__pycache__/inference_pool.cpython-313.pyc +0 -0
- package/python/__pycache__/inference_pool.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_backpressure.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/__pycache__/test_inference_pool_backpressure.cpython-312.pyc +0 -0
- package/python/__pycache__/test_inference_pool_backpressure.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_coreml_cache.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/__pycache__/test_inference_pool_coreml_cache.cpython-312.pyc +0 -0
- package/python/__pycache__/test_inference_pool_coreml_cache.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_deadline.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_device_selection.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/__pycache__/test_inference_pool_device_selection.cpython-312.pyc +0 -0
- package/python/__pycache__/test_inference_pool_device_selection.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_layout.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/__pycache__/test_inference_pool_layout.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_memstats.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/__pycache__/test_inference_pool_memstats.cpython-312.pyc +0 -0
- package/python/__pycache__/test_inference_pool_memstats.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_ov_ppp.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/__pycache__/test_inference_pool_ov_ppp.cpython-312.pyc +0 -0
- package/python/__pycache__/test_inference_pool_ov_ppp.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_ov_requests.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_preprocess.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/__pycache__/test_inference_pool_preprocess.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_reconfigure.cpython-314.pyc +0 -0
- package/python/__pycache__/test_inference_pool_static_batch.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/__pycache__/test_inference_pool_static_batch.cpython-312.pyc +0 -0
- package/python/__pycache__/test_inference_pool_static_batch.cpython-314.pyc +0 -0
- package/python/__pycache__/yamnet_audio.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/__init__.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/__init__.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/__init__.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/_safety.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/arcface.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/arcface.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/arcface.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/clip.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/clip.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/clip.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/ctc.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/ctc.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/ctc.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/plate_slots.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/plate_slots.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/rfdetr.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/rfdetr.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/saliency.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/saliency.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/saliency.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/scrfd.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/scrfd.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/scrfd.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/softmax.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/softmax.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/softmax.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/ssd.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/ssd.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/ssd.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/test_clip.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_clip.cpython-313-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_ctc.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_ctc.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/test_ctc.cpython-313-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_ctc.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/test_ctc.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/test_plate_slots.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_rfdetr.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_rfdetr.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/test_saliency.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_saliency.cpython-313-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_saliency.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/test_softmax.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_softmax.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/test_yolo_nms.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/test_yolo_nms.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/test_yolo_nms.cpython-39.pyc +0 -0
- package/python/postprocessors/__pycache__/test_yolonas.cpython-312-pytest-9.1.1.pyc +0 -0
- package/python/postprocessors/__pycache__/yamnet.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yamnet.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yamnet.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo.cpython-39.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo_seg.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo_seg.cpython-313.pyc +0 -0
- package/python/postprocessors/__pycache__/yolo_seg.cpython-314.pyc +0 -0
- package/python/postprocessors/__pycache__/yolonas.cpython-312.pyc +0 -0
- package/python/postprocessors/__pycache__/yolonas.cpython-314.pyc +0 -0
|
@@ -670,6 +670,45 @@ function prestreamEligible(input) {
|
|
|
670
670
|
};
|
|
671
671
|
return { eligible: true };
|
|
672
672
|
}
|
|
673
|
+
//#endregion
|
|
674
|
+
//#region src/stream-broker/stream-broker/prestream-signal-selection.ts
|
|
675
|
+
function nextEnabledSignals(input) {
|
|
676
|
+
const base = input.stored ?? input.declared;
|
|
677
|
+
const set = new Set(base);
|
|
678
|
+
if (input.enabled) set.add(input.code);
|
|
679
|
+
else set.delete(input.code);
|
|
680
|
+
const known = input.declared.filter((c) => set.has(c));
|
|
681
|
+
const extra = [...set].filter((c) => !input.declared.includes(c));
|
|
682
|
+
return [...known, ...extra];
|
|
683
|
+
}
|
|
684
|
+
//#endregion
|
|
685
|
+
//#region src/stream-broker/stream-broker/wake-hold.ts
|
|
686
|
+
/**
|
|
687
|
+
* The HOLD a camera's own signal buys, and what it may not buy.
|
|
688
|
+
*
|
|
689
|
+
* A stream a consumer merely watches is not a reason to wake a battery camera —
|
|
690
|
+
* a Frigate-style puller holds a session open for ever, and treating that as
|
|
691
|
+
* demand keeps the camera awake for ever, which is the whole point of the
|
|
692
|
+
* battery (D173). So a prestream consumer is FED without being INTENT, and the
|
|
693
|
+
* only thing that dials is a signal the camera reported about itself.
|
|
694
|
+
*
|
|
695
|
+
* This module is the timing half of that, and it is pure so the arithmetic can
|
|
696
|
+
* be argued with. Three rules:
|
|
697
|
+
*
|
|
698
|
+
* - **A hold EXTENDS, it never stacks.** Continuous motion keeps a camera up
|
|
699
|
+
* and it sleeps `holdMs` after the LAST signal, not after the sum of them.
|
|
700
|
+
* A counter or a queue would turn a busy afternoon into an hour of wake.
|
|
701
|
+
* - **A run is bounded ABSOLUTELY, from its first signal.** A stuck PIR
|
|
702
|
+
* reporting every second would otherwise extend the hold indefinitely and
|
|
703
|
+
* drain the battery — the one failure this whole design exists to prevent.
|
|
704
|
+
* The same re-arm-under-a-ceiling shape D371 gives `recording-signal`.
|
|
705
|
+
* - **A deadline, not a chain of timers.** The state is one number; the timer
|
|
706
|
+
* only decides when to LOOK at it. A chain would drift and would have to be
|
|
707
|
+
* cancelled correctly on every path.
|
|
708
|
+
*/
|
|
709
|
+
/** How long a camera stays awake after the LAST signal. The operator's, per
|
|
710
|
+
* camera; this is the default the broker ships. */
|
|
711
|
+
var DEFAULT_WAKE_HOLD_MS = 15e3;
|
|
673
712
|
/**
|
|
674
713
|
* Ceiling on ONE run, measured from its first signal.
|
|
675
714
|
*
|
|
@@ -19307,6 +19346,20 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
19307
19346
|
* nothing, and `[]` when it does not declare the cap at all — both mean no
|
|
19308
19347
|
* signal of that name is acceptable, which is the same refusal.
|
|
19309
19348
|
*/
|
|
19349
|
+
/**
|
|
19350
|
+
* The signals a camera declares, with their labels — what the form renders.
|
|
19351
|
+
* `[]` on any failure, which the caller reads as "not eligible": a form built
|
|
19352
|
+
* from a list we could not read would offer wakes nobody confirmed.
|
|
19353
|
+
*/
|
|
19354
|
+
async declaredSignalList(deviceId) {
|
|
19355
|
+
const read = this.deviceSignalDetailReader;
|
|
19356
|
+
if (read === null) return [];
|
|
19357
|
+
try {
|
|
19358
|
+
return await read(deviceId);
|
|
19359
|
+
} catch {
|
|
19360
|
+
return [];
|
|
19361
|
+
}
|
|
19362
|
+
}
|
|
19310
19363
|
async declaredSignalCodes(deviceId) {
|
|
19311
19364
|
const read = this.deviceSignalReader;
|
|
19312
19365
|
if (read === null) return [];
|
|
@@ -19324,6 +19377,12 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
19324
19377
|
setDeviceSignalReader(read) {
|
|
19325
19378
|
this.deviceSignalReader = read;
|
|
19326
19379
|
}
|
|
19380
|
+
/** The same read, with labels — the form needs them, the wake decision does
|
|
19381
|
+
* not. Injected by the host addon for the same reason. */
|
|
19382
|
+
deviceSignalDetailReader = null;
|
|
19383
|
+
setDeviceSignalDetailReader(read) {
|
|
19384
|
+
this.deviceSignalDetailReader = read;
|
|
19385
|
+
}
|
|
19327
19386
|
/**
|
|
19328
19387
|
* Whether this device runs on a battery.
|
|
19329
19388
|
*
|
|
@@ -19445,6 +19504,12 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
19445
19504
|
value: setting,
|
|
19446
19505
|
label: hwAccelOptionLabel(setting)
|
|
19447
19506
|
}));
|
|
19507
|
+
const declaredSignals = await this.declaredSignalList(deviceId);
|
|
19508
|
+
const prestreamGate = prestreamEligible({
|
|
19509
|
+
batteryOperated: await this.isBatteryOperated(deviceId),
|
|
19510
|
+
declaresSignals: declaredSignals.length > 0
|
|
19511
|
+
});
|
|
19512
|
+
const prestreamOffered = prestreamGate.eligible;
|
|
19448
19513
|
const streamTabs = streams.filter((s) => !s.camStreamId.startsWith("derived:")).map((stream) => {
|
|
19449
19514
|
const camStreamId = stream.camStreamId;
|
|
19450
19515
|
const brokerId = brokerIdFor(deviceId, camStreamId);
|
|
@@ -19544,6 +19609,20 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
19544
19609
|
tooltip: "Copy muted URL to clipboard"
|
|
19545
19610
|
}]
|
|
19546
19611
|
},
|
|
19612
|
+
...prestreamOffered ? [{
|
|
19613
|
+
type: "text",
|
|
19614
|
+
key: `rtspPrestreamUrl:${camStreamId}`,
|
|
19615
|
+
label: "RTSP URL (prestream)",
|
|
19616
|
+
description: "Video-only. Shows this camera’s last picture while it sleeps and cuts to live video when one of its wake signals fires — without a consumer on this URL ever keeping the camera awake.",
|
|
19617
|
+
readonlyField: true,
|
|
19618
|
+
span: 2,
|
|
19619
|
+
value: rtspEntry?.url ? `${rtspEntry.url}/prestream` : "inactive",
|
|
19620
|
+
actions: [{
|
|
19621
|
+
action: "copy-value",
|
|
19622
|
+
icon: "copy",
|
|
19623
|
+
tooltip: "Copy prestream URL to clipboard"
|
|
19624
|
+
}]
|
|
19625
|
+
}] : [],
|
|
19547
19626
|
{
|
|
19548
19627
|
type: "boolean",
|
|
19549
19628
|
style: "switch",
|
|
@@ -19711,7 +19790,7 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
19711
19790
|
}
|
|
19712
19791
|
]
|
|
19713
19792
|
}));
|
|
19714
|
-
|
|
19793
|
+
const streamBrokerSection = {
|
|
19715
19794
|
id: "stream-broker-streams",
|
|
19716
19795
|
title: "",
|
|
19717
19796
|
tab: "stream-broker",
|
|
@@ -19728,7 +19807,37 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
19728
19807
|
...derivedTabs
|
|
19729
19808
|
]
|
|
19730
19809
|
}]
|
|
19731
|
-
}
|
|
19810
|
+
};
|
|
19811
|
+
const prestreamCfg = override?.prestream;
|
|
19812
|
+
const prestreamSections = prestreamGate.eligible ? [{
|
|
19813
|
+
id: "stream-broker-prestream",
|
|
19814
|
+
title: "Wake signals",
|
|
19815
|
+
tab: "stream-broker",
|
|
19816
|
+
location: "top-tab",
|
|
19817
|
+
order: 50,
|
|
19818
|
+
description: "A consumer can dial this camera’s prestream URL (the profile URL with /prestream) and be shown the last picture while the camera sleeps. It is fed without keeping the camera awake: only the signals below wake it, and it goes back to sleep once they stop.",
|
|
19819
|
+
fields: [...declaredSignals.map((signal) => ({
|
|
19820
|
+
type: "boolean",
|
|
19821
|
+
style: "switch",
|
|
19822
|
+
key: `prestreamSignal:${signal.code}`,
|
|
19823
|
+
label: signal.label,
|
|
19824
|
+
description: signal.recommended ? "Recommended by this camera’s driver." : "Offered by this camera’s driver.",
|
|
19825
|
+
default: signal.recommended,
|
|
19826
|
+
value: prestreamCfg?.enabledSignals === void 0 ? true : prestreamCfg.enabledSignals.includes(signal.code)
|
|
19827
|
+
})), {
|
|
19828
|
+
type: "number",
|
|
19829
|
+
key: "prestreamHoldMs",
|
|
19830
|
+
label: "Stay awake after the last signal",
|
|
19831
|
+
description: "How long the camera keeps streaming once its signals stop. Extended by each new signal, never stacked — continuous motion keeps it up and it sleeps this long after the last one. One run is capped at five minutes whatever this says, and a camera whose sensor never stops is left alone for ten minutes after that.",
|
|
19832
|
+
min: 1e3,
|
|
19833
|
+
max: 3e5,
|
|
19834
|
+
step: 1e3,
|
|
19835
|
+
unit: "ms",
|
|
19836
|
+
default: DEFAULT_WAKE_HOLD_MS,
|
|
19837
|
+
value: prestreamCfg?.holdMs ?? 15e3
|
|
19838
|
+
}]
|
|
19839
|
+
}] : [];
|
|
19840
|
+
const diagnosticsSection = {
|
|
19732
19841
|
id: "stream-broker-diagnostics",
|
|
19733
19842
|
title: "Diagnostics",
|
|
19734
19843
|
tab: "stream-broker",
|
|
@@ -19751,7 +19860,12 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
19751
19860
|
default: false,
|
|
19752
19861
|
value: override?.webrtcDebug === true
|
|
19753
19862
|
}]
|
|
19754
|
-
}
|
|
19863
|
+
};
|
|
19864
|
+
return { sections: [
|
|
19865
|
+
streamBrokerSection,
|
|
19866
|
+
...prestreamSections,
|
|
19867
|
+
diagnosticsSection
|
|
19868
|
+
] };
|
|
19755
19869
|
}
|
|
19756
19870
|
async getDeviceLiveContribution(_input) {
|
|
19757
19871
|
return null;
|
|
@@ -19863,6 +19977,31 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
19863
19977
|
nextOverride.sourceParserArgs = spa;
|
|
19864
19978
|
continue;
|
|
19865
19979
|
}
|
|
19980
|
+
if (fieldKey.startsWith("prestreamSignal:")) {
|
|
19981
|
+
overrideDirty = true;
|
|
19982
|
+
const next = nextEnabledSignals({
|
|
19983
|
+
declared: await this.declaredSignalCodes(deviceId),
|
|
19984
|
+
stored: nextOverride.prestream?.enabledSignals,
|
|
19985
|
+
code: fieldKey.slice(16),
|
|
19986
|
+
enabled: value === null || value === void 0 ? true : Boolean(value)
|
|
19987
|
+
});
|
|
19988
|
+
nextOverride.prestream = {
|
|
19989
|
+
...nextOverride.prestream,
|
|
19990
|
+
enabledSignals: [...next]
|
|
19991
|
+
};
|
|
19992
|
+
continue;
|
|
19993
|
+
}
|
|
19994
|
+
if (fieldKey === "prestreamHoldMs") {
|
|
19995
|
+
overrideDirty = true;
|
|
19996
|
+
const raw = typeof value === "number" ? value : Number(value);
|
|
19997
|
+
if (!Number.isFinite(raw)) continue;
|
|
19998
|
+
const clamped = Math.min(3e5, Math.max(1e3, Math.round(raw)));
|
|
19999
|
+
nextOverride.prestream = {
|
|
20000
|
+
...nextOverride.prestream,
|
|
20001
|
+
holdMs: clamped
|
|
20002
|
+
};
|
|
20003
|
+
continue;
|
|
20004
|
+
}
|
|
19866
20005
|
if (fieldKey === "streamingDebug") {
|
|
19867
20006
|
overrideDirty = true;
|
|
19868
20007
|
const next = value === null || value === void 0 ? void 0 : Boolean(value);
|
|
@@ -31266,9 +31405,11 @@ var StreamBrokerAddon = class extends require_dist.BaseAddon {
|
|
|
31266
31405
|
const localNode = this.ctx.kernel.localNodeId;
|
|
31267
31406
|
const localNodeId = localNode ? localNode.includes("/") ? localNode.split("/")[0] : localNode : void 0;
|
|
31268
31407
|
this.brokerManager.setApiAccess(this.ctx.api, localNodeId, this.ctx.kernel.hwaccel);
|
|
31269
|
-
|
|
31270
|
-
return
|
|
31271
|
-
}
|
|
31408
|
+
const readSignals = async (deviceId) => {
|
|
31409
|
+
return await (await this.ctx.fetchDevice(deviceId)).streamSignals?.listSignals({}) ?? [];
|
|
31410
|
+
};
|
|
31411
|
+
this.brokerManager.setDeviceSignalReader(async (deviceId) => (await readSignals(deviceId)).map((s) => s.code));
|
|
31412
|
+
this.brokerManager.setDeviceSignalDetailReader(readSignals);
|
|
31272
31413
|
await this.loadFfmpegConfig();
|
|
31273
31414
|
await this.declareRowCollections();
|
|
31274
31415
|
await this.purgeFlattenedSettingsKeys();
|
|
@@ -666,6 +666,45 @@ function prestreamEligible(input) {
|
|
|
666
666
|
};
|
|
667
667
|
return { eligible: true };
|
|
668
668
|
}
|
|
669
|
+
//#endregion
|
|
670
|
+
//#region src/stream-broker/stream-broker/prestream-signal-selection.ts
|
|
671
|
+
function nextEnabledSignals(input) {
|
|
672
|
+
const base = input.stored ?? input.declared;
|
|
673
|
+
const set = new Set(base);
|
|
674
|
+
if (input.enabled) set.add(input.code);
|
|
675
|
+
else set.delete(input.code);
|
|
676
|
+
const known = input.declared.filter((c) => set.has(c));
|
|
677
|
+
const extra = [...set].filter((c) => !input.declared.includes(c));
|
|
678
|
+
return [...known, ...extra];
|
|
679
|
+
}
|
|
680
|
+
//#endregion
|
|
681
|
+
//#region src/stream-broker/stream-broker/wake-hold.ts
|
|
682
|
+
/**
|
|
683
|
+
* The HOLD a camera's own signal buys, and what it may not buy.
|
|
684
|
+
*
|
|
685
|
+
* A stream a consumer merely watches is not a reason to wake a battery camera —
|
|
686
|
+
* a Frigate-style puller holds a session open for ever, and treating that as
|
|
687
|
+
* demand keeps the camera awake for ever, which is the whole point of the
|
|
688
|
+
* battery (D173). So a prestream consumer is FED without being INTENT, and the
|
|
689
|
+
* only thing that dials is a signal the camera reported about itself.
|
|
690
|
+
*
|
|
691
|
+
* This module is the timing half of that, and it is pure so the arithmetic can
|
|
692
|
+
* be argued with. Three rules:
|
|
693
|
+
*
|
|
694
|
+
* - **A hold EXTENDS, it never stacks.** Continuous motion keeps a camera up
|
|
695
|
+
* and it sleeps `holdMs` after the LAST signal, not after the sum of them.
|
|
696
|
+
* A counter or a queue would turn a busy afternoon into an hour of wake.
|
|
697
|
+
* - **A run is bounded ABSOLUTELY, from its first signal.** A stuck PIR
|
|
698
|
+
* reporting every second would otherwise extend the hold indefinitely and
|
|
699
|
+
* drain the battery — the one failure this whole design exists to prevent.
|
|
700
|
+
* The same re-arm-under-a-ceiling shape D371 gives `recording-signal`.
|
|
701
|
+
* - **A deadline, not a chain of timers.** The state is one number; the timer
|
|
702
|
+
* only decides when to LOOK at it. A chain would drift and would have to be
|
|
703
|
+
* cancelled correctly on every path.
|
|
704
|
+
*/
|
|
705
|
+
/** How long a camera stays awake after the LAST signal. The operator's, per
|
|
706
|
+
* camera; this is the default the broker ships. */
|
|
707
|
+
var DEFAULT_WAKE_HOLD_MS = 15e3;
|
|
669
708
|
/**
|
|
670
709
|
* Ceiling on ONE run, measured from its first signal.
|
|
671
710
|
*
|
|
@@ -19303,6 +19342,20 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
19303
19342
|
* nothing, and `[]` when it does not declare the cap at all — both mean no
|
|
19304
19343
|
* signal of that name is acceptable, which is the same refusal.
|
|
19305
19344
|
*/
|
|
19345
|
+
/**
|
|
19346
|
+
* The signals a camera declares, with their labels — what the form renders.
|
|
19347
|
+
* `[]` on any failure, which the caller reads as "not eligible": a form built
|
|
19348
|
+
* from a list we could not read would offer wakes nobody confirmed.
|
|
19349
|
+
*/
|
|
19350
|
+
async declaredSignalList(deviceId) {
|
|
19351
|
+
const read = this.deviceSignalDetailReader;
|
|
19352
|
+
if (read === null) return [];
|
|
19353
|
+
try {
|
|
19354
|
+
return await read(deviceId);
|
|
19355
|
+
} catch {
|
|
19356
|
+
return [];
|
|
19357
|
+
}
|
|
19358
|
+
}
|
|
19306
19359
|
async declaredSignalCodes(deviceId) {
|
|
19307
19360
|
const read = this.deviceSignalReader;
|
|
19308
19361
|
if (read === null) return [];
|
|
@@ -19320,6 +19373,12 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
19320
19373
|
setDeviceSignalReader(read) {
|
|
19321
19374
|
this.deviceSignalReader = read;
|
|
19322
19375
|
}
|
|
19376
|
+
/** The same read, with labels — the form needs them, the wake decision does
|
|
19377
|
+
* not. Injected by the host addon for the same reason. */
|
|
19378
|
+
deviceSignalDetailReader = null;
|
|
19379
|
+
setDeviceSignalDetailReader(read) {
|
|
19380
|
+
this.deviceSignalDetailReader = read;
|
|
19381
|
+
}
|
|
19323
19382
|
/**
|
|
19324
19383
|
* Whether this device runs on a battery.
|
|
19325
19384
|
*
|
|
@@ -19441,6 +19500,12 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
19441
19500
|
value: setting,
|
|
19442
19501
|
label: hwAccelOptionLabel(setting)
|
|
19443
19502
|
}));
|
|
19503
|
+
const declaredSignals = await this.declaredSignalList(deviceId);
|
|
19504
|
+
const prestreamGate = prestreamEligible({
|
|
19505
|
+
batteryOperated: await this.isBatteryOperated(deviceId),
|
|
19506
|
+
declaresSignals: declaredSignals.length > 0
|
|
19507
|
+
});
|
|
19508
|
+
const prestreamOffered = prestreamGate.eligible;
|
|
19444
19509
|
const streamTabs = streams.filter((s) => !s.camStreamId.startsWith("derived:")).map((stream) => {
|
|
19445
19510
|
const camStreamId = stream.camStreamId;
|
|
19446
19511
|
const brokerId = brokerIdFor(deviceId, camStreamId);
|
|
@@ -19540,6 +19605,20 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
19540
19605
|
tooltip: "Copy muted URL to clipboard"
|
|
19541
19606
|
}]
|
|
19542
19607
|
},
|
|
19608
|
+
...prestreamOffered ? [{
|
|
19609
|
+
type: "text",
|
|
19610
|
+
key: `rtspPrestreamUrl:${camStreamId}`,
|
|
19611
|
+
label: "RTSP URL (prestream)",
|
|
19612
|
+
description: "Video-only. Shows this camera’s last picture while it sleeps and cuts to live video when one of its wake signals fires — without a consumer on this URL ever keeping the camera awake.",
|
|
19613
|
+
readonlyField: true,
|
|
19614
|
+
span: 2,
|
|
19615
|
+
value: rtspEntry?.url ? `${rtspEntry.url}/prestream` : "inactive",
|
|
19616
|
+
actions: [{
|
|
19617
|
+
action: "copy-value",
|
|
19618
|
+
icon: "copy",
|
|
19619
|
+
tooltip: "Copy prestream URL to clipboard"
|
|
19620
|
+
}]
|
|
19621
|
+
}] : [],
|
|
19543
19622
|
{
|
|
19544
19623
|
type: "boolean",
|
|
19545
19624
|
style: "switch",
|
|
@@ -19707,7 +19786,7 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
19707
19786
|
}
|
|
19708
19787
|
]
|
|
19709
19788
|
}));
|
|
19710
|
-
|
|
19789
|
+
const streamBrokerSection = {
|
|
19711
19790
|
id: "stream-broker-streams",
|
|
19712
19791
|
title: "",
|
|
19713
19792
|
tab: "stream-broker",
|
|
@@ -19724,7 +19803,37 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
19724
19803
|
...derivedTabs
|
|
19725
19804
|
]
|
|
19726
19805
|
}]
|
|
19727
|
-
}
|
|
19806
|
+
};
|
|
19807
|
+
const prestreamCfg = override?.prestream;
|
|
19808
|
+
const prestreamSections = prestreamGate.eligible ? [{
|
|
19809
|
+
id: "stream-broker-prestream",
|
|
19810
|
+
title: "Wake signals",
|
|
19811
|
+
tab: "stream-broker",
|
|
19812
|
+
location: "top-tab",
|
|
19813
|
+
order: 50,
|
|
19814
|
+
description: "A consumer can dial this camera’s prestream URL (the profile URL with /prestream) and be shown the last picture while the camera sleeps. It is fed without keeping the camera awake: only the signals below wake it, and it goes back to sleep once they stop.",
|
|
19815
|
+
fields: [...declaredSignals.map((signal) => ({
|
|
19816
|
+
type: "boolean",
|
|
19817
|
+
style: "switch",
|
|
19818
|
+
key: `prestreamSignal:${signal.code}`,
|
|
19819
|
+
label: signal.label,
|
|
19820
|
+
description: signal.recommended ? "Recommended by this camera’s driver." : "Offered by this camera’s driver.",
|
|
19821
|
+
default: signal.recommended,
|
|
19822
|
+
value: prestreamCfg?.enabledSignals === void 0 ? true : prestreamCfg.enabledSignals.includes(signal.code)
|
|
19823
|
+
})), {
|
|
19824
|
+
type: "number",
|
|
19825
|
+
key: "prestreamHoldMs",
|
|
19826
|
+
label: "Stay awake after the last signal",
|
|
19827
|
+
description: "How long the camera keeps streaming once its signals stop. Extended by each new signal, never stacked — continuous motion keeps it up and it sleeps this long after the last one. One run is capped at five minutes whatever this says, and a camera whose sensor never stops is left alone for ten minutes after that.",
|
|
19828
|
+
min: 1e3,
|
|
19829
|
+
max: 3e5,
|
|
19830
|
+
step: 1e3,
|
|
19831
|
+
unit: "ms",
|
|
19832
|
+
default: DEFAULT_WAKE_HOLD_MS,
|
|
19833
|
+
value: prestreamCfg?.holdMs ?? 15e3
|
|
19834
|
+
}]
|
|
19835
|
+
}] : [];
|
|
19836
|
+
const diagnosticsSection = {
|
|
19728
19837
|
id: "stream-broker-diagnostics",
|
|
19729
19838
|
title: "Diagnostics",
|
|
19730
19839
|
tab: "stream-broker",
|
|
@@ -19747,7 +19856,12 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
19747
19856
|
default: false,
|
|
19748
19857
|
value: override?.webrtcDebug === true
|
|
19749
19858
|
}]
|
|
19750
|
-
}
|
|
19859
|
+
};
|
|
19860
|
+
return { sections: [
|
|
19861
|
+
streamBrokerSection,
|
|
19862
|
+
...prestreamSections,
|
|
19863
|
+
diagnosticsSection
|
|
19864
|
+
] };
|
|
19751
19865
|
}
|
|
19752
19866
|
async getDeviceLiveContribution(_input) {
|
|
19753
19867
|
return null;
|
|
@@ -19859,6 +19973,31 @@ var StreamBrokerManager = class StreamBrokerManager {
|
|
|
19859
19973
|
nextOverride.sourceParserArgs = spa;
|
|
19860
19974
|
continue;
|
|
19861
19975
|
}
|
|
19976
|
+
if (fieldKey.startsWith("prestreamSignal:")) {
|
|
19977
|
+
overrideDirty = true;
|
|
19978
|
+
const next = nextEnabledSignals({
|
|
19979
|
+
declared: await this.declaredSignalCodes(deviceId),
|
|
19980
|
+
stored: nextOverride.prestream?.enabledSignals,
|
|
19981
|
+
code: fieldKey.slice(16),
|
|
19982
|
+
enabled: value === null || value === void 0 ? true : Boolean(value)
|
|
19983
|
+
});
|
|
19984
|
+
nextOverride.prestream = {
|
|
19985
|
+
...nextOverride.prestream,
|
|
19986
|
+
enabledSignals: [...next]
|
|
19987
|
+
};
|
|
19988
|
+
continue;
|
|
19989
|
+
}
|
|
19990
|
+
if (fieldKey === "prestreamHoldMs") {
|
|
19991
|
+
overrideDirty = true;
|
|
19992
|
+
const raw = typeof value === "number" ? value : Number(value);
|
|
19993
|
+
if (!Number.isFinite(raw)) continue;
|
|
19994
|
+
const clamped = Math.min(3e5, Math.max(1e3, Math.round(raw)));
|
|
19995
|
+
nextOverride.prestream = {
|
|
19996
|
+
...nextOverride.prestream,
|
|
19997
|
+
holdMs: clamped
|
|
19998
|
+
};
|
|
19999
|
+
continue;
|
|
20000
|
+
}
|
|
19862
20001
|
if (fieldKey === "streamingDebug") {
|
|
19863
20002
|
overrideDirty = true;
|
|
19864
20003
|
const next = value === null || value === void 0 ? void 0 : Boolean(value);
|
|
@@ -31262,9 +31401,11 @@ var StreamBrokerAddon = class extends BaseAddon {
|
|
|
31262
31401
|
const localNode = this.ctx.kernel.localNodeId;
|
|
31263
31402
|
const localNodeId = localNode ? localNode.includes("/") ? localNode.split("/")[0] : localNode : void 0;
|
|
31264
31403
|
this.brokerManager.setApiAccess(this.ctx.api, localNodeId, this.ctx.kernel.hwaccel);
|
|
31265
|
-
|
|
31266
|
-
return
|
|
31267
|
-
}
|
|
31404
|
+
const readSignals = async (deviceId) => {
|
|
31405
|
+
return await (await this.ctx.fetchDevice(deviceId)).streamSignals?.listSignals({}) ?? [];
|
|
31406
|
+
};
|
|
31407
|
+
this.brokerManager.setDeviceSignalReader(async (deviceId) => (await readSignals(deviceId)).map((s) => s.code));
|
|
31408
|
+
this.brokerManager.setDeviceSignalDetailReader(readSignals);
|
|
31268
31409
|
await this.loadFfmpegConfig();
|
|
31269
31410
|
await this.declareRowCollections();
|
|
31270
31411
|
await this.purgeFlattenedSettingsKeys();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-pipeline",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.231",
|
|
4
4
|
"description": "Pipeline bundle — runner, detection, motion, audio + stream broker. Multi-entry npm package shipping pipeline addons under a single bundle.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/python/__pycache__/test_inference_pool_device_selection.cpython-312-pytest-9.1.1.pyc
DELETED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|