@camstack/types 1.2.179 → 1.2.181
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/capabilities/data-store-provider.cap.d.ts +4 -2
- package/dist/capabilities/index.d.ts +2 -2
- package/dist/capabilities/motion.cap.d.ts +18 -1
- package/dist/capabilities/pipeline-analytics.cap.d.ts +30 -8
- package/dist/capabilities/pipeline-runner.cap.d.ts +8 -2
- package/dist/capabilities/settings-store.cap.d.ts +4 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +302 -12
- package/dist/index.mjs +297 -13
- package/dist/interfaces/pipeline-runner-capability.d.ts +2 -0
- package/dist/interfaces/storage.d.ts +19 -2
- package/dist/media/media-owner-key.d.ts +90 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,6 +5,124 @@ const require_canonical_hash = require("./canonical-hash-DNV8S5ET.js");
|
|
|
5
5
|
const require_enums = require("./enums.js");
|
|
6
6
|
const require_err_msg = require("./err-msg-COpsHMw2.js");
|
|
7
7
|
let zod = require("zod");
|
|
8
|
+
//#region src/media/media-owner-key.ts
|
|
9
|
+
/**
|
|
10
|
+
* The one place a media key is built and read.
|
|
11
|
+
*
|
|
12
|
+
* A media key addresses a file by its OWNER, in one grammar:
|
|
13
|
+
*
|
|
14
|
+
* ```
|
|
15
|
+
* <ownerType>:<ownerId>:<fileKind>[:<timestampMs>]
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* The owner type is a TABLE. That is already true of every owner on the live
|
|
19
|
+
* hub - `track`, `summary`, `face`, `identity`, `plate` - and the exception was
|
|
20
|
+
* `event`, which covered three tables at once. While event ids were UUIDs the
|
|
21
|
+
* ambiguity cost nothing, because a UUID is unique across tables. D474 made an
|
|
22
|
+
* event id a SQLite rowid alias, so each table numbers from 1 and `event:5`
|
|
23
|
+
* stopped naming one row. Splitting `event` into `motion` / `object` / `audio`
|
|
24
|
+
* restores uniqueness without a fifth segment and without a magic numbering
|
|
25
|
+
* offset, which would have hidden the same invariant somewhere nobody reads.
|
|
26
|
+
*
|
|
27
|
+
* Why this module exists at all, rather than a `split(':')` at each call site:
|
|
28
|
+
* the key is built in the addon, in `ui-library` and in the notification
|
|
29
|
+
* dispatcher, and read positionally in admin-ui (`split(':')[1]` for an owner
|
|
30
|
+
* id). Positional reads are silently wrong the moment a segment moves, and
|
|
31
|
+
* there is no compiler that notices. It lives in `@camstack/types` because both
|
|
32
|
+
* an addon and the UI packages must reach it, and addons may not import each
|
|
33
|
+
* other.
|
|
34
|
+
*
|
|
35
|
+
* `parseMediaOwnerKey` returns `null` for anything it cannot read, and never
|
|
36
|
+
* throws: it sits on the media READ path, where an exception takes out every
|
|
37
|
+
* thumbnail and a guess puts the wrong image on screen. A legacy `event:` key
|
|
38
|
+
* is therefore unreadable BY DESIGN - on this hub the 299 883 resolvable ones
|
|
39
|
+
* are rewritten by the D474 data migration, and the 676 that remain were
|
|
40
|
+
* already orphaned, their event having aged out before it.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Owner types, i.e. the tables that own media.
|
|
44
|
+
*
|
|
45
|
+
* `motion` / `object` / `audio` are the three event tables; `event` is
|
|
46
|
+
* deliberately absent and must not be re-added - it is the ambiguity this
|
|
47
|
+
* module exists to remove.
|
|
48
|
+
*
|
|
49
|
+
* This list must cover `OWNER_KINDS` in
|
|
50
|
+
* `addon-post-analysis/src/pipeline-analytics/store/media-store.ts`, which is
|
|
51
|
+
* the AUTHORITY on what may own media. I first derived it from the prefixes
|
|
52
|
+
* actually present on the live hub and so omitted `vehicle` and `scene`: both
|
|
53
|
+
* are declared owner kinds with no rows today, and the parser would have
|
|
54
|
+
* rejected their keys the day one appeared. A list derived from data is a list
|
|
55
|
+
* that is correct until the data changes.
|
|
56
|
+
*/
|
|
57
|
+
var MEDIA_OWNER_TYPES = [
|
|
58
|
+
"track",
|
|
59
|
+
"summary",
|
|
60
|
+
"face",
|
|
61
|
+
"identity",
|
|
62
|
+
"plate",
|
|
63
|
+
"vehicle",
|
|
64
|
+
"scene",
|
|
65
|
+
"motion",
|
|
66
|
+
"object",
|
|
67
|
+
"audio"
|
|
68
|
+
];
|
|
69
|
+
var OWNER_TYPES = new Set(MEDIA_OWNER_TYPES);
|
|
70
|
+
/** Digits only. `Number('')` is 0 and `Number(' ')` is 0, so neither is used. */
|
|
71
|
+
var DIGITS = /^\d+$/;
|
|
72
|
+
function isOwnerType(value) {
|
|
73
|
+
return OWNER_TYPES.has(value);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Read a media key. `null` when it is not one - an unknown owner type, a legacy
|
|
77
|
+
* `event:` key, a missing segment, or a fourth segment that is not a timestamp.
|
|
78
|
+
*
|
|
79
|
+
* `fileKind` is returned as a plain string rather than validated against
|
|
80
|
+
* `MediaFileKindEnum`: kinds have been RETIRED over time (`crop`,
|
|
81
|
+
* `fullFrameBoxed`) and rows carrying them still exist, so a parser that
|
|
82
|
+
* rejected them would make historical media unreadable while claiming the key
|
|
83
|
+
* was malformed.
|
|
84
|
+
*/
|
|
85
|
+
function parseMediaOwnerKey(key) {
|
|
86
|
+
const parts = key.split(":");
|
|
87
|
+
if (parts.length !== 3 && parts.length !== 4) return null;
|
|
88
|
+
const [ownerType, ownerId, fileKind, timestamp] = parts;
|
|
89
|
+
if (ownerType === void 0 || !isOwnerType(ownerType)) return null;
|
|
90
|
+
if (ownerId === void 0 || ownerId.length === 0) return null;
|
|
91
|
+
if (fileKind === void 0 || fileKind.length === 0) return null;
|
|
92
|
+
if (timestamp === void 0) return {
|
|
93
|
+
ownerType,
|
|
94
|
+
ownerId,
|
|
95
|
+
fileKind
|
|
96
|
+
};
|
|
97
|
+
if (!DIGITS.test(timestamp)) return null;
|
|
98
|
+
return {
|
|
99
|
+
ownerType,
|
|
100
|
+
ownerId,
|
|
101
|
+
fileKind,
|
|
102
|
+
timestampMs: Number(timestamp)
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** Build a media key. The timestamp segment is omitted when there is none. */
|
|
106
|
+
function formatMediaOwnerKey(key) {
|
|
107
|
+
const head = `${key.ownerType}:${key.ownerId}:${key.fileKind}`;
|
|
108
|
+
return key.timestampMs === void 0 ? head : `${head}:${key.timestampMs}`;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The owner types that are event tables, in the order the event-media resolver
|
|
112
|
+
* should consider them. Exported so a consumer asking "is this key an event?"
|
|
113
|
+
* does not re-spell the list and drift from it.
|
|
114
|
+
*/
|
|
115
|
+
var EVENT_OWNER_TYPES = [
|
|
116
|
+
"motion",
|
|
117
|
+
"object",
|
|
118
|
+
"audio"
|
|
119
|
+
];
|
|
120
|
+
var EVENT_OWNER_TYPE_SET = new Set(EVENT_OWNER_TYPES);
|
|
121
|
+
/** True when this key's owner is one of the three event tables. */
|
|
122
|
+
function isEventOwnerType(ownerType) {
|
|
123
|
+
return EVENT_OWNER_TYPE_SET.has(ownerType);
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
8
126
|
//#region src/addon/build-addon-route-provider.ts
|
|
9
127
|
/**
|
|
10
128
|
* Build an `IAddonRouteProvider` from a list of routes. Implements
|
|
@@ -9723,8 +9841,17 @@ var settingsStoreCapability = {
|
|
|
9723
9841
|
insert: require_sleep.method(zod.z.object({
|
|
9724
9842
|
namespace: zod.z.string().optional(),
|
|
9725
9843
|
collection: zod.z.string(),
|
|
9726
|
-
record:
|
|
9727
|
-
|
|
9844
|
+
record: zod.z.object({
|
|
9845
|
+
id: zod.z.string().optional(),
|
|
9846
|
+
data: zod.z.record(zod.z.string(), zod.z.unknown())
|
|
9847
|
+
})
|
|
9848
|
+
}), zod.z.object({
|
|
9849
|
+
/**
|
|
9850
|
+
* The id the row ACTUALLY got (D473): the one supplied, the UUID minted
|
|
9851
|
+
* for an absent one, or the ROWID SQLite assigned on an `INTEGER`
|
|
9852
|
+
* primary key — which is the only place an auto key is knowable.
|
|
9853
|
+
*/
|
|
9854
|
+
id: zod.z.union([zod.z.string(), zod.z.number()]) }), { kind: "mutation" }),
|
|
9728
9855
|
/**
|
|
9729
9856
|
* Insert MANY records in ONE transaction, returning how many landed.
|
|
9730
9857
|
*
|
|
@@ -9987,8 +10114,17 @@ var dataStoreProviderCapability = {
|
|
|
9987
10114
|
insert: require_sleep.method(zod.z.object({
|
|
9988
10115
|
namespace: zod.z.string().optional(),
|
|
9989
10116
|
collection: zod.z.string(),
|
|
9990
|
-
record:
|
|
9991
|
-
|
|
10117
|
+
record: zod.z.object({
|
|
10118
|
+
id: zod.z.string().optional(),
|
|
10119
|
+
data: zod.z.record(zod.z.string(), zod.z.unknown())
|
|
10120
|
+
})
|
|
10121
|
+
}), zod.z.object({
|
|
10122
|
+
/**
|
|
10123
|
+
* The id the row ACTUALLY got (D473): the one supplied, the UUID minted
|
|
10124
|
+
* for an absent one, or the ROWID SQLite assigned on an `INTEGER`
|
|
10125
|
+
* primary key — which is the only place an auto key is knowable.
|
|
10126
|
+
*/
|
|
10127
|
+
id: zod.z.union([zod.z.string(), zod.z.number()]) }), {
|
|
9992
10128
|
kind: "mutation",
|
|
9993
10129
|
auth: "admin"
|
|
9994
10130
|
}),
|
|
@@ -19459,7 +19595,20 @@ var TrackSchema = zod.z.object({
|
|
|
19459
19595
|
...TrackRetrainFields
|
|
19460
19596
|
});
|
|
19461
19597
|
var BaseEventFields = {
|
|
19462
|
-
|
|
19598
|
+
/**
|
|
19599
|
+
* A SQLite ROWID, assigned by the database (D474).
|
|
19600
|
+
*
|
|
19601
|
+
* Was a 36-character UUID and cost 263 MB of a 1 117 MB database — paid
|
|
19602
|
+
* TWICE per row, in the row and in the primary-key index, across 2.1 million
|
|
19603
|
+
* motion, audio and object events. An `INTEGER PRIMARY KEY` in SQLite **is**
|
|
19604
|
+
* the rowid: the table itself is that B-tree, so the index stops existing
|
|
19605
|
+
* rather than getting smaller. No shorter string does that.
|
|
19606
|
+
*
|
|
19607
|
+
* Defined once here for all three event kinds, which is why they move
|
|
19608
|
+
* together: a per-table migration would have forked this and
|
|
19609
|
+
* `COMMON_BASE_COLUMNS` and reunited them two stages later.
|
|
19610
|
+
*/
|
|
19611
|
+
id: zod.z.number().int(),
|
|
19463
19612
|
deviceId: zod.z.number(),
|
|
19464
19613
|
timestamp: zod.z.number()
|
|
19465
19614
|
};
|
|
@@ -19478,7 +19627,34 @@ var MotionEventSchema = zod.z.object({
|
|
|
19478
19627
|
/** Omitted in slim projection. */
|
|
19479
19628
|
frameHeight: zod.z.number().optional(),
|
|
19480
19629
|
/** Populated by B5 (recording playback URL for this event). */
|
|
19481
|
-
mediaUrl: zod.z.string().optional()
|
|
19630
|
+
mediaUrl: zod.z.string().optional(),
|
|
19631
|
+
/**
|
|
19632
|
+
* One row per motion EPISODE, not one per push (D475). `null` while the
|
|
19633
|
+
* episode is still open — a further rising edge extends it in place rather
|
|
19634
|
+
* than inserting a new row. Set once, at close, to `lastOnAt - startedAt`
|
|
19635
|
+
* (the span from the first rising edge to the LAST one, deliberately NOT
|
|
19636
|
+
* `closedAt - startedAt` — the close delay is a quiet CONFIRMATION, not
|
|
19637
|
+
* movement, and folding it in would report `MOTION_CLOSE_AFTER_MS` of
|
|
19638
|
+
* motion for an instantaneous trigger).
|
|
19639
|
+
*
|
|
19640
|
+
* **Absent** (not merely `null`) on a row written before D475 — that means
|
|
19641
|
+
* "closed the old way, before this column existed", never "still open".
|
|
19642
|
+
* Nothing in this codebase may read an absent `durationMs` as an open
|
|
19643
|
+
* episode; only `null` means open.
|
|
19644
|
+
*/
|
|
19645
|
+
durationMs: zod.z.number().nullable().optional(),
|
|
19646
|
+
/**
|
|
19647
|
+
* Ms offsets from `timestamp` (the episode's own first rising edge, so the
|
|
19648
|
+
* first entry is always `0`) of every genuine off→on transition the
|
|
19649
|
+
* episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
|
|
19650
|
+
* push: a firmware source that keepalives at ~1 Hz for the whole burst
|
|
19651
|
+
* (Reolink, Hikvision) produces exactly one edge; a source that reports an
|
|
19652
|
+
* explicit `false` mid-episode and then resumes before the quiet window
|
|
19653
|
+
* elapses produces another. Stored compactly — see `motion-edge-codec.ts`
|
|
19654
|
+
* — and decoded back to this shape on read. Absent/empty on a legacy row,
|
|
19655
|
+
* which must never be read as "no episode happened here".
|
|
19656
|
+
*/
|
|
19657
|
+
edges: zod.z.array(zod.z.number()).readonly().optional()
|
|
19482
19658
|
});
|
|
19483
19659
|
/**
|
|
19484
19660
|
* Which detection SOURCE produced an object event. `pipeline` = the ML
|
|
@@ -19541,6 +19717,23 @@ var ObjectEventSchema = zod.z.object({
|
|
|
19541
19717
|
* includes it (it is light). Absent on rows written before this field.
|
|
19542
19718
|
*/
|
|
19543
19719
|
frameId: zod.z.string().optional(),
|
|
19720
|
+
/**
|
|
19721
|
+
* A PRODUCER-chosen key that makes a synthetic event's emission idempotent
|
|
19722
|
+
* (D474).
|
|
19723
|
+
*
|
|
19724
|
+
* Only the package detector writes it, and it exists because the event id
|
|
19725
|
+
* stopped being choosable: the delivery and pick-up rows used to BE their
|
|
19726
|
+
* dedupe key (`pa-pkg-<entryId>-delivered`), which is how "never emit a
|
|
19727
|
+
* second delivery for this entry" survived a restart. An `INTEGER` rowid is
|
|
19728
|
+
* assigned by SQLite, so that key had to move off the primary key rather
|
|
19729
|
+
* than be dropped — a detector that cannot recognise its own row re-delivers
|
|
19730
|
+
* every parcel on every boot.
|
|
19731
|
+
*
|
|
19732
|
+
* Absent on every other object event, and on every row written before this
|
|
19733
|
+
* field. Never a substitute for `id`: it is unique per (producer, occasion),
|
|
19734
|
+
* not per row, and nothing addresses a row by it.
|
|
19735
|
+
*/
|
|
19736
|
+
idempotencyKey: zod.z.string().optional(),
|
|
19544
19737
|
/** Omitted in slim projection. */
|
|
19545
19738
|
trackId: zod.z.string().optional(),
|
|
19546
19739
|
className: zod.z.string(),
|
|
@@ -21271,6 +21464,14 @@ var NativeCropResultSchema = zod.z.object({
|
|
|
21271
21464
|
* set `encodeJpeg: true`; `bytes` is then absent.
|
|
21272
21465
|
*/
|
|
21273
21466
|
jpeg: zod.z.string().optional(),
|
|
21467
|
+
/**
|
|
21468
|
+
* The SAME compressed JPEG as `jpeg`, as bytes (D462). Present instead of
|
|
21469
|
+
* `jpeg` when the request set `acceptJpegBytes`; a request that did not gets
|
|
21470
|
+
* `jpeg` exactly as before. MsgPack and the mesh leg both carry binary —
|
|
21471
|
+
* `bytes` above has crossed this boundary as a `Uint8Array` all along — so
|
|
21472
|
+
* base64 was buying nothing but a multi-megabyte string in the relay's heap.
|
|
21473
|
+
*/
|
|
21474
|
+
jpegBytes: zod.z.instanceof(Uint8Array).optional(),
|
|
21274
21475
|
width: zod.z.number().int().positive(),
|
|
21275
21476
|
height: zod.z.number().int().positive(),
|
|
21276
21477
|
/**
|
|
@@ -21337,7 +21538,14 @@ var ParkTrackFrameResultSchema = zod.z.discriminatedUnion("parked", [zod.z.objec
|
|
|
21337
21538
|
})]);
|
|
21338
21539
|
/** A retrieved parcel — the runner's own JPEG, base64 for the wire. */
|
|
21339
21540
|
var ParkedTrackFrameSchema = zod.z.object({
|
|
21340
|
-
|
|
21541
|
+
/**
|
|
21542
|
+
* Base64 JPEG — the pre-D462 wire. OPTIONAL since D462: a request that set
|
|
21543
|
+
* `acceptJpegBytes` is answered in `jpegBytes` and this is then absent.
|
|
21544
|
+
* Exactly one of the two is present.
|
|
21545
|
+
*/
|
|
21546
|
+
jpeg: zod.z.string().optional(),
|
|
21547
|
+
/** The same JPEG as bytes, for a caller that declared it reads them (D462). */
|
|
21548
|
+
jpegBytes: zod.z.instanceof(Uint8Array).optional(),
|
|
21341
21549
|
width: zod.z.number().int().positive(),
|
|
21342
21550
|
height: zod.z.number().int().positive(),
|
|
21343
21551
|
/** The frame instant the parcel shows (the caller's clock, echoed back). */
|
|
@@ -22176,6 +22384,13 @@ var pipelineRunnerCapability = {
|
|
|
22176
22384
|
bbox: NativeCropBboxSchema,
|
|
22177
22385
|
maxWidth: zod.z.number().int().positive().optional(),
|
|
22178
22386
|
/**
|
|
22387
|
+
* The caller reads a `Uint8Array` (D462). When set, a JPEG answer comes
|
|
22388
|
+
* back in `jpegBytes` instead of base64 `jpeg`. Absent means the old
|
|
22389
|
+
* wire — never assume consent: a pre-D462 caller parses the field as
|
|
22390
|
+
* base64 and bytes would decode to garbage rather than fail.
|
|
22391
|
+
*/
|
|
22392
|
+
acceptJpegBytes: zod.z.boolean().optional(),
|
|
22393
|
+
/**
|
|
22179
22394
|
* When `true`, the runner encodes the resolved crop to JPEG ON THE
|
|
22180
22395
|
* OWNING NODE and returns it in `jpeg` (base64) INSTEAD of raw `bytes`.
|
|
22181
22396
|
* Callers set this for CROSS-NODE fetches (`handle.nodeId` is a remote
|
|
@@ -22348,7 +22563,14 @@ var pipelineRunnerCapability = {
|
|
|
22348
22563
|
getParkedTrackFrame: require_sleep.method(zod.z.object({
|
|
22349
22564
|
deviceId: zod.z.number(),
|
|
22350
22565
|
trackId: zod.z.string(),
|
|
22351
|
-
kind: ParkedFrameKindSchema
|
|
22566
|
+
kind: ParkedFrameKindSchema,
|
|
22567
|
+
/**
|
|
22568
|
+
* The caller reads a `Uint8Array` (D462). When set, a JPEG answer comes
|
|
22569
|
+
* back in `jpegBytes` instead of base64 `jpeg`. Absent means the old
|
|
22570
|
+
* wire — never assume consent: a pre-D462 caller parses the field as
|
|
22571
|
+
* base64 and bytes would decode to garbage rather than fail.
|
|
22572
|
+
*/
|
|
22573
|
+
acceptJpegBytes: zod.z.boolean().optional()
|
|
22352
22574
|
}), ParkedTrackFrameSchema.nullable()),
|
|
22353
22575
|
/**
|
|
22354
22576
|
* Drop every parcel of a track — the hub closed it. Idempotent. A track
|
|
@@ -30644,14 +30866,72 @@ authKey: zod.z.string().optional() }), zod.z.object({
|
|
|
30644
30866
|
* (alert-center, advanced-notifier) can subscribe once and receive
|
|
30645
30867
|
* motion from every camera.
|
|
30646
30868
|
*/
|
|
30869
|
+
/**
|
|
30870
|
+
* How long after a camera's last rising edge `pipeline-analytics` closes the
|
|
30871
|
+
* motion EPISODE row it kept open for it (D475) — "il delay della cap", in
|
|
30872
|
+
* the operator's words. One authority for the whole system: every provider
|
|
30873
|
+
* that seeds `autoClearAfterMs` on a `Camera` device writes this SAME
|
|
30874
|
+
* constant while `detected: true` (never its own number — Hikvision used to
|
|
30875
|
+
* seed its internal 3 s inactivity timer here, which is a different clock for
|
|
30876
|
+
* a different purpose), and `MotionEpisodeTracker` imports it directly rather
|
|
30877
|
+
* than reading the live cap value on the hot path. A `Sensor` device (HA
|
|
30878
|
+
* binary sensor, Homematic) gets a genuine push both ways and has no episode
|
|
30879
|
+
* to close on a timer, so it writes `null`, never this constant.
|
|
30880
|
+
*/
|
|
30881
|
+
var MOTION_CLOSE_AFTER_MS = 15e3;
|
|
30647
30882
|
var MotionStatusSchema = zod.z.object({
|
|
30648
30883
|
detected: zod.z.boolean(),
|
|
30649
30884
|
/** Ms epoch of the last detected-true observation. Null if never detected. */
|
|
30650
30885
|
lastDetectedAt: zod.z.number().nullable(),
|
|
30651
30886
|
/**
|
|
30652
|
-
*
|
|
30653
|
-
*
|
|
30654
|
-
*
|
|
30887
|
+
* `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
|
|
30888
|
+
* `null` while false and on every `Sensor` device (D475) — see that
|
|
30889
|
+
* constant's doc for the one-authority rule.
|
|
30890
|
+
*
|
|
30891
|
+
* ## Reading this field still arms nothing
|
|
30892
|
+
*
|
|
30893
|
+
* It reads like an instruction to the consumer ("revert after N ms if
|
|
30894
|
+
* no fresh push arrives") and it is not one: nothing in this repo reads
|
|
30895
|
+
* the LIVE cap value to drive a timer. `pipeline-analytics`'s motion-episode
|
|
30896
|
+
* close DOES now use the same number — `MOTION_CLOSE_AFTER_MS` — but as an
|
|
30897
|
+
* imported constant, not as a read of `device.state.motion.value`, so this
|
|
30898
|
+
* field stays what it always was: DESCRIPTIVE output, mirroring an answer
|
|
30899
|
+
* computed elsewhere. Building a self-clear timer out of a READ of this
|
|
30900
|
+
* field would add a second falling-edge authority beside whichever one
|
|
30901
|
+
* already owns the device, and two that can disagree are worse than one.
|
|
30902
|
+
* Consumers that need a falling edge SHAPED differently — held open across
|
|
30903
|
+
* a flapping source — debounce on their own side and say so, as
|
|
30904
|
+
* `addon-export-alexa/src/motion-clear-hold.ts` and
|
|
30905
|
+
* `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
|
|
30906
|
+
*
|
|
30907
|
+
* ## Who writes it
|
|
30908
|
+
*
|
|
30909
|
+
* - **Cameras** — the runner's phase machine, `active → watching` on
|
|
30910
|
+
* `cooldown_expired`, which then writes this slice with
|
|
30911
|
+
* `detected: false` (`handlePhaseChanged` in
|
|
30912
|
+
* `pipeline-runner/index.ts`). It produces the FALLING edge, which
|
|
30913
|
+
* matters most for the sources that only ever push a rising one:
|
|
30914
|
+
* Reolink emits `MotionOnMotionChanged { detected: true }` and never
|
|
30915
|
+
* a false.
|
|
30916
|
+
* - **Sensors** (Home Assistant binary sensors, Homematic) — the
|
|
30917
|
+
* provider pushes the false itself, from the upstream system's own
|
|
30918
|
+
* state change. No phase machine is involved.
|
|
30919
|
+
*
|
|
30920
|
+
* ### The phase machine is CANONICAL, not sole — and that is a defect
|
|
30921
|
+
*
|
|
30922
|
+
* An earlier revision of this docblock (mine, 2026-09-12) claimed the
|
|
30923
|
+
* phase machine is the sole writer for a camera. It is not.
|
|
30924
|
+
* `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
|
|
30925
|
+
* `setCapSlice(motionCapability, …)` on their own rising edge, and
|
|
30926
|
+
* Hikvision's comment says why: it read THIS docblock, agreed the
|
|
30927
|
+
* runner is canonical, and wrote anyway to avoid per-tick churn. So
|
|
30928
|
+
* two authorities can disagree about one slice, which this repo
|
|
30929
|
+
* forbids, and the doc said otherwise — which is worse than saying
|
|
30930
|
+
* nothing, because it reads as verification.
|
|
30931
|
+
*
|
|
30932
|
+
* This predates D475 and is not fixed there: the fix touches every
|
|
30933
|
+
* camera provider. Recorded in D475's Consequences. Do not restore the
|
|
30934
|
+
* "sole writer" wording without also removing the other writers.
|
|
30655
30935
|
*/
|
|
30656
30936
|
autoClearAfterMs: zod.z.number().nullable()
|
|
30657
30937
|
});
|
|
@@ -30721,7 +31001,11 @@ onMotionChanged: { data: MotionOnMotionChangedDataSchema } },
|
|
|
30721
31001
|
*/
|
|
30722
31002
|
runtimeState: MotionStatusSchema,
|
|
30723
31003
|
/**
|
|
30724
|
-
* Runtime-state durability: **session** —
|
|
31004
|
+
* Runtime-state durability: **session** — every writer of this slice
|
|
31005
|
+
* writes only on an EDGE, so a restored `detected: true` would stay
|
|
31006
|
+
* frozen until the next one instead of being corrected. The next edge
|
|
31007
|
+
* re-publishes the real state. (On who the writers are, and why there
|
|
31008
|
+
* is more than one, see `autoClearAfterMs` above.)
|
|
30725
31009
|
*
|
|
30726
31010
|
* See `RuntimeStateDurability`. Enforced by
|
|
30727
31011
|
* `scripts/check-runtime-state-durability.ts`.
|
|
@@ -56003,6 +56287,7 @@ exports.DoorbellStatusSchema = DoorbellStatusSchema;
|
|
|
56003
56287
|
exports.EVENTFUL_CAP_NAMES = EVENTFUL_CAP_NAMES;
|
|
56004
56288
|
exports.EVENT_DENSITY_BATCH_MAX = EVENT_DENSITY_BATCH_MAX;
|
|
56005
56289
|
exports.EVENT_KIND_BY_CAP = EVENT_KIND_BY_CAP;
|
|
56290
|
+
exports.EVENT_OWNER_TYPES = EVENT_OWNER_TYPES;
|
|
56006
56291
|
exports.EVENT_PAD_MS = EVENT_PAD_MS;
|
|
56007
56292
|
exports.EVENT_TAXONOMY = EVENT_TAXONOMY;
|
|
56008
56293
|
exports.EXPORT_DENSE_MAX_RANGES = EXPORT_DENSE_MAX_RANGES;
|
|
@@ -56193,10 +56478,12 @@ exports.MAX_KEYS = MAX_KEYS;
|
|
|
56193
56478
|
exports.MAX_REASONS_PER_KEY = MAX_REASONS_PER_KEY;
|
|
56194
56479
|
exports.MAX_SENSOR_TRIGGER_DEVICES = MAX_SENSOR_TRIGGER_DEVICES;
|
|
56195
56480
|
exports.MAX_TRACK_DEBUG_NOTE_LEN = MAX_TRACK_DEBUG_NOTE_LEN;
|
|
56481
|
+
exports.MEDIA_OWNER_TYPES = MEDIA_OWNER_TYPES;
|
|
56196
56482
|
exports.METHOD_ACCESS_MAP = METHOD_ACCESS_MAP;
|
|
56197
56483
|
exports.METHOD_DEVICE_SELECTORS = METHOD_DEVICE_SELECTORS;
|
|
56198
56484
|
exports.MODEL_FORMATS = MODEL_FORMATS;
|
|
56199
56485
|
exports.MODEL_PROVIDER_IDS = MODEL_PROVIDER_IDS;
|
|
56486
|
+
exports.MOTION_CLOSE_AFTER_MS = MOTION_CLOSE_AFTER_MS;
|
|
56200
56487
|
exports.MOTION_TRIGGER_FEATURE = MOTION_TRIGGER_FEATURE;
|
|
56201
56488
|
exports.ManagedModelCatalogEntrySchema = ManagedModelCatalogEntrySchema;
|
|
56202
56489
|
exports.ManagedModelExtraFileSchema = ManagedModelExtraFileSchema;
|
|
@@ -56968,6 +57255,7 @@ exports.floodCapability = floodCapability;
|
|
|
56968
57255
|
exports.foldSnapshotByFunction = foldSnapshotByFunction;
|
|
56969
57256
|
exports.formatForBackend = formatForBackend;
|
|
56970
57257
|
exports.formatForRuntime = formatForRuntime;
|
|
57258
|
+
exports.formatMediaOwnerKey = formatMediaOwnerKey;
|
|
56971
57259
|
exports.gasCapability = gasCapability;
|
|
56972
57260
|
exports.gbToBytes = gbToBytes;
|
|
56973
57261
|
exports.generateAutomationBlock = generateAutomationBlock;
|
|
@@ -57003,6 +57291,7 @@ exports.isDetectionMacroClass = isDetectionMacroClass;
|
|
|
57003
57291
|
exports.isDeviceConfigCap = require_sleep.isDeviceConfigCap;
|
|
57004
57292
|
exports.isDeviceScopedCap = require_sleep.isDeviceScopedCap;
|
|
57005
57293
|
exports.isEvent = require_sleep.isEvent;
|
|
57294
|
+
exports.isEventOwnerType = isEventOwnerType;
|
|
57006
57295
|
exports.isFirstLevelMacroClass = isFirstLevelMacroClass;
|
|
57007
57296
|
exports.isIsolatedBuiltin = isIsolatedBuiltin;
|
|
57008
57297
|
exports.isLocationEnabled = isLocationEnabled;
|
|
@@ -57090,6 +57379,7 @@ exports.parseExpression = parseExpression;
|
|
|
57090
57379
|
exports.parseJsonArray = require_sleep.parseJsonArray;
|
|
57091
57380
|
exports.parseJsonObject = require_sleep.parseJsonObject;
|
|
57092
57381
|
exports.parseJsonUnknown = require_sleep.parseJsonUnknown;
|
|
57382
|
+
exports.parseMediaOwnerKey = parseMediaOwnerKey;
|
|
57093
57383
|
exports.parseProcStatus = parseProcStatus;
|
|
57094
57384
|
exports.parseProfileBrokerId = require_sleep.parseProfileBrokerId;
|
|
57095
57385
|
exports.parseRuleSection = parseRuleSection;
|