@camstack/addon-post-analysis 1.2.310 → 1.2.312
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/{clip-model-registry-xYrJkXdS.js → clip-model-registry-CRkjjUyj.js} +257 -18
- package/dist/{clip-model-registry-BROqj9pS.mjs → clip-model-registry-ivwvxxex.mjs} +210 -19
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +2 -2
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-KF5Fk8sD.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BocOElW2.mjs} +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Ds6vT2FJ.mjs +26 -0
- package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-B8qeS8Da.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-B_KS96RA.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-B3xcTHUM.mjs → hostInit-CRloCh1h.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +413 -31
- package/dist/pipeline-analytics/index.mjs +413 -31
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DytyGNZ-.mjs +0 -26
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_clip_model_registry = require("../clip-model-registry-
|
|
5
|
+
const require_clip_model_registry = require("../clip-model-registry-CRkjjUyj.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_clip_model_registry.__toESM(node_fs, 1);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -1396,11 +1396,85 @@ var NcAlarmExclusions = class {
|
|
|
1396
1396
|
}
|
|
1397
1397
|
};
|
|
1398
1398
|
//#endregion
|
|
1399
|
+
//#region src/notification-center/alarm/alarm-mode-delays.ts
|
|
1400
|
+
/**
|
|
1401
|
+
* The persisted per-mode delay overrides, read through a guard (D697).
|
|
1402
|
+
*
|
|
1403
|
+
* The panel's config schema keeps `mode` a plain string so a blob written by a later release
|
|
1404
|
+
* with a mode this one does not know cannot fail the whole config. This is where that string is
|
|
1405
|
+
* narrowed: an entry that cannot be trusted is dropped and REPORTED, never repaired. An entry of an unknown
|
|
1406
|
+
* mode is also handed back raw (`foreign`) so a write can keep it.
|
|
1407
|
+
*/
|
|
1408
|
+
function inBounds(value, max) {
|
|
1409
|
+
return Number.isInteger(value) && value >= 0 && value <= max;
|
|
1410
|
+
}
|
|
1411
|
+
/** Read the persisted overrides through a guard, never a cast (like `nonBlockingFromPersisted`). */
|
|
1412
|
+
function modeDelaysFromPersisted(raw) {
|
|
1413
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1414
|
+
const entries = [];
|
|
1415
|
+
const dropped = [];
|
|
1416
|
+
const foreign = [];
|
|
1417
|
+
for (const item of raw) {
|
|
1418
|
+
const mode = require_clip_model_registry.AlarmArmModeSchema.safeParse(item.mode);
|
|
1419
|
+
if (!mode.success) {
|
|
1420
|
+
dropped.push({
|
|
1421
|
+
mode: item.mode,
|
|
1422
|
+
why: "unknown-mode"
|
|
1423
|
+
});
|
|
1424
|
+
foreign.push(item);
|
|
1425
|
+
continue;
|
|
1426
|
+
}
|
|
1427
|
+
if (seen.has(mode.data)) {
|
|
1428
|
+
dropped.push({
|
|
1429
|
+
mode: item.mode,
|
|
1430
|
+
why: "duplicate-mode"
|
|
1431
|
+
});
|
|
1432
|
+
continue;
|
|
1433
|
+
}
|
|
1434
|
+
seen.add(mode.data);
|
|
1435
|
+
const kept = { mode: mode.data };
|
|
1436
|
+
const fields = [
|
|
1437
|
+
{
|
|
1438
|
+
field: "exitDelaySec",
|
|
1439
|
+
max: require_clip_model_registry.NC_ALARM_DELAY_MAX_SEC
|
|
1440
|
+
},
|
|
1441
|
+
{
|
|
1442
|
+
field: "entryDelaySec",
|
|
1443
|
+
max: require_clip_model_registry.NC_ALARM_DELAY_MAX_SEC
|
|
1444
|
+
},
|
|
1445
|
+
{
|
|
1446
|
+
field: "triggeredDurationSec",
|
|
1447
|
+
max: require_clip_model_registry.NC_ALARM_TRIGGERED_MAX_SEC
|
|
1448
|
+
}
|
|
1449
|
+
];
|
|
1450
|
+
for (const { field, max } of fields) {
|
|
1451
|
+
const value = item[field];
|
|
1452
|
+
if (value === void 0) continue;
|
|
1453
|
+
if (!inBounds(value, max)) {
|
|
1454
|
+
dropped.push({
|
|
1455
|
+
mode: item.mode,
|
|
1456
|
+
why: "out-of-bounds",
|
|
1457
|
+
field
|
|
1458
|
+
});
|
|
1459
|
+
continue;
|
|
1460
|
+
}
|
|
1461
|
+
kept[field] = value;
|
|
1462
|
+
}
|
|
1463
|
+
if (kept.exitDelaySec !== void 0 || kept.entryDelaySec !== void 0 || kept.triggeredDurationSec !== void 0) entries.push(kept);
|
|
1464
|
+
}
|
|
1465
|
+
return {
|
|
1466
|
+
entries,
|
|
1467
|
+
dropped,
|
|
1468
|
+
foreign
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
//#endregion
|
|
1399
1472
|
//#region src/notification-center/alarm/alarm-state-machine.ts
|
|
1400
1473
|
/**
|
|
1401
|
-
*
|
|
1402
|
-
*
|
|
1403
|
-
*
|
|
1474
|
+
* The GLOBAL defaults an operator can change per panel — what a mode inherits
|
|
1475
|
+
* when it overrides nothing. A mode can override each of the three since D697
|
|
1476
|
+
* (`resolveModeDelays`): `home` has no exit delay on purpose — nobody is
|
|
1477
|
+
* leaving — and says so with its own entry, not by this shared value.
|
|
1404
1478
|
*
|
|
1405
1479
|
* `triggeredDurationSec: 0` = "sound until somebody disarms it". That is what
|
|
1406
1480
|
* the panel did before the field existed, so an install that never opens the
|
|
@@ -1815,6 +1889,19 @@ var ncAlarmPanelSchema = require_clip_model_registry.object({
|
|
|
1815
1889
|
nonBlocking: require_clip_model_registry.array(require_clip_model_registry.object({
|
|
1816
1890
|
mode: require_clip_model_registry.string(),
|
|
1817
1891
|
deviceIds: require_clip_model_registry.array(require_clip_model_registry.number().int())
|
|
1892
|
+
})).default([]),
|
|
1893
|
+
/**
|
|
1894
|
+
* Per arm mode, the delays that override the global ones (D697). Loose on purpose — `mode` is
|
|
1895
|
+
* a string and the numbers are unbounded here — and read back through
|
|
1896
|
+
* {@link modeDelaysFromPersisted}, never a cast: an entry this release cannot trust is dropped
|
|
1897
|
+
* and logged at boot, never the whole config. A blob written before D697 has no key and
|
|
1898
|
+
* parses to no overrides.
|
|
1899
|
+
*/
|
|
1900
|
+
modeDelays: require_clip_model_registry.array(require_clip_model_registry.object({
|
|
1901
|
+
mode: require_clip_model_registry.string(),
|
|
1902
|
+
exitDelaySec: require_clip_model_registry.number().optional(),
|
|
1903
|
+
entryDelaySec: require_clip_model_registry.number().optional(),
|
|
1904
|
+
triggeredDurationSec: require_clip_model_registry.number().optional()
|
|
1818
1905
|
})).default([])
|
|
1819
1906
|
});
|
|
1820
1907
|
/**
|
|
@@ -1885,7 +1972,6 @@ function machineFromPersisted(raw) {
|
|
|
1885
1972
|
var NcAlarmPanelDevice = class extends require_clip_model_registry.BaseDevice {
|
|
1886
1973
|
features = [];
|
|
1887
1974
|
machine = DISARMED;
|
|
1888
|
-
delays = DEFAULT_DELAYS;
|
|
1889
1975
|
ticker = null;
|
|
1890
1976
|
onPublish = null;
|
|
1891
1977
|
/**
|
|
@@ -1916,7 +2002,7 @@ var NcAlarmPanelDevice = class extends require_clip_model_registry.BaseDevice {
|
|
|
1916
2002
|
refusalSeq = 0;
|
|
1917
2003
|
constructor(ctx) {
|
|
1918
2004
|
super(ctx, ncAlarmPanelSchema, { type: require_clip_model_registry.DeviceType.AlarmPanel });
|
|
1919
|
-
this.
|
|
2005
|
+
this.logDroppedModeDelays();
|
|
1920
2006
|
this.restore();
|
|
1921
2007
|
this.registerAlarmCap();
|
|
1922
2008
|
this.publish();
|
|
@@ -1928,30 +2014,148 @@ var NcAlarmPanelDevice = class extends require_clip_model_registry.BaseDevice {
|
|
|
1928
2014
|
...this.readDelays(),
|
|
1929
2015
|
announceArm: this.config.get("announceArm"),
|
|
1930
2016
|
announceTargets: [...this.config.get("announceTargets")],
|
|
1931
|
-
nonBlocking: nonBlockingFromPersisted(this.config.get("nonBlocking"))
|
|
2017
|
+
nonBlocking: nonBlockingFromPersisted(this.config.get("nonBlocking")),
|
|
2018
|
+
modeDelays: modeDelaysFromPersisted(this.config.get("modeDelays")).entries
|
|
1932
2019
|
};
|
|
1933
2020
|
}
|
|
1934
2021
|
/**
|
|
1935
2022
|
* Apply an editor's patch and return what the panel settled on.
|
|
1936
2023
|
*
|
|
1937
2024
|
* The new delays take effect on the NEXT transition, never retroactively: a
|
|
1938
|
-
* machine already counting down keeps the instants it was given
|
|
1939
|
-
*
|
|
1940
|
-
*
|
|
2025
|
+
* machine already counting down keeps the instants it was given (the exit
|
|
2026
|
+
* delay is read at arm, the entry delay and siren duration at trigger, and
|
|
2027
|
+
* the machine stores the instants, not the durations). Re-deriving `armedAt`
|
|
2028
|
+
* from a delay the operator changed mid-exit would move the arm instant under
|
|
2029
|
+
* somebody who is walking out of the door.
|
|
2030
|
+
*
|
|
2031
|
+
* Two phases, so a refused patch writes NOTHING:
|
|
2032
|
+
* 1. every refusal is checked first (a mode this panel does not offer, an
|
|
2033
|
+
* add to a full non-blocking list) — it logs why and throws;
|
|
2034
|
+
* 2. every LIST write is CALLED before the first `await`. `DeviceConfig.set`
|
|
2035
|
+
* replaces its in-memory value synchronously, so a list computed after an
|
|
2036
|
+
* await could be based on a list another call has since replaced — and a
|
|
2037
|
+
* whole-list write (the deleted-device prune sends one, computed
|
|
2038
|
+
* synchronously before calling: `device-prune-runner.ts` `applyAlarm`)
|
|
2039
|
+
* would erase the membership edit that landed in between.
|
|
1941
2040
|
*/
|
|
1942
2041
|
async applySettings(patch) {
|
|
2042
|
+
const edits = patch.modeDelays ?? [];
|
|
2043
|
+
const membership = patch.nonBlockingEdit;
|
|
2044
|
+
for (const edit of edits) this.refuseUnofferedMode(edit.mode);
|
|
2045
|
+
if (membership !== void 0) {
|
|
2046
|
+
this.refuseUnofferedMode(membership.mode, membership.deviceId);
|
|
2047
|
+
const verdict = require_clip_model_registry.applyNonBlockingEdit(patch.nonBlocking !== void 0 ? patch.nonBlocking : nonBlockingFromPersisted(this.config.get("nonBlocking")), membership);
|
|
2048
|
+
if (verdict.kind === "refused") {
|
|
2049
|
+
this.ctx.logger.info("alarm settings refused", {
|
|
2050
|
+
tags: { deviceId: membership.deviceId },
|
|
2051
|
+
meta: {
|
|
2052
|
+
panelId: this.id,
|
|
2053
|
+
reason: "list-full",
|
|
2054
|
+
mode: verdict.mode
|
|
2055
|
+
}
|
|
2056
|
+
});
|
|
2057
|
+
throw new Error(`the ${verdict.mode} non-blocking list already holds ${verdict.max} devices — remove one first`);
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
const listWrites = [];
|
|
2061
|
+
if (patch.nonBlocking !== void 0) listWrites.push(this.config.set("nonBlocking", patch.nonBlocking.map((l) => ({
|
|
2062
|
+
mode: l.mode,
|
|
2063
|
+
deviceIds: [...l.deviceIds]
|
|
2064
|
+
}))));
|
|
2065
|
+
let membershipChanged = null;
|
|
2066
|
+
if (membership !== void 0) {
|
|
2067
|
+
const result = require_clip_model_registry.applyNonBlockingEdit(nonBlockingFromPersisted(this.config.get("nonBlocking")), membership);
|
|
2068
|
+
if (result.kind === "applied") {
|
|
2069
|
+
membershipChanged = result.changed;
|
|
2070
|
+
if (result.changed) listWrites.push(this.config.set("nonBlocking", result.lists.map((l) => ({
|
|
2071
|
+
mode: l.mode,
|
|
2072
|
+
deviceIds: [...l.deviceIds]
|
|
2073
|
+
}))));
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
const persistedDelays = modeDelaysFromPersisted(this.config.get("modeDelays"));
|
|
2077
|
+
const beforeDelays = persistedDelays.entries;
|
|
2078
|
+
const afterDelays = edits.length > 0 ? require_clip_model_registry.applyModeDelaysEdits(beforeDelays, edits) : beforeDelays;
|
|
2079
|
+
if (edits.length > 0) {
|
|
2080
|
+
for (const drop of persistedDelays.dropped) {
|
|
2081
|
+
if (drop.why === "unknown-mode") continue;
|
|
2082
|
+
this.ctx.logger.info("alarm per-mode delay discarded on write", {
|
|
2083
|
+
tags: { deviceId: this.id },
|
|
2084
|
+
meta: {
|
|
2085
|
+
mode: drop.mode,
|
|
2086
|
+
why: drop.why,
|
|
2087
|
+
field: drop.field
|
|
2088
|
+
}
|
|
2089
|
+
});
|
|
2090
|
+
}
|
|
2091
|
+
listWrites.push(this.config.set("modeDelays", [...afterDelays, ...persistedDelays.foreign]));
|
|
2092
|
+
}
|
|
2093
|
+
await Promise.all(listWrites);
|
|
2094
|
+
this.logModeDelayChanges(beforeDelays, afterDelays, edits);
|
|
1943
2095
|
if (patch.exitDelaySec !== void 0) await this.config.set("exitDelaySec", patch.exitDelaySec);
|
|
1944
2096
|
if (patch.entryDelaySec !== void 0) await this.config.set("entryDelaySec", patch.entryDelaySec);
|
|
1945
2097
|
if (patch.triggeredDurationSec !== void 0) await this.config.set("triggeredDurationSec", patch.triggeredDurationSec);
|
|
1946
2098
|
if (patch.announceArm !== void 0) await this.config.set("announceArm", patch.announceArm);
|
|
1947
2099
|
if (patch.announceTargets !== void 0) await this.config.set("announceTargets", [...patch.announceTargets]);
|
|
1948
|
-
if (
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
2100
|
+
if (membership !== void 0 && membershipChanged !== null) this.ctx.logger.info("alarm non-blocking list changed", {
|
|
2101
|
+
tags: { deviceId: membership.deviceId },
|
|
2102
|
+
meta: {
|
|
2103
|
+
panelId: this.id,
|
|
2104
|
+
mode: membership.mode,
|
|
2105
|
+
nonBlocking: membership.nonBlocking,
|
|
2106
|
+
changed: membershipChanged
|
|
2107
|
+
}
|
|
2108
|
+
});
|
|
1953
2109
|
return this.settings();
|
|
1954
2110
|
}
|
|
2111
|
+
/**
|
|
2112
|
+
* Refuse a mode this panel does not offer — logged by reason, then thrown. A membership edit
|
|
2113
|
+
* passes its SENSOR as `deviceId` (the tag); a per-mode delay edit defaults to the panel.
|
|
2114
|
+
*/
|
|
2115
|
+
refuseUnofferedMode(mode, deviceId = this.id) {
|
|
2116
|
+
if (NC_ALARM_MODES.includes(mode)) return;
|
|
2117
|
+
this.ctx.logger.info("alarm settings refused", {
|
|
2118
|
+
tags: { deviceId },
|
|
2119
|
+
meta: {
|
|
2120
|
+
panelId: this.id,
|
|
2121
|
+
reason: "mode-not-offered",
|
|
2122
|
+
mode
|
|
2123
|
+
}
|
|
2124
|
+
});
|
|
2125
|
+
throw new Error(`alarm mode '${mode}' is not offered by this panel`);
|
|
2126
|
+
}
|
|
2127
|
+
/** One INFO line per mode-delay field an edit really changed (`null` = inherit). */
|
|
2128
|
+
logModeDelayChanges(before, after, edits) {
|
|
2129
|
+
for (const edit of edits) {
|
|
2130
|
+
const was = before.find((e) => e.mode === edit.mode);
|
|
2131
|
+
const now = after.find((e) => e.mode === edit.mode);
|
|
2132
|
+
for (const field of require_clip_model_registry.NC_ALARM_DELAY_FIELDS) {
|
|
2133
|
+
const from = was?.[field] ?? null;
|
|
2134
|
+
const to = now?.[field] ?? null;
|
|
2135
|
+
if (from === to) continue;
|
|
2136
|
+
this.ctx.logger.info("alarm mode delays changed", {
|
|
2137
|
+
tags: { deviceId: this.id },
|
|
2138
|
+
meta: {
|
|
2139
|
+
mode: edit.mode,
|
|
2140
|
+
field,
|
|
2141
|
+
from,
|
|
2142
|
+
to
|
|
2143
|
+
}
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
/** One INFO line per persisted per-mode delay entry the guard refused to trust. */
|
|
2149
|
+
logDroppedModeDelays() {
|
|
2150
|
+
for (const drop of modeDelaysFromPersisted(this.config.get("modeDelays")).dropped) this.ctx.logger.info("alarm per-mode delay dropped at boot", {
|
|
2151
|
+
tags: { deviceId: this.id },
|
|
2152
|
+
meta: {
|
|
2153
|
+
mode: drop.mode,
|
|
2154
|
+
why: drop.why,
|
|
2155
|
+
field: drop.field
|
|
2156
|
+
}
|
|
2157
|
+
});
|
|
2158
|
+
}
|
|
1955
2159
|
/** Wire the state mirror and the transition notifications. See
|
|
1956
2160
|
* {@link NcAlarmPublishHook}. */
|
|
1957
2161
|
setPublishHook(hook) {
|
|
@@ -2234,7 +2438,17 @@ var NcAlarmPanelDevice = class extends require_clip_model_registry.BaseDevice {
|
|
|
2234
2438
|
else this.exclusions.clear();
|
|
2235
2439
|
this.persist();
|
|
2236
2440
|
}
|
|
2237
|
-
|
|
2441
|
+
const resolved = this.delaysFor(mode);
|
|
2442
|
+
this.machine = arm(mode, resolved, now);
|
|
2443
|
+
this.ctx.logger.info("alarm delays applied", {
|
|
2444
|
+
tags: { deviceId: this.id },
|
|
2445
|
+
meta: {
|
|
2446
|
+
mode,
|
|
2447
|
+
phase: "arm",
|
|
2448
|
+
exitDelaySec: resolved.exitDelaySec,
|
|
2449
|
+
source: resolved.source.exitDelaySec
|
|
2450
|
+
}
|
|
2451
|
+
});
|
|
2238
2452
|
this.attribution.clear();
|
|
2239
2453
|
this.publish(now);
|
|
2240
2454
|
for (const { device, why } of exclusions ?? []) this.ctx.logger.info("alarm armed with an open device excluded", {
|
|
@@ -2334,7 +2548,9 @@ var NcAlarmPanelDevice = class extends require_clip_model_registry.BaseDevice {
|
|
|
2334
2548
|
this.announceIgnoredTrigger(source, now);
|
|
2335
2549
|
return;
|
|
2336
2550
|
}
|
|
2337
|
-
const
|
|
2551
|
+
const armed = armModeOf(this.machine.target, NC_ALARM_MODES);
|
|
2552
|
+
const resolved = this.delaysFor(armed);
|
|
2553
|
+
const next = trigger(this.machine, resolved, now);
|
|
2338
2554
|
if (next === this.machine) {
|
|
2339
2555
|
const state = stateAt(this.machine, now);
|
|
2340
2556
|
this.ctx.logger.info(ignoredTriggerMessage(state), {
|
|
@@ -2352,6 +2568,19 @@ var NcAlarmPanelDevice = class extends require_clip_model_registry.BaseDevice {
|
|
|
2352
2568
|
}
|
|
2353
2569
|
});
|
|
2354
2570
|
this.machine = next;
|
|
2571
|
+
this.ctx.logger.info("alarm delays applied", {
|
|
2572
|
+
tags: { deviceId: this.id },
|
|
2573
|
+
meta: {
|
|
2574
|
+
mode: armed,
|
|
2575
|
+
phase: "trigger",
|
|
2576
|
+
entryDelaySec: resolved.entryDelaySec,
|
|
2577
|
+
triggeredDurationSec: resolved.triggeredDurationSec,
|
|
2578
|
+
source: {
|
|
2579
|
+
entry: resolved.source.entryDelaySec,
|
|
2580
|
+
siren: resolved.source.triggeredDurationSec
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
});
|
|
2355
2584
|
this.publish(now);
|
|
2356
2585
|
}
|
|
2357
2586
|
});
|
|
@@ -2487,7 +2716,32 @@ var NcAlarmPanelDevice = class extends require_clip_model_registry.BaseDevice {
|
|
|
2487
2716
|
});
|
|
2488
2717
|
}
|
|
2489
2718
|
}
|
|
2490
|
-
/** The
|
|
2719
|
+
/** The global durations plus the per-mode overrides, read from the persisted config NOW. */
|
|
2720
|
+
delaySettings() {
|
|
2721
|
+
return {
|
|
2722
|
+
...this.readDelays(),
|
|
2723
|
+
modeDelays: modeDelaysFromPersisted(this.config.get("modeDelays")).entries
|
|
2724
|
+
};
|
|
2725
|
+
}
|
|
2726
|
+
/**
|
|
2727
|
+
* What `mode` runs on, read at the transition that uses it — never cached. `null` (no armed
|
|
2728
|
+
* mode to look at) is the global values, every field `default`.
|
|
2729
|
+
*/
|
|
2730
|
+
delaysFor(mode) {
|
|
2731
|
+
const settings = this.delaySettings();
|
|
2732
|
+
if (mode === null) return {
|
|
2733
|
+
exitDelaySec: settings.exitDelaySec,
|
|
2734
|
+
entryDelaySec: settings.entryDelaySec,
|
|
2735
|
+
triggeredDurationSec: settings.triggeredDurationSec,
|
|
2736
|
+
source: {
|
|
2737
|
+
exitDelaySec: "default",
|
|
2738
|
+
entryDelaySec: "default",
|
|
2739
|
+
triggeredDurationSec: "default"
|
|
2740
|
+
}
|
|
2741
|
+
};
|
|
2742
|
+
return require_clip_model_registry.resolveModeDelays(settings, mode);
|
|
2743
|
+
}
|
|
2744
|
+
/** The three global durations, read from the persisted config. */
|
|
2491
2745
|
readDelays() {
|
|
2492
2746
|
return {
|
|
2493
2747
|
exitDelaySec: this.config.get("exitDelaySec"),
|
|
@@ -8353,6 +8607,79 @@ function sameWord(a, b) {
|
|
|
8353
8607
|
return a !== void 0 && a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
8354
8608
|
}
|
|
8355
8609
|
//#endregion
|
|
8610
|
+
//#region src/notification-center/alarm/alarm-sensors.ts
|
|
8611
|
+
/**
|
|
8612
|
+
* One id's state as the table shows it — spec §3.4's hub table, nothing else.
|
|
8613
|
+
*
|
|
8614
|
+
* Built ON `classifyCoveredOpening`, the arm check's classifier: what is
|
|
8615
|
+
* unknown, open or skipped is decided there, and this only splits a skip into
|
|
8616
|
+
* `closed` / `not-opening` for the table. A later change to the classifier
|
|
8617
|
+
* therefore moves the table with the arm.
|
|
8618
|
+
*/
|
|
8619
|
+
function alarmSensorStateOf(deviceId, opening, online) {
|
|
8620
|
+
const verdict = classifyCoveredOpening(opening);
|
|
8621
|
+
switch (verdict.kind) {
|
|
8622
|
+
case "unknown": return {
|
|
8623
|
+
deviceId,
|
|
8624
|
+
reading: "unknown",
|
|
8625
|
+
state: null,
|
|
8626
|
+
unknownWhy: verdict.why,
|
|
8627
|
+
online
|
|
8628
|
+
};
|
|
8629
|
+
case "open": return {
|
|
8630
|
+
deviceId,
|
|
8631
|
+
reading: "open",
|
|
8632
|
+
state: verdict.state,
|
|
8633
|
+
unknownWhy: null,
|
|
8634
|
+
online
|
|
8635
|
+
};
|
|
8636
|
+
case "skip": return opening?.kind === "reading" ? {
|
|
8637
|
+
deviceId,
|
|
8638
|
+
reading: "closed",
|
|
8639
|
+
state: opening.state,
|
|
8640
|
+
unknownWhy: null,
|
|
8641
|
+
online
|
|
8642
|
+
} : {
|
|
8643
|
+
deviceId,
|
|
8644
|
+
reading: "not-opening",
|
|
8645
|
+
state: null,
|
|
8646
|
+
unknownWhy: null,
|
|
8647
|
+
online
|
|
8648
|
+
};
|
|
8649
|
+
}
|
|
8650
|
+
}
|
|
8651
|
+
/** The asked ids, de-duplicated and ascending. */
|
|
8652
|
+
function alarmSensorReadIds(ids) {
|
|
8653
|
+
return [...new Set(ids)].toSorted((a, b) => a - b);
|
|
8654
|
+
}
|
|
8655
|
+
/**
|
|
8656
|
+
* The sensors read's health, logged on CHANGE only (the call is polled every
|
|
8657
|
+
* 5-15 s by every open tab). "Wholesale failure" = ids were asked and not one
|
|
8658
|
+
* was answered (the device manager is unreachable): WARN once when it starts,
|
|
8659
|
+
* ONE INFO when answers return. A partial answer is not a health change — those
|
|
8660
|
+
* ids already leave their own debug line and show as `unknown`.
|
|
8661
|
+
*/
|
|
8662
|
+
var NcAlarmSensorsReadLog = class {
|
|
8663
|
+
failing = false;
|
|
8664
|
+
next(asked, answered) {
|
|
8665
|
+
const wholesale = asked > 0 && answered === 0;
|
|
8666
|
+
if (wholesale === this.failing) return null;
|
|
8667
|
+
this.failing = wholesale;
|
|
8668
|
+
return wholesale ? {
|
|
8669
|
+
level: "warn",
|
|
8670
|
+
message: "alarm sensors read: no asked device answered — every row is unknown",
|
|
8671
|
+
meta: { asked }
|
|
8672
|
+
} : {
|
|
8673
|
+
level: "info",
|
|
8674
|
+
message: "alarm sensors read: devices answer again",
|
|
8675
|
+
meta: {
|
|
8676
|
+
asked,
|
|
8677
|
+
answered
|
|
8678
|
+
}
|
|
8679
|
+
};
|
|
8680
|
+
}
|
|
8681
|
+
};
|
|
8682
|
+
//#endregion
|
|
8356
8683
|
//#region src/notification-center/alarm/alarm-live.ts
|
|
8357
8684
|
/** Every covered id across all modes, ascending, once — what the one read asks for. */
|
|
8358
8685
|
function alarmLiveReadIds(covered) {
|
|
@@ -11337,6 +11664,18 @@ function streamHealthAccepted(category, reason) {
|
|
|
11337
11664
|
function discardedSystemEvent(event, lookup) {
|
|
11338
11665
|
const accessory = discardedAccessoryLiveness(event, lookup);
|
|
11339
11666
|
if (accessory !== null) return accessory;
|
|
11667
|
+
if (event.category === "worker.online" || event.category === "worker.offline") {
|
|
11668
|
+
const workerId = stringField(event.data, "workerId");
|
|
11669
|
+
return {
|
|
11670
|
+
category: event.category,
|
|
11671
|
+
reason: "a runner is not a node",
|
|
11672
|
+
tags: {},
|
|
11673
|
+
meta: {
|
|
11674
|
+
reason: "a runner is not a node",
|
|
11675
|
+
...workerId !== void 0 ? { workerId } : {}
|
|
11676
|
+
}
|
|
11677
|
+
};
|
|
11678
|
+
}
|
|
11340
11679
|
if (event.category !== "stream.online" && event.category !== "stream.offline") return null;
|
|
11341
11680
|
const data = event.data;
|
|
11342
11681
|
const reason = stringField(data, "reason");
|
|
@@ -11663,12 +12002,11 @@ function incomingFromSystemEvent(event, lookup) {
|
|
|
11663
12002
|
deviceId
|
|
11664
12003
|
});
|
|
11665
12004
|
}
|
|
11666
|
-
case "agent.online":
|
|
11667
|
-
case "agent.offline":
|
|
11668
12005
|
case "worker.online":
|
|
11669
|
-
case "worker.offline":
|
|
11670
|
-
|
|
11671
|
-
|
|
12006
|
+
case "worker.offline": return null;
|
|
12007
|
+
case "agent.online":
|
|
12008
|
+
case "agent.offline": {
|
|
12009
|
+
const nodeId = stringField(data, "agentId");
|
|
11672
12010
|
if (nodeId === void 0) return null;
|
|
11673
12011
|
const kind = event.category.endsWith(".online") ? "node-online" : "node-offline";
|
|
11674
12012
|
return systemSubject({
|
|
@@ -11676,7 +12014,7 @@ function incomingFromSystemEvent(event, lookup) {
|
|
|
11676
12014
|
kind,
|
|
11677
12015
|
subject: `node:${nodeId}`,
|
|
11678
12016
|
titleKey: `system.${kind}.title`,
|
|
11679
|
-
bodyKey:
|
|
12017
|
+
bodyKey: `system.${kind}.body.agent`,
|
|
11680
12018
|
textParams: { nodeId },
|
|
11681
12019
|
nodeId
|
|
11682
12020
|
});
|
|
@@ -13293,10 +13631,8 @@ var en_default = {
|
|
|
13293
13631
|
"system.stream-offline.body.profiled": "{{profile}} stream for camera {{deviceName}} is offline",
|
|
13294
13632
|
"system.node-online.title": "Node online",
|
|
13295
13633
|
"system.node-online.body.agent": "Agent {{nodeId}} is online",
|
|
13296
|
-
"system.node-online.body.worker": "Worker {{nodeId}} is online",
|
|
13297
13634
|
"system.node-offline.title": "Node offline",
|
|
13298
13635
|
"system.node-offline.body.agent": "Agent {{nodeId}} is offline",
|
|
13299
|
-
"system.node-offline.body.worker": "Worker {{nodeId}} is offline",
|
|
13300
13636
|
"system.node-inference-unavailable.title": "Node has no usable inference device",
|
|
13301
13637
|
"system.node-inference-unavailable.body": "Node {{nodeId}} can no longer run inference — every enabled device is unavailable ({{devices}}). It will not receive cameras until one returns.",
|
|
13302
13638
|
"system.addon-crash-loop.title": "Addon stopped after repeated crashes",
|
|
@@ -13623,10 +13959,8 @@ var it_default = {
|
|
|
13623
13959
|
"system.stream-offline.body.profiled": "Lo stream {{profile}} della camera {{deviceName}} è offline",
|
|
13624
13960
|
"system.node-online.title": "Nodo online",
|
|
13625
13961
|
"system.node-online.body.agent": "L'agent {{nodeId}} è online",
|
|
13626
|
-
"system.node-online.body.worker": "Il worker {{nodeId}} è online",
|
|
13627
13962
|
"system.node-offline.title": "Nodo offline",
|
|
13628
13963
|
"system.node-offline.body.agent": "L'agent {{nodeId}} è offline",
|
|
13629
|
-
"system.node-offline.body.worker": "Il worker {{nodeId}} è offline",
|
|
13630
13964
|
"system.node-inference-unavailable.title": "Nodo senza dispositivi di inferenza utilizzabili",
|
|
13631
13965
|
"system.node-inference-unavailable.body": "Il nodo {{nodeId}} non può più eseguire inferenza — tutti i dispositivi abilitati non sono disponibili ({{devices}}). Non riceverà camere finché uno non torna disponibile.",
|
|
13632
13966
|
"system.addon-crash-loop.title": "Addon fermato dopo ripetuti crash",
|
|
@@ -17027,7 +17361,8 @@ var ncActions = require_clip_model_registry.defineCustomActions({
|
|
|
17027
17361
|
* `getStatus`; this one opens up once it filters to the caller's grants.
|
|
17028
17362
|
* The config pair is the admin's, as `getAlarmConfig` / `setAlarmConfig` are
|
|
17029
17363
|
* on the cap: it carries the delays, the recipients and the non-blocking
|
|
17030
|
-
* lists.
|
|
17364
|
+
* lists. `getAlarmSensors` is the admin's too: it names which of the asked
|
|
17365
|
+
* devices are open.
|
|
17031
17366
|
*/
|
|
17032
17367
|
"nc.getAlarmStatus": require_clip_model_registry.customAction(require_clip_model_registry.object({}), NcAlarmStatusSchema, {
|
|
17033
17368
|
auth: "admin",
|
|
@@ -17068,6 +17403,10 @@ var ncActions = require_clip_model_registry.defineCustomActions({
|
|
|
17068
17403
|
auth: "admin",
|
|
17069
17404
|
caller: "required"
|
|
17070
17405
|
}),
|
|
17406
|
+
"nc.getAlarmSensors": require_clip_model_registry.customAction(require_clip_model_registry.NcAlarmSensorsInputSchema, require_clip_model_registry.NcAlarmSensorsSchema, {
|
|
17407
|
+
auth: "admin",
|
|
17408
|
+
caller: "required"
|
|
17409
|
+
}),
|
|
17071
17410
|
"nc.setAlarmConfig": require_clip_model_registry.customAction(require_clip_model_registry.object({ patch: require_clip_model_registry.NcAlarmSettingsPatchSchema }), require_clip_model_registry.NcAlarmConfigSchema, {
|
|
17072
17411
|
kind: "mutation",
|
|
17073
17412
|
auth: "admin",
|
|
@@ -17860,6 +18199,10 @@ function makeNcActionHandlers(deps) {
|
|
|
17860
18199
|
requireAdmin(caller);
|
|
17861
18200
|
return requireAlarm("nc.getAlarmConfig").getConfig();
|
|
17862
18201
|
},
|
|
18202
|
+
"nc.getAlarmSensors": async (input, caller) => {
|
|
18203
|
+
requireAdmin(caller);
|
|
18204
|
+
return requireAlarm("nc.getAlarmSensors").getSensors(input);
|
|
18205
|
+
},
|
|
17863
18206
|
"nc.setAlarmConfig": async (input, caller) => {
|
|
17864
18207
|
requireAdmin(caller);
|
|
17865
18208
|
return requireAlarm("nc.setAlarmConfig").setConfig(input.patch);
|
|
@@ -32694,7 +33037,8 @@ function consumablesSliceOf(event) {
|
|
|
32694
33037
|
var NC_ALARM_SETTINGS_FALLBACK = {
|
|
32695
33038
|
...DEFAULT_DELAYS,
|
|
32696
33039
|
announceTargets: [],
|
|
32697
|
-
nonBlocking: []
|
|
33040
|
+
nonBlocking: [],
|
|
33041
|
+
modeDelays: []
|
|
32698
33042
|
};
|
|
32699
33043
|
/**
|
|
32700
33044
|
* How often the "matched NO rule" report may fire per device. Long enough that
|
|
@@ -33076,6 +33420,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
33076
33420
|
alarmPanel = null;
|
|
33077
33421
|
/** The live view's read, logged on change only — see `alarm-live-read-log.ts`. */
|
|
33078
33422
|
alarmLiveReadLog = new NcAlarmLiveReadLog();
|
|
33423
|
+
alarmSensorsReadLog = new NcAlarmSensorsReadLog();
|
|
33079
33424
|
/**
|
|
33080
33425
|
* The words the panel answers the `deviceState` gate with — its state, plus
|
|
33081
33426
|
* the armed mode a `pending`/`triggered` belongs to (D630). Refreshed on
|
|
@@ -35394,6 +35739,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
35394
35739
|
return { success: true };
|
|
35395
35740
|
},
|
|
35396
35741
|
getAlarmConfig: async () => this.alarmConfig(),
|
|
35742
|
+
getAlarmSensors: async (input) => this.alarmSensors(input),
|
|
35397
35743
|
setAlarmConfig: async ({ patch }) => this.setAlarmConfig(patch)
|
|
35398
35744
|
};
|
|
35399
35745
|
}
|
|
@@ -35621,6 +35967,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
35621
35967
|
});
|
|
35622
35968
|
},
|
|
35623
35969
|
getConfig: () => this.alarmConfig(),
|
|
35970
|
+
getSensors: (input) => this.alarmSensors(input),
|
|
35624
35971
|
setConfig: (patch) => this.setAlarmConfig(patch)
|
|
35625
35972
|
};
|
|
35626
35973
|
}
|
|
@@ -35634,7 +35981,8 @@ var NotificationCenter = class NotificationCenter {
|
|
|
35634
35981
|
exitDelaySec: settings.exitDelaySec,
|
|
35635
35982
|
entryDelaySec: settings.entryDelaySec,
|
|
35636
35983
|
triggeredDurationSec: settings.triggeredDurationSec,
|
|
35637
|
-
announceTargets: settings.announceTargets.length
|
|
35984
|
+
announceTargets: settings.announceTargets.length,
|
|
35985
|
+
modeDelays: settings.modeDelays.length
|
|
35638
35986
|
}
|
|
35639
35987
|
});
|
|
35640
35988
|
return this.alarmConfig();
|
|
@@ -35690,6 +36038,40 @@ var NotificationCenter = class NotificationCenter {
|
|
|
35690
36038
|
name: this.deviceNameFromDirectory()
|
|
35691
36039
|
});
|
|
35692
36040
|
}
|
|
36041
|
+
/**
|
|
36042
|
+
* The opening states of the asked ids, for the alarm editors' sensor table.
|
|
36043
|
+
*
|
|
36044
|
+
* A separate read from `getAlarmConfig` on purpose: that one is polled every
|
|
36045
|
+
* 2–15 s and the household set needs a whole-fleet bindings pass, while the
|
|
36046
|
+
* client already holds the ids. Cost: one `loadRuntimeState` RPC per id, 8 in
|
|
36047
|
+
* flight (`readAlarmOpenings`); the client sends at most `NC_ALARM_SENSORS_MAX`
|
|
36048
|
+
* ids per call and polls every 15 s, every 5 s only while a countdown or the
|
|
36049
|
+
* siren runs. No log line per call (it is polled): one WARN/INFO per change of
|
|
36050
|
+
* wholesale read health (`NcAlarmSensorsReadLog`); a per-id read failure keeps
|
|
36051
|
+
* `readAlarmOpenings`' own debug line, and shows as `unknown` (D49).
|
|
36052
|
+
*
|
|
36053
|
+
* An id with no persisted device (removed) or whose provider never wrote an
|
|
36054
|
+
* opening slice reads `not-opening`, exactly as the arm check sees it — NOT
|
|
36055
|
+
* "verified not an opening". The client shows "Removed" from its fleet read.
|
|
36056
|
+
*/
|
|
36057
|
+
async alarmSensors(input) {
|
|
36058
|
+
const read = this.deps.readDeviceOpenings;
|
|
36059
|
+
if (read === void 0) throw new Error("the opening reader is not wired on this node");
|
|
36060
|
+
const ids = alarmSensorReadIds(input.deviceIds);
|
|
36061
|
+
const openings = await read(ids);
|
|
36062
|
+
const line = this.alarmSensorsReadLog.next(ids.length, openings.size);
|
|
36063
|
+
if (line !== null) {
|
|
36064
|
+
const panelId = this.alarmPanel?.deviceId;
|
|
36065
|
+
this.logger[line.level](line.message, {
|
|
36066
|
+
...panelId === void 0 ? {} : { tags: { deviceId: panelId } },
|
|
36067
|
+
meta: line.meta
|
|
36068
|
+
});
|
|
36069
|
+
}
|
|
36070
|
+
return {
|
|
36071
|
+
sensors: ids.map((id) => alarmSensorStateOf(id, openings.get(id), this.deviceDirectory.get(id)?.online ?? null)),
|
|
36072
|
+
readAt: Date.now()
|
|
36073
|
+
};
|
|
36074
|
+
}
|
|
35693
36075
|
/** Settings + the DERIVED coverage + the live panel. Read together — see the cap doc. */
|
|
35694
36076
|
async alarmConfig() {
|
|
35695
36077
|
const panel = this.alarmPanel;
|