@camstack/addon-provider-homematic 1.2.116 → 1.2.117

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/addon.js CHANGED
@@ -5369,7 +5369,7 @@ var ZodIssueCode = {
5369
5369
  var ZodFirstPartyTypeKind;
5370
5370
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5371
5371
  //#endregion
5372
- //#region ../types/dist/sleep-GU_us3DG.mjs
5372
+ //#region ../types/dist/sleep-i3eUVc-d.mjs
5373
5373
  /**
5374
5374
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5375
5375
  * window to float samples (D455).
@@ -6511,6 +6511,17 @@ var CamProfileSchema = _enum([
6511
6511
  "mid",
6512
6512
  "low"
6513
6513
  ]);
6514
+ /**
6515
+ * Which rung a per-camera consumer listens on: an explicit profile, or `auto`.
6516
+ *
6517
+ * `auto` is a REAL value, not an absent one — it means "resolve it by the rule
6518
+ * that consumer owns". It lives beside `CamProfile` because a rung is the only
6519
+ * operator-facing way to name a stream: a cam-stream id is broker-internal and
6520
+ * moves when a slot is reassigned, so a stored id silently stops meaning what
6521
+ * the operator picked. Today's one user is the audio analyser, whose rule is
6522
+ * "the lowest AVAILABLE native rung" (D537).
6523
+ */
6524
+ var AudioStreamPickSchema = union([CamProfileSchema, literal("auto")]);
6514
6525
  var CamStreamKindSchema = _enum([
6515
6526
  "pull-rtsp",
6516
6527
  "pull-rtmp",
@@ -6929,6 +6940,18 @@ function systemMethod(input, output, options) {
6929
6940
  systemOnly: true
6930
6941
  };
6931
6942
  }
6943
+ /**
6944
+ * A method a SOURCE of a collection cap may legitimately not serve — OPTIONAL
6945
+ * on `InferProvider`. The `providerOptional: true` literal is what
6946
+ * `InferProvider` keys on; see {@link CapabilityMethodSchema.providerOptional}
6947
+ * for when this is the honest answer and when it is a soft stub.
6948
+ */
6949
+ function optionalMethod(input, output, options) {
6950
+ return {
6951
+ ...method(input, output, options),
6952
+ providerOptional: true
6953
+ };
6954
+ }
6932
6955
  /** Shorthand to define an event schema */
6933
6956
  function event(data) {
6934
6957
  return { data };
@@ -7359,6 +7382,8 @@ var EVENT_OWNER_TYPES = [
7359
7382
  * nothing failing until a caller asked.
7360
7383
  */
7361
7384
  var EventOwnerTypeSchema = _enum(EVENT_OWNER_TYPES);
7385
+ /** The same list as a Zod enum, for the cap input that carries it. */
7386
+ var MediaPresenceOwnerKindSchema = _enum([...EVENT_OWNER_TYPES, "track"]);
7362
7387
  new Set(EVENT_OWNER_TYPES);
7363
7388
  var EncodeProfileSchema = object({
7364
7389
  video: object({
@@ -7731,6 +7756,7 @@ var CameraSwitchIdSchema = _enum([
7731
7756
  "device-audio",
7732
7757
  "broker-audio",
7733
7758
  "audio-analysis",
7759
+ "event-saving",
7734
7760
  "recording",
7735
7761
  "notifications"
7736
7762
  ]);
@@ -7766,10 +7792,20 @@ var CameraSwitchAuthoritySchema = discriminatedUnion("kind", [
7766
7792
  var CameraSwitchUnavailableReasonSchema = _enum([
7767
7793
  "no-provider",
7768
7794
  "source-unreachable",
7769
- "not-configured",
7770
7795
  "device-disabled"
7771
7796
  ]);
7772
7797
  /**
7798
+ * Something true about a switch that is nonetheless OFFERED.
7799
+ *
7800
+ * The distinction from {@link CameraSwitchUnavailableReasonSchema} is the
7801
+ * whole point: a reason replaces the control, a warning travels WITH it. The
7802
+ * privacy mask taught us the difference — greying the one button that starts
7803
+ * the feature teaches the operator the feature is broken, while a live toggle
7804
+ * that flips nothing teaches them the switch is broken. Neither is true; the
7805
+ * honest surface is a working toggle that says what is missing.
7806
+ */
7807
+ var CameraSwitchWarningSchema = _enum(["no-privacy-zones"]);
7808
+ /**
7773
7809
  * One switch, resolved for one camera.
7774
7810
  *
7775
7811
  * `label` and `costWhenOff` travel ON THE WIRE rather than being looked up
@@ -7788,6 +7824,11 @@ var CameraSwitchSchema = object({
7788
7824
  /** False = do not render a control. `unavailableReason` says why. */
7789
7825
  available: boolean(),
7790
7826
  unavailableReason: CameraSwitchUnavailableReasonSchema.optional(),
7827
+ /**
7828
+ * Present on an AVAILABLE switch only. Rendered beside the live control;
7829
+ * never a substitute for one.
7830
+ */
7831
+ warning: CameraSwitchWarningSchema.optional(),
7791
7832
  /** Current state. Meaningless when `available` is false — read it as `true`. */
7792
7833
  enabled: boolean(),
7793
7834
  authority: CameraSwitchAuthoritySchema
@@ -8205,11 +8246,31 @@ var RecordingBandTriggersSchema = RecordingTriggersSchema;
8205
8246
  * band engine's `applies` rule). `start`/`end` are `HH:MM`; an `end <= start`
8206
8247
  * span wraps past midnight (handled by the band engine).
8207
8248
  */
8249
+ /**
8250
+ * Whether a band's footage carries sound.
8251
+ *
8252
+ * The recorder has always attached with `audio: 'aac'`, so every band of every
8253
+ * camera recorded sound whether anyone wanted it or not. The broker already
8254
+ * serves the other answer — `audio: 'none'` takes the video-only `/muted`
8255
+ * restream — so this was a choice the product could always have offered.
8256
+ *
8257
+ * It belongs on the BAND rather than the camera because one camera can want
8258
+ * both: silent across the shop floor during opening hours, sound overnight.
8259
+ * And it applies to BOTH modes — an events band firing on motion has the same
8260
+ * question to answer as a continuous one.
8261
+ */
8262
+ var RecordingBandAudioSchema = _enum(["on", "muted"]);
8208
8263
  var RecordingBandSchema = object({
8209
8264
  days: array(RecordingWeekdaySchema),
8210
8265
  start: string().regex(HHMM),
8211
8266
  end: string().regex(HHMM),
8212
8267
  mode: RecordingBandModeSchema,
8268
+ /**
8269
+ * Sound, or silence. OPTIONAL and absent-means-`on`: every recording
8270
+ * written before this existed carried sound, and a band whose key is
8271
+ * missing must keep meaning what it meant. See {@link RecordingBandAudioSchema}.
8272
+ */
8273
+ audio: RecordingBandAudioSchema.optional(),
8213
8274
  triggers: RecordingBandTriggersSchema.optional(),
8214
8275
  /**
8215
8276
  * RETIRED (D381). A band no longer carries a pre-buffer of its own.
@@ -13559,6 +13620,210 @@ var deviceAdoptionCapability = {
13559
13620
  }
13560
13621
  };
13561
13622
  /**
13623
+ * device-admin-link — "this device has a management page of its own, and here
13624
+ * is its address".
13625
+ *
13626
+ * ## Why this is not a `deviceConfig` cap
13627
+ *
13628
+ * There is nothing to edit. A `deviceConfig` cap (D14) exists so the framework
13629
+ * can DERIVE a settings form from `getOptions` + `getStatus` and route a flat
13630
+ * patch back through a setter; it costs a `builderId` reducer in
13631
+ * `device-config-contribution.ts` and a `*-config-schema.ts` beside it, and it
13632
+ * renders a form section. This cap answers ONE question with ONE read and
13633
+ * renders a button. Nothing about it is a form, so it carries no `deviceConfig`
13634
+ * block, no `settings`, no `runtimeState` and no reducer — exactly like
13635
+ * `reboot`, the other pure-RPC device-native cap.
13636
+ *
13637
+ * ## Absent, and the difference between "no page" and "we cannot say"
13638
+ *
13639
+ * The two are answered at DIFFERENT layers, on purpose:
13640
+ *
13641
+ * - **"We cannot say"** → the provider never registers the cap for that
13642
+ * device. A VeSync humidifier, a Petkit feeder, a Dreame vacuum and a Dreo
13643
+ * fan are reached only through a vendor cloud; there is no address to hand
13644
+ * out and no page to open. A Tuya plug, a Wyze camera and a Gree air
13645
+ * conditioner DO have a LAN IP, and still have no HTTP management page
13646
+ * behind it. None of them register, so `deviceManager.getBindings` never
13647
+ * lists the cap and no surface asks.
13648
+ * - **"This device has no page, and I know that"** → the provider registers
13649
+ * and `getAdminLink` returns `null`. This is the answer for a device whose
13650
+ * sibling DOES have a page: a Reolink battery camera reached over UDP by
13651
+ * `uid` with a blank `host`, an Ecowitt gateway configured in `listener`
13652
+ * transport, a Home Assistant broker authenticated by supervisor token
13653
+ * (which carries no `baseUrl` at all).
13654
+ *
13655
+ * Both draw NOTHING. A button that opens a browser error is worse than no
13656
+ * button, and D62 is the same rule from the other side: an off switch is
13657
+ * reported off, never made to look broken. There is no third state where the
13658
+ * UI renders a disabled button "because the device might have a page".
13659
+ *
13660
+ * ## The URL never carries credentials
13661
+ *
13662
+ * Not in userinfo, not in a query string. Every provider builds through
13663
+ * `buildDeviceAdminUrl` (`device-admin-link-url.ts`), which takes host, port,
13664
+ * scheme and path as separate arguments — there is no parameter a secret could
13665
+ * arrive in — and re-checks its own output for the `scheme://user:pass@` shape
13666
+ * that `scripts/check-no-credential-urls-in-fixtures.ts` bans from recorded
13667
+ * output. `scripts/check-admin-link-builder-is-the-only-url-source.ts` is what
13668
+ * keeps providers from hand-rolling one anyway.
13669
+ *
13670
+ * This matters here more than anywhere else in the repo, because every provider
13671
+ * that knows a device's host knows its PASSWORD too: `{ host, port, username,
13672
+ * password }` sit in one object on Hikvision, Amcrest, Reolink and ONVIF alike,
13673
+ * and `http://admin:hunter2@192.168.50.139/` is a URL a browser accepts. The
13674
+ * camera's own page will ask for its own login. That is correct, and pre-
13675
+ * filling it is the operator's business, not ours.
13676
+ *
13677
+ * ## It is a LAN fact
13678
+ *
13679
+ * The URL addresses the device where the NODE can see it. It is not proxied,
13680
+ * not made reachable from outside, and not sent anywhere. A surface renders it
13681
+ * as a link the operator's own browser follows, on the operator's own network,
13682
+ * or renders nothing.
13683
+ */
13684
+ /**
13685
+ * Whose page is it. The distinction is for the OPERATOR, who needs to know
13686
+ * before clicking whether he is about to land on a camera's own web server or
13687
+ * inside Home Assistant.
13688
+ */
13689
+ var AdminLinkTargetEnum = _enum(["device", "integration"]);
13690
+ var DeviceAdminLinkSchema = object({
13691
+ /**
13692
+ * Absolute `http(s)://` URL. Built by `buildDeviceAdminUrl` and therefore
13693
+ * free of userinfo and of any credential-shaped query key.
13694
+ */
13695
+ url: string(),
13696
+ /**
13697
+ * What the surface calls it — "Web UI", "Home Assistant", "UniFi controller".
13698
+ * The PROVIDER names it, because only the provider knows what the page is;
13699
+ * a UI that invented the label from the addon id would call the Home
13700
+ * Assistant device page "Provider Homeassistant".
13701
+ */
13702
+ label: string(),
13703
+ target: AdminLinkTargetEnum,
13704
+ /**
13705
+ * Host the URL points at, without scheme, port or path — for the tooltip, so
13706
+ * an operator can see WHERE the button goes before he follows it. Redundant
13707
+ * with `url` by construction; carried separately so no surface has to parse
13708
+ * a URL to show it.
13709
+ */
13710
+ host: string()
13711
+ });
13712
+ var deviceAdminLinkCapability = {
13713
+ name: "device-admin-link",
13714
+ scope: "device",
13715
+ deviceNative: true,
13716
+ mode: "singleton",
13717
+ methods: {
13718
+ /**
13719
+ * The device's management page, or `null` when this device has none.
13720
+ *
13721
+ * `auth: 'admin'` deliberately. This is administration, not actuation —
13722
+ * the same bucket as `reboot` and `camera-credentials`, and explicitly NOT
13723
+ * the actuation set `scripts/check-actuation-not-admin.ts` protects (D403).
13724
+ * The URL is also a statement about the LAN, which a household member with
13725
+ * a `view` grant on a light has no reason to be handed.
13726
+ *
13727
+ * The surfaces gate on the QUERY, never on a role they guessed: a caller
13728
+ * without the right loses the query and draws nothing, which is the same
13729
+ * thing a device with no page draws. There is no path on which a button
13730
+ * appears and then fails — the D403 failure mode, from the other end.
13731
+ */
13732
+ getAdminLink: method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema.nullable(), { auth: "admin" }) }
13733
+ };
13734
+ /**
13735
+ * Query keys that may never appear on an admin link. A credential smuggled as
13736
+ * `?password=` is the same leak as userinfo, in a form the userinfo check
13737
+ * cannot see: it survives copy-paste, referrer headers and proxy logs
13738
+ * identically.
13739
+ */
13740
+ var CREDENTIAL_QUERY_KEYS = [
13741
+ "password",
13742
+ "passwd",
13743
+ "pwd",
13744
+ "pass",
13745
+ "user",
13746
+ "username",
13747
+ "usr",
13748
+ "login",
13749
+ "token",
13750
+ "access_token",
13751
+ "auth",
13752
+ "authorization",
13753
+ "apikey",
13754
+ "api_key",
13755
+ "secret",
13756
+ "credential",
13757
+ "credentials",
13758
+ "session",
13759
+ "sessionid",
13760
+ "key"
13761
+ ];
13762
+ /**
13763
+ * The same userinfo shape `scripts/check-no-credential-urls-in-fixtures.ts`
13764
+ * scans recorded fixtures for, applied to what we are about to EMIT. Kept
13765
+ * structurally identical on purpose: a URL this function returns is a URL that
13766
+ * guard would pass.
13767
+ */
13768
+ var CREDENTIAL_URL = /[a-z][a-z0-9+.-]*:\/\/[^/\s:@'"]+:[^/\s:@'"]+@/i;
13769
+ /** A host that is safe to place in an authority component verbatim. */
13770
+ var PLAIN_HOST = /^[a-zA-Z0-9._-]+$/;
13771
+ /** A bracketed IPv6 literal, the only other authority form we emit. */
13772
+ var IPV6_HOST = /^\[[0-9a-fA-F:.]+\]$/;
13773
+ function defaultPortFor(scheme) {
13774
+ return scheme === "https" ? 443 : 80;
13775
+ }
13776
+ /**
13777
+ * Build the admin-page URL, or refuse by name.
13778
+ *
13779
+ * The refusal is never thrown: a provider answering "no page for this device"
13780
+ * is an ordinary answer (`getAdminLink` → `null`), and a throw here would turn
13781
+ * a missing host on one device into a failed query for the whole surface.
13782
+ */
13783
+ function buildDeviceAdminUrl(parts) {
13784
+ const host = parts.host.trim();
13785
+ if (host.length === 0) return {
13786
+ ok: false,
13787
+ reason: "empty-host"
13788
+ };
13789
+ if (host.includes("@")) return {
13790
+ ok: false,
13791
+ reason: "host-carries-userinfo"
13792
+ };
13793
+ if (!PLAIN_HOST.test(host) && !IPV6_HOST.test(host)) return {
13794
+ ok: false,
13795
+ reason: "host-not-plain"
13796
+ };
13797
+ if (parts.port !== null) {
13798
+ if (!Number.isInteger(parts.port) || parts.port < 1 || parts.port > 65535) return {
13799
+ ok: false,
13800
+ reason: "port-out-of-range"
13801
+ };
13802
+ }
13803
+ if (!parts.path.startsWith("/")) return {
13804
+ ok: false,
13805
+ reason: "path-not-absolute"
13806
+ };
13807
+ const query = parts.query ?? {};
13808
+ for (const key of Object.keys(query)) if (CREDENTIAL_QUERY_KEYS.includes(key.toLowerCase())) return {
13809
+ ok: false,
13810
+ reason: "credential-query-key"
13811
+ };
13812
+ const authority = parts.port === null || parts.port === defaultPortFor(parts.scheme) ? host : `${host}:${String(parts.port)}`;
13813
+ const search = new URLSearchParams();
13814
+ for (const [key, value] of Object.entries(query)) search.append(key, value);
13815
+ const suffix = search.size > 0 ? `?${search.toString()}` : "";
13816
+ const url = `${parts.scheme}://${authority}${parts.path}${suffix}`;
13817
+ if (CREDENTIAL_URL.test(url)) return {
13818
+ ok: false,
13819
+ reason: "userinfo-in-result"
13820
+ };
13821
+ return {
13822
+ ok: true,
13823
+ url
13824
+ };
13825
+ }
13826
+ /**
13562
13827
  * `device-export` — collection cap for addons that export camstack
13563
13828
  * devices to external ecosystems (HomeAssistant via MQTT discovery,
13564
13829
  * HomeKit/HAP, Alexa Smart Home, …).
@@ -20325,6 +20590,7 @@ var MediaFileKindEnum = _enum([
20325
20590
  "keyFrameSmall",
20326
20591
  "thumbnailSmall",
20327
20592
  "confirmationCrop",
20593
+ "debugCrop",
20328
20594
  "sceneFrame"
20329
20595
  ]);
20330
20596
  /**
@@ -21404,7 +21670,11 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21404
21670
  ownerType: EventOwnerTypeSchema,
21405
21671
  eventId: number().int(),
21406
21672
  deviceId: number()
21407
- }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
21673
+ }), array(MediaFileInfoSchema).readonly()), method(object({
21674
+ deviceId: number(),
21675
+ ownerKind: MediaPresenceOwnerKindSchema,
21676
+ ownerIds: array(string()).max(5e3)
21677
+ }), array(string()).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
21408
21678
  kind: "mutation",
21409
21679
  auth: "admin"
21410
21680
  }), method(RebuildObjectEmbeddingsInput, RebuildObjectEmbeddingsResultSchema, {
@@ -21989,6 +22259,18 @@ var RunnerCameraConfigSchema = object({
21989
22259
  motionStreamId: string(),
21990
22260
  detectionStreamId: string(),
21991
22261
  /**
22262
+ * Which rung this camera's audio analysis listens on — `auto` (the lowest
22263
+ * AVAILABLE native rung) or an explicit one.
22264
+ *
22265
+ * Not consumed by the runner: carried here so it travels with the camera's
22266
+ * config the way `audioMotionWindowMs` does, and so the orchestrator's
22267
+ * watchdog resolves the same stream the audio subscription did. OPTIONAL
22268
+ * with no default — absent means `auto`, which is what every camera written
22269
+ * before this field had, and an absent optional adds nothing to the
22270
+ * generated cap-input defaults (D537).
22271
+ */
22272
+ audioStreamPick: AudioStreamPickSchema.optional(),
22273
+ /**
21992
22274
  * Motion sources that can activate the pipeline. At least one must
21993
22275
  * be present. Multiple sources OR-ed: any one firing arms the
21994
22276
  * cooldown timer and transitions watching → active. The same
@@ -23059,10 +23341,9 @@ var ServerBootModeSchema = _enum([
23059
23341
  * - `idle` / `checking` / `staging` — steady / in-flight registry work.
23060
23342
  * - `pending-restart` — a version is staged and the node has NOT yet
23061
23343
  * restarted onto it (still running the OLD version).
23062
- * - `awaiting-confirmation` — the node HAS restarted onto the staged version
23063
- * (it is the active probation boot) and is waiting to confirm boot-health.
23064
- * Apply/rollback are refused in this state and the node must NOT be
23065
- * manually restarted, or the probation boot auto-rolls-back.
23344
+ * - `awaiting-confirmation` — RETIRED with the probation boot (single-copy
23345
+ * collapse, 2026-07-18). Kept in the enum so an older client still parses;
23346
+ * no provider emits it.
23066
23347
  */
23067
23348
  var ServerUpdateStateSchema = _enum([
23068
23349
  "idle",
@@ -23151,12 +23432,35 @@ var ServerRollbackInfoSchema = object({
23151
23432
  var ServerPackageStatusSchema = object({
23152
23433
  /** Root package name (`@camstack/server` on the hub). */
23153
23434
  packageName: string(),
23154
- /** Version of the code the running process ACTUALLY loaded. */
23435
+ /**
23436
+ * Version of the code THIS PROCESS loaded — captured once, at boot, from the
23437
+ * package.json of the copy the code was loaded from, and never re-read. A
23438
+ * copy replaced on disk under a live process must not move this number;
23439
+ * that divergence is what `installedVersion` is for. Before 2026-09-20 this
23440
+ * was a lazy disk read that would have followed such a swap silently.
23441
+ */
23155
23442
  runningVersion: string().nullable(),
23156
23443
  /** Node.js runtime version the node's process runs on (`process.versions.node`). */
23157
23444
  nodeRuntimeVersion: string().nullable(),
23158
23445
  /** Active data-dir root version; null when booted from seed/workspace. */
23159
23446
  activeVersion: string().nullable(),
23447
+ /**
23448
+ * Version of the root package in the data-root `current/` copy RIGHT NOW —
23449
+ * what the next boot would load if nothing is staged. Normally equal to
23450
+ * `runningVersion`; differs only when the copy on disk changed under the
23451
+ * live process. `null` when there is no data-root copy (baked seed or
23452
+ * workspace boot). Optional for version skew: an older provider omits it.
23453
+ */
23454
+ installedVersion: string().nullable().optional(),
23455
+ /**
23456
+ * True when the code that would run after a restart is NOT the code running
23457
+ * now: a version is staged (`pendingVersion`), or `installedVersion` differs
23458
+ * from `runningVersion`. Derived on every read, never stored. This is the
23459
+ * one field that lets an operator or a script say "1.2.353 is installed and
23460
+ * will run after a restart" instead of reading it off a number that means
23461
+ * "installed". Optional for version skew.
23462
+ */
23463
+ restartRequired: boolean().optional(),
23160
23464
  /** N-1 version kept for rollback; null when no previous version exists. */
23161
23465
  previousVersion: string().nullable(),
23162
23466
  /** Version of the immutable baked seed closure (image fallback). */
@@ -23191,19 +23495,71 @@ var ServerPackageStatusSchema = object({
23191
23495
  });
23192
23496
  var ServerUpdateCheckResultSchema = object({
23193
23497
  packageName: string(),
23498
+ /** Same meaning as on the status payload: what THIS process loaded. */
23194
23499
  runningVersion: string().nullable(),
23195
23500
  latestVersion: string().nullable(),
23501
+ /**
23502
+ * `latestVersion` is newer than `runningVersion` AND is not the version
23503
+ * already staged on this node. A staged-and-waiting version is not "an
23504
+ * update available" — applying it again is refused (`already-staged`), and
23505
+ * a button that announced "updating…" on this boolean then got refused
23506
+ * would be lying. It is reported through `pendingVersion` instead.
23507
+ */
23196
23508
  updateAvailable: boolean(),
23197
23509
  checkedAtMs: number(),
23198
23510
  /** Non-null when the registry lookup failed (offline, bad registry, …). */
23199
- error: string().nullable()
23511
+ error: string().nullable(),
23512
+ /**
23513
+ * Version staged on this node and awaiting its restart, when one is. Before
23514
+ * 2026-09-20 a check answered `{runningVersion, updateAvailable:false}` for a
23515
+ * node whose staged closure had never booted, and the caller had no way to
23516
+ * see the difference. Optional for version skew.
23517
+ */
23518
+ pendingVersion: string().nullable().optional(),
23519
+ /** See the status payload: staged, or installed-on-disk ≠ running. Optional for skew. */
23520
+ restartRequired: boolean().optional()
23200
23521
  });
23522
+ /**
23523
+ * What an action DID — the discriminant `accepted` cannot carry. Before
23524
+ * 2026-09-20 `applyServerUpdate` answered `accepted:false` both for a refusal
23525
+ * where nothing was wrong (already current, already staged) and for a staging
23526
+ * install that died on `npm error code ECONNRESET`; the only difference was
23527
+ * prose in `message`, and an operator restarted a container on the strength of
23528
+ * it, booting the previous version.
23529
+ *
23530
+ * - `staged` — the closure is installed and validated; the node
23531
+ * restarts to apply it (`restarting:true`). NOTHING
23532
+ * runs the new code until that restart completes.
23533
+ * - `restart-scheduled` — a plain, version-preserving restart was accepted.
23534
+ * - `already-current` — the target IS the running version; nothing done.
23535
+ * - `refused` — dropped by a gate; `reason` names which.
23536
+ * - `failed` — attempted and did not land; `error` says why.
23537
+ */
23538
+ var ServerUpdateOutcomeSchema = _enum([
23539
+ "staged",
23540
+ "restart-scheduled",
23541
+ "already-current",
23542
+ "refused",
23543
+ "failed"
23544
+ ]);
23201
23545
  var ServerUpdateActionResultSchema = object({
23202
23546
  accepted: boolean(),
23203
23547
  targetVersion: string().nullable(),
23204
23548
  /** True when a graceful restart was scheduled to apply the change. */
23205
23549
  restarting: boolean(),
23206
- message: string()
23550
+ message: string(),
23551
+ /** See {@link ServerUpdateOutcomeSchema}. Optional for version skew. */
23552
+ outcome: ServerUpdateOutcomeSchema.optional(),
23553
+ /**
23554
+ * Stable, greppable cause for `refused` / `failed` (the engine's
23555
+ * `UpdateRefusalReason`, `staging-failed`, …). `null` on success.
23556
+ */
23557
+ reason: string().nullable().optional(),
23558
+ /**
23559
+ * The failure itself, verbatim enough to act on (the npm output for a
23560
+ * staging install). Non-null only for `failed`.
23561
+ */
23562
+ error: string().nullable().optional()
23207
23563
  });
23208
23564
  method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), ServerUpdateCheckResultSchema, {
23209
23565
  kind: "mutation",
@@ -24312,6 +24668,18 @@ method(VectorDeclareIndexInputSchema, _void(), {
24312
24668
  kind: "mutation",
24313
24669
  auth: "admin"
24314
24670
  }), method(VectorStatsInputSchema, VectorStatsResultSchema, { auth: "admin" });
24671
+ _enum([
24672
+ "queue-full",
24673
+ "camera-backoff",
24674
+ "sleeping",
24675
+ "camera-refused",
24676
+ "no-keyframe",
24677
+ "no-catalog-row",
24678
+ "unsupported",
24679
+ "unknown-device",
24680
+ "uid-missing"
24681
+ ]);
24682
+ _enum(["deferred", "final"]);
24315
24683
  var ClipSchema = object({
24316
24684
  /** Opaque, provider-namespaced id. The default provider encodes the time
24317
24685
  * window so `getClipPlayback` is self-contained (no event re-query). */
@@ -24386,29 +24754,455 @@ var ClipSchema = object({
24386
24754
  holes: array(object({
24387
24755
  startMs: number(),
24388
24756
  endMs: number()
24389
- })).optional()
24757
+ })).optional(),
24758
+ /**
24759
+ * A URL that MAY produce this clip's still, asked only for rows actually on
24760
+ * screen. The opposite of {@link ClipSchema.thumbnail}: that one is VOUCHED
24761
+ * (a JPEG is already on disk), this one is an offer. The route answers 200
24762
+ * with the image, or 204 with `x-camstack-reason` when it could not mint one
24763
+ * — a surface latches that refusal to the instant rather than retrying.
24764
+ *
24765
+ * Never both: a clip with a vouched `thumbnail` needs no mint.
24766
+ */
24767
+ thumbnailMint: string().optional(),
24768
+ /**
24769
+ * Why no still will be produced for this clip RIGHT NOW — set when the
24770
+ * provider already knows, so the surface draws the glyph and the reason
24771
+ * instead of firing a mint that cannot succeed.
24772
+ *
24773
+ * `sleeping` — a standalone battery camera; a read would be a wake (D549 4).
24774
+ * `camera-refused` — the camera answered the CoverPreview with a refusal.
24775
+ * `no-keyframe` — the window holds no decodable I-frame (an event that sits
24776
+ * inside no file is the measured case).
24777
+ * `unsupported` — this source cannot mint stills at all.
24778
+ */
24779
+ thumbnailUnavailable: object({ reason: _enum([
24780
+ "sleeping",
24781
+ "camera-refused",
24782
+ "no-keyframe",
24783
+ "unsupported"
24784
+ ]) }).optional(),
24785
+ /**
24786
+ * When the catalog this row came from was last CONFIRMED against the device.
24787
+ * Absent means "this row was read live". A persisted catalog served while a
24788
+ * camera sleeps carries the age it really has — a cached list is never drawn
24789
+ * as current (D549 13).
24790
+ */
24791
+ catalogAsOf: number().optional(),
24792
+ /**
24793
+ * The camera's OWN type strings for this clip, all of them, unmapped
24794
+ * (`md`, `people`, `dog_cat`, `sched`, …). Kept beside {@link labels}
24795
+ * because a firmware inventing a type must not vanish: the mapping into our
24796
+ * filter vocabulary is lossy on purpose and this is the lossless copy
24797
+ * (D549 20).
24798
+ */
24799
+ nativeTypes: array(string()).optional(),
24800
+ /**
24801
+ * What the subject DID — `crossline`, `intrude`, `loitering`. A behaviour
24802
+ * travels BESIDE a class, never instead of one, and the class filter ignores
24803
+ * it: "a person crossed a line" is still a person (D549 20).
24804
+ */
24805
+ behaviours: array(string()).optional(),
24806
+ /**
24807
+ * The same recording as two files — the sub twin (what the row and its
24808
+ * thumbnail are) and its main twin, matched at listing time so a quality
24809
+ * change never re-searches the camera (D549 15). `getClipPlayback`'s
24810
+ * `profile` picks between them: `low | mid` → sub, `high` → main.
24811
+ */
24812
+ streams: object({
24813
+ sub: object({
24814
+ id: string(),
24815
+ bytes: number().optional()
24816
+ }).optional(),
24817
+ main: object({
24818
+ id: string(),
24819
+ bytes: number().optional()
24820
+ }).optional()
24821
+ }).optional(),
24822
+ /** The camera says it holds a sub-stream copy of this recording. */
24823
+ supportSub: boolean().optional(),
24824
+ /**
24825
+ * Whether this row has bytes behind it. ABSENT means yes — every clip that
24826
+ * IS a file is playable, and only a source that lists EVENTS can produce a
24827
+ * row with nothing to play (the measured case: 1 of 29 hub events on 3628
24828
+ * fell inside no file). Such a row is shown, never dropped and never offered
24829
+ * as playable-then-failing (D549 19).
24830
+ */
24831
+ playable: boolean().optional(),
24832
+ /** Why {@link playable} is false, verbatim (`no-file-for-window`). */
24833
+ unplayableReason: string().optional(),
24834
+ /**
24835
+ * This clip is STILL BEING WRITTEN, so {@link ClipSchema.timeRange}`.endMs`
24836
+ * is not its end.
24837
+ *
24838
+ * Absent — the common case — means the row is closed and its `endMs` is the
24839
+ * end of the recording. Present and `true` means the source told us the file
24840
+ * has no end yet, and the `endMs` we carry is whatever the camera's index
24841
+ * entry happened to hold: on a Reolink E1 Outdoor PoE (592, measured
24842
+ * 2026-09-20) the newest file `…_192927_000000_…_0.mp4` reported an `endTime`
24843
+ * of 19:29:56 and STILL reported it eight minutes later, while the file went
24844
+ * on growing. A surface that drew 29 s there was lying about a clip that
24845
+ * plays for minutes, on the one row the operator looks at first.
24846
+ *
24847
+ * `endMs` is deliberately still a number: it is the best bound anything has
24848
+ * for a mint window or a byte fetch, and every consumer already requires it.
24849
+ * This flag says what it is WORTH, and a duration is not drawn from it.
24850
+ */
24851
+ inProgress: boolean().optional()
24390
24852
  });
24391
24853
  var ClipPlaybackSchema = object({
24392
- /** HLS master URL through the hub data-plane (Range + token in path). */
24854
+ /**
24855
+ * A media URL through the hub data-plane. {@link ClipPlaybackSchema.format}
24856
+ * says what kind — an HLS master playlist for a recording-derived clip, a
24857
+ * progressive MP4 for a native file the vendor addon muxed and serves with
24858
+ * `Range`. The URL is NOT a credential: both planes are registered
24859
+ * `access: 'authenticated'` and the hub's `/addon/<id>/<prefix>` proxy takes
24860
+ * the session cookie, so nothing is appended to it (D549 22).
24861
+ */
24393
24862
  playbackUrl: string(),
24863
+ /**
24864
+ * How to play {@link playbackUrl}. Absent means `hls` — the shape every
24865
+ * existing consumer already assumes. A player that branches on this is the
24866
+ * one change a native clip needs; a player that ignores it will hand an MP4
24867
+ * to hls.js and fail parsing it as a manifest.
24868
+ */
24869
+ format: _enum(["hls", "mp4"]).optional(),
24870
+ /**
24871
+ * Which twin was actually served. A clip that holds only one stream answers
24872
+ * with the one it has, and the surface SAYS so — a missing main twin is
24873
+ * never served silently as if it were the asked-for quality (D549 15).
24874
+ */
24875
+ served: CamProfileSchema.optional(),
24394
24876
  /** Optional LAN/remote alternates for the same clip. */
24395
24877
  playbackEndpoints: array(string()).optional(),
24396
24878
  token: string().optional()
24397
24879
  });
24880
+ /**
24881
+ * Why a source cannot answer right now — per SOURCE, never fleet-wide.
24882
+ *
24883
+ * `ok` is the only state whose clip list may be read as complete. The other
24884
+ * three exist because an empty list from a sleeping camera reads as "this
24885
+ * camera has no recordings", which is the defect this whole line of work is
24886
+ * about: a battery camera nobody woke (`sleeping`), a camera that could not be
24887
+ * reached at all (`unreachable`, also what a provider that THREW reports), a
24888
+ * camera whose SD card is not mounted (`no-storage`, measured on 640 —
24889
+ * `HddInfo mount 0`, honestly nothing to list rather than "no clips"), and a
24890
+ * camera whose OWN index disagrees with its OWN calendar (`index-empty`,
24891
+ * measured on 618: the calendar marks 3–20 September, days 6–20 list zero
24892
+ * files on both streams and both filters, with 177 GB free). That last one is
24893
+ * a defect ON THE CAMERA, and the only honest thing a provider can do is say
24894
+ * which days it asked for and got nothing — "no clips" would be a lie about a
24895
+ * card that is full of them.
24896
+ */
24897
+ /**
24898
+ * The operator's authorisation to WAKE a sleeping camera for one clip read.
24899
+ *
24900
+ * ONE value, absent by default, and it is a `force`-shaped operator signal in
24901
+ * exactly the sense `snapshot-wake-gate.ts` uses the word: *"`snapshot.getSnapshot`'s
24902
+ * `force` flag, and nothing else… a background caller must never set it…
24903
+ * Stale but honest beats woken"*. A scheduler, a retry, a reconcile and a
24904
+ * prefetch never set it; a surface sets it only behind the same confirm the
24905
+ * "Wake and refresh" gesture uses, and it is refused below 15 % battery
24906
+ * exactly as that gesture is.
24907
+ *
24908
+ * The gate is decided BEFORE `getApi()`, because on UDP the login IS the wake
24909
+ * (D549) — a read that opened a session and then checked would have woken the
24910
+ * camera to find out it was not allowed to.
24911
+ *
24912
+ * **One authorised yes is one wake.** `next-natural` — take the clip the next
24913
+ * time the camera is awake for its own reasons — is deliberately not a member:
24914
+ * it was proposed, it is free on the battery, and the operator declined it on
24915
+ * 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."*,
24916
+ * D558 "Considered and not taken").
24917
+ */
24918
+ var ClipWakeSchema = _enum(["authorised"]);
24919
+ /**
24920
+ * A clip's finished bytes, inline — the twin of `recordingExport.readExportBytes`.
24921
+ *
24922
+ * `bytes` is the DECODED length, so nobody infers it from the base64 length,
24923
+ * and `served` says which twin the caller actually got.
24924
+ */
24925
+ var ClipBytesSchema = object({
24926
+ base64: string(),
24927
+ contentType: string(),
24928
+ /** Suggested filename, extension included. */
24929
+ name: string(),
24930
+ bytes: number().int().nonnegative(),
24931
+ /**
24932
+ * Which twin was actually served — the same contract
24933
+ * {@link ClipPlaybackSchema.served} carries, and REQUIRED here because the
24934
+ * export record persists it: a row read a week later must say the same thing
24935
+ * the panel said at the moment of the tap. A missing main twin is never
24936
+ * served silently as if it were the asked-for quality (D549 15).
24937
+ */
24938
+ served: CamProfileSchema,
24939
+ /**
24940
+ * The DECODED duration of the delivered file, when the fetch measured one.
24941
+ *
24942
+ * A clip fetch verifies its own completion against the catalog row and
24943
+ * retries a materially short pass (D568); this is that measurement, carried
24944
+ * so a consumer can say the same thing rather than re-deriving it. Absent
24945
+ * when the producer did not measure — never zero, which would say the file
24946
+ * is empty.
24947
+ */
24948
+ durationMs: number().positive().optional()
24949
+ });
24950
+ /**
24951
+ * Where a clip's STREAM can be dialled (D597) — the answer to
24952
+ * {@link videoclipsCapability.methods.dialClipStream}.
24953
+ *
24954
+ * `url` is plain `http://` on the provider's own token-gated listener, one
24955
+ * shot, short-lived. It is NOT the hub-proxied `/addon/<id>/clip-stream/…`
24956
+ * path: that plane takes only a user credential, which an addon does not
24957
+ * hold, and the framework offers no addon→addon byte transport — the one
24958
+ * precedent is the broker's own restream URLs (`getStreamWithCodec`), which
24959
+ * a consumer dials directly, and this is that precedent one addon over.
24960
+ */
24961
+ /**
24962
+ * The audio a clip stream can be asked for, and the audio it carries.
24963
+ *
24964
+ * The camera stores AAC and no WebRTC session speaks it, so a stream that
24965
+ * carries sound carries it TRANSCODED, muxed as a second track beside the
24966
+ * video by the one ffmpeg the provider already runs (D600). `opus` is
24967
+ * 48 kHz mono, `pcmu` is 8 kHz µ-law — the two codecs a broker session
24968
+ * negotiates — and `none` is a stream asked for, or served, without sound.
24969
+ */
24970
+ var ClipStreamAudioSchema = _enum([
24971
+ "opus",
24972
+ "pcmu",
24973
+ "none"
24974
+ ]);
24975
+ /**
24976
+ * Why a stream that was ASKED for sound is not carrying it (D600).
24977
+ *
24978
+ * Only ever set beside `servedAudio: 'none'` after a non-`none` ask. Absent
24979
+ * means the ask was honoured, or there was no ask.
24980
+ *
24981
+ * - `not-requested` — the caller asked for `none`.
24982
+ * - `unsupported-ask` — the provider cannot produce that codec.
24983
+ *
24984
+ * Whether the RECORDING has sound at all is not knowable here: the camera
24985
+ * declares no audio track before its first frame arrives. That answer is in
24986
+ * the stream itself — an fMP4 with no audio track — and the broker reads it
24987
+ * there. A dial that says `opus` promises the codec, never the content.
24988
+ */
24989
+ var ClipStreamAudioReasonSchema = _enum(["not-requested", "unsupported-ask"]);
24990
+ var ClipStreamDialSchema = object({
24991
+ url: string().min(1),
24992
+ /** After this the token is gone and the URL answers 404 `dial-expired`. */
24993
+ expiresAtMs: number(),
24994
+ /** What the stream IS — a consumer that cannot honour it must not dial. */
24995
+ transport: literal("forward-only"),
24996
+ /**
24997
+ * Which twin the stream ACTUALLY carries — not what was asked for.
24998
+ *
24999
+ * The stream serves every profile (D599), and a recording whose main twin
25000
+ * does not exist is streamed as its sub copy, which this field reports as
25001
+ * `low`. That is the whole reason the dial no longer refuses `high`:
25002
+ * stating what was served is what makes accepting honest, where a silent
25003
+ * substitution would be the 33x lie D576 forbids. Stated rather than
25004
+ * assumed, the way {@link ClipPlaybackSchema.served} is (D549 15).
25005
+ */
25006
+ served: CamProfileSchema,
25007
+ /**
25008
+ * Which audio the stream will ACTUALLY carry — `served`'s companion for
25009
+ * sound, and the same reasoning (D599, D600).
25010
+ *
25011
+ * The broker asks for the codec its WebRTC session negotiated, and the
25012
+ * provider either muxes that codec or carries none. Stating it is what
25013
+ * lets the broker wire the matching sink before the first byte, and what
25014
+ * keeps a session from being handed a codec it cannot send — the audio
25015
+ * shape of the substitution `served` exists to prevent.
25016
+ *
25017
+ * It is a promise about the CODEC, not about the content: a recording with
25018
+ * no microphone track answers `opus` here and then arrives with no audio
25019
+ * track at all. That second answer is in the stream, and the broker reads
25020
+ * it from the `moov`.
25021
+ */
25022
+ servedAudio: ClipStreamAudioSchema,
25023
+ /** Why `servedAudio` is `none` although sound was asked for. */
25024
+ audioReason: ClipStreamAudioReasonSchema.optional()
25025
+ });
25026
+ var ClipSourceAvailabilitySchema = object({
25027
+ state: _enum([
25028
+ "ok",
25029
+ "sleeping",
25030
+ "unreachable",
25031
+ "no-storage",
25032
+ "index-empty"
25033
+ ]),
25034
+ /** Free text, shown verbatim. Names the camera's own refusal when there is one. */
25035
+ reason: string().optional(),
25036
+ /** When this source's catalog was last CONFIRMED. A cached list is never
25037
+ * drawn as current: the surface shows the age whenever it is older than the
25038
+ * refresh interval. */
25039
+ catalogAsOf: number().optional()
25040
+ });
25041
+ /**
25042
+ * One SOURCE of clips for a camera — a row of the picker, and the namespace
25043
+ * every clip id from it is prefixed with (`analytics`,
25044
+ * `native:reolink:onboard`, `hksv`, …).
25045
+ *
25046
+ * A provider lists the sources IT serves for that device, and answers for each
25047
+ * of them whether it can answer at all. A provider with nothing to offer on a
25048
+ * camera returns `[]` — it is not that camera's business.
25049
+ */
25050
+ var ClipSourceSchema = object({
25051
+ /** The value this source stamps on {@link ClipSchema.source}, and the prefix
25052
+ * of every clip id it mints. `getClipPlayback` routes on it. */
25053
+ source: string(),
25054
+ /** Operator-facing name of the source ("CamStack recordings", "SD card"). */
25055
+ label: string(),
25056
+ /**
25057
+ * The addon that SERVES this row.
25058
+ *
25059
+ * A surface cannot otherwise resolve a source to who answers it, and the
25060
+ * alternative — a `native:reolink:* → provider-reolink` table inside the
25061
+ * widget — is a second authority on provider identity living in the one
25062
+ * package with no business knowing it, wrong the day a third source appears
25063
+ * (D557). One addon may serve SEVERAL sources, which is why the per-device
25064
+ * switch is keyed by SOURCE and not by this (D555) — `addon-provider-reolink`
25065
+ * served a hub child two of them until the two views were measured to be one
25066
+ * store read twice (D555) and then read ONE way (D565).
25067
+ *
25068
+ * Optional for version skew only. The collection dispatcher stamps it from
25069
+ * the registry, so a row that travelled through the fan-out carries the
25070
+ * authoritative id whatever the provider filled in.
25071
+ */
25072
+ addonId: string().optional(),
25073
+ availability: ClipSourceAvailabilitySchema
25074
+ });
24398
25075
  DeviceType.Camera, method(object({
24399
25076
  deviceId: number(),
24400
25077
  since: number(),
24401
25078
  until: number(),
24402
- limit: number().int().positive().optional()
25079
+ limit: number().int().positive().optional(),
25080
+ /**
25081
+ * WHICH provider to ask — the `addonId` a {@link ClipSourceSchema} row
25082
+ * carries, never a source id and never a list. **Required** (D554 amended).
25083
+ *
25084
+ * A provider the device is not bound to is refused by name rather than
25085
+ * answered by another one (D552's `rejectUnresolvedAddonPin` rule).
25086
+ *
25087
+ * It was optional, documented as "absent means the device's BOUND
25088
+ * provider, which is CamStack on every camera". No code implemented
25089
+ * that. Measured on the live hub 2026-09-20 — device 592, bound to
25090
+ * `recorder` AND `provider-reolink` — a bare call with `limit: 3`
25091
+ * answered SIX rows, three from each source, merged newest-first:
25092
+ * `device-collection-dispatch.ts` simply left the fan-out un-narrowed,
25093
+ * so absence bought the union this method exists not to be, and
25094
+ * `limit` meant `limit × sources`.
25095
+ *
25096
+ * There is nothing to restore the default to. `getBindings` answers a
25097
+ * collection cap with a DERIVED, PLURAL set (D554 amended, step 0);
25098
+ * `setWrapperActive` — the singleton authority that could name one —
25099
+ * throws for a collection cap by design. Naming CamStack here instead
25100
+ * would privilege one addon by id inside a surface whose premise is
25101
+ * that sources are peers, and would be wrong on the first camera with
25102
+ * no recorder binding. So absence is REFUSED, in the schema, where the
25103
+ * generated types make it unomittable rather than merely discouraged.
25104
+ *
25105
+ * The default belongs to the SURFACE, which has the `listSources` rows
25106
+ * and can say which one it picked (D569 § 1–3; the viewer already
25107
+ * always sends this).
25108
+ *
25109
+ * It replaced `sources?: string[]`, a VIEW filter over source ids that
25110
+ * assumed the answer was a fan-out over everything a camera has. The
25111
+ * operator settled otherwise on 2026-09-20 — *"l'utilizzatore è uno
25112
+ * solo"* — so the list is asked of one provider at a time and there is
25113
+ * nothing to filter out of it.
25114
+ */
25115
+ provider: string().min(1)
24403
25116
  }), array(ClipSchema).readonly(), {
24404
25117
  kind: "query",
24405
- auth: "admin"
25118
+ auth: "protected"
25119
+ }), method(object({ deviceId: number() }), array(ClipSourceSchema).readonly(), {
25120
+ kind: "query",
25121
+ auth: "protected"
24406
25122
  }), method(object({
24407
25123
  deviceId: number(),
24408
- clipId: string()
25124
+ clipId: string(),
25125
+ /**
25126
+ * Which twin to serve, on the ONE quality scale the system already has
25127
+ * (`CamProfileSchema`). `low | mid` → the sub file, `high` → the main
25128
+ * twin; both ids are already on the row so this never re-searches the
25129
+ * camera. Absent means the provider's own default (the sub file, which
25130
+ * every source is measured to hold).
25131
+ *
25132
+ * `auto` is deliberately NOT accepted here: a stored file has no
25133
+ * broker session, so the adaptive tier cannot be resolved for it. The
25134
+ * viewer resolves `auto` to a profile the same way live does, before
25135
+ * it calls (D549 15).
25136
+ */
25137
+ profile: CamProfileSchema.optional()
24409
25138
  }), ClipPlaybackSchema, {
24410
25139
  kind: "query",
24411
- auth: "admin"
25140
+ auth: "protected"
25141
+ }), optionalMethod(object({
25142
+ deviceId: number(),
25143
+ clipId: string().min(1),
25144
+ /**
25145
+ * WHICH provider holds the bytes — the `addonId` a
25146
+ * {@link ClipSourceSchema} row carries. **Required**, for the reason
25147
+ * `listClips` states one method above and for a second one this method
25148
+ * learned the hard way.
25149
+ *
25150
+ * The caller is the stream broker, and the broker lives in
25151
+ * `addon-pipeline` — the same addon that registers the RECORDER's
25152
+ * videoclips provider. With no provider named, the in-process
25153
+ * resolution handed it its own sibling, which implements this method
25154
+ * solely to explain that an analytics visit has no file behind it. So
25155
+ * every onboard clip answered with that sentence, a flash of video and
25156
+ * nothing more, while the SAME call from outside the addon reached the
25157
+ * right provider and returned the bytes (measured on 3628,
25158
+ * 2026-09-21).
25159
+ */
25160
+ provider: string().min(1),
25161
+ /**
25162
+ * Which twin to fetch, on the mapping D549 15 already fixed:
25163
+ * `low | mid` → the sub file, `high` → the main twin. `auto` is not
25164
+ * accepted here for the same reason it is not accepted by
25165
+ * `getClipPlayback` — a stored file has no broker session, so the
25166
+ * adaptive tier cannot be resolved for it.
25167
+ */
25168
+ profile: CamProfileSchema.optional(),
25169
+ /**
25170
+ * The CALLER's byte bound, so an over-size clip is refused before it is
25171
+ * read and encoded rather than after. Capped by
25172
+ * {@link VIDEOCLIPS_MAX_READ_BYTES} whatever is passed; absent means
25173
+ * that ceiling.
25174
+ */
25175
+ maxBytes: number().int().positive().optional(),
25176
+ /**
25177
+ * The operator's authorisation to wake a sleeping camera for this
25178
+ * read. Absent — the default — means a sleeping standalone battery
25179
+ * camera is REFUSED by name, before any session is opened.
25180
+ */
25181
+ wake: ClipWakeSchema.optional()
25182
+ }), ClipBytesSchema, {
25183
+ kind: "query",
25184
+ auth: "protected"
25185
+ }), optionalMethod(object({
25186
+ deviceId: number(),
25187
+ clipId: string().min(1),
25188
+ /** WHICH provider holds the camera — see `readClipBytes.provider`. */
25189
+ provider: string().min(1),
25190
+ /** Which twin — `high` → the main file, everything else the sub (D599). */
25191
+ profile: CamProfileSchema.optional(),
25192
+ /**
25193
+ * Which audio codec the caller's session negotiated, so the provider
25194
+ * can mux it (D600). The ask travels on the DIAL because the mux is
25195
+ * spawned once, before the first byte, and cannot gain a track later.
25196
+ *
25197
+ * ABSENT IS `none`, never a guess: a caller that does not state an
25198
+ * ask is served the video-only stream that shipped first, so a broker
25199
+ * older than this field keeps working unchanged (D382 — an unstated
25200
+ * ask is zero).
25201
+ */
25202
+ audio: ClipStreamAudioSchema.optional()
25203
+ }), ClipStreamDialSchema, {
25204
+ kind: "query",
25205
+ auth: "protected"
24412
25206
  });
24413
25207
  /**
24414
25208
  * Optional client-side hints sent at session creation to help the provider
@@ -24480,6 +25274,104 @@ var WebrtcStreamChoiceSchema = object({
24480
25274
  /** Bitrate in kbps (null for adaptive). */
24481
25275
  bitrateKbps: number().nullable()
24482
25276
  });
25277
+ /**
25278
+ * The last move the adaptive ladder made on this session, and the numbers it
25279
+ * rested on. Mirrors `LadderDecision` in the stream-broker's
25280
+ * `adaptive-controller.ts` — the log line's evidence, made readable.
25281
+ *
25282
+ * Nullability is load-bearing: `lossRatio: null` means the loss tracker had
25283
+ * no window to report, which is NOT a link with zero loss.
25284
+ */
25285
+ var WebrtcLadderDecisionSchema = object({
25286
+ /** `loss` — RTCP / client-reported packet loss; `stall` — the picture
25287
+ * stopped moving; `healthy` — a promotion. */
25288
+ cause: _enum([
25289
+ "loss",
25290
+ "stall",
25291
+ "healthy"
25292
+ ]),
25293
+ /** The rung the ladder moved TO. Null for a move that changed no rung
25294
+ * (an MTU reduction, a forced transcode-down at the bottom). */
25295
+ tier: CamProfileSchema.nullable(),
25296
+ /** Whether that move forced the egress transcode-down rung. */
25297
+ forcedTranscodeDown: boolean(),
25298
+ /** Loss ratio over the tracker's window at the moment of the decision. */
25299
+ lossRatio: number().nullable(),
25300
+ /** The client's reported downlink at that moment, in kbps. */
25301
+ clientKbps: number().nullable(),
25302
+ /** How long the stall detector had been seeing nothing arrive. */
25303
+ stalledForMs: number().nullable(),
25304
+ /** Whether the broker WAS sending while that was measured — the half that
25305
+ * separates "the camera stopped" from "the link stopped". */
25306
+ serverSending: boolean().nullable(),
25307
+ /** Epoch ms. The overlay prints its age; a decision from 40 minutes ago
25308
+ * must not read like one from this second. */
25309
+ at: number()
25310
+ });
25311
+ /** Per-tier count of MEASURED downgrades this viewer session has suffered. */
25312
+ var WebrtcTierFailureSchema = object({
25313
+ tier: CamProfileSchema,
25314
+ failures: number().int().nonnegative()
25315
+ });
25316
+ /**
25317
+ * What the SERVER decided for one WebRTC session — the half the client cannot
25318
+ * measure. See `getSessionDebug`.
25319
+ */
25320
+ var WebrtcSessionDebugSchema = object({
25321
+ sessionId: string(),
25322
+ /** Whether the session still exists here. Everything below is null when
25323
+ * it does not — a dead session has no measurements, not zero ones. */
25324
+ alive: boolean(),
25325
+ /** Epoch ms this snapshot was taken, so the overlay can show its age. */
25326
+ observedAt: number(),
25327
+ /** The broker actually feeding this session (`<deviceId>/<camStreamId>`). */
25328
+ brokerId: string().nullable(),
25329
+ camStreamId: string().nullable(),
25330
+ /** `derived` = this hub BUILT the rung being served (a transcode/scale of
25331
+ * another stream); `native` = the camera's own encode. */
25332
+ sourceKind: _enum([
25333
+ "native",
25334
+ "derived",
25335
+ "unknown"
25336
+ ]),
25337
+ /** Whether the ladder is running at all (an `adaptive` target). */
25338
+ adaptive: boolean(),
25339
+ /** The rung `auto` ACTUALLY resolved to right now. Null when the broker
25340
+ * is bound to no profile slot (a manually-activated stream). */
25341
+ servedTier: CamProfileSchema.nullable(),
25342
+ /** Which node ingests the source. Null when unresolved. */
25343
+ ingestNodeId: string().nullable(),
25344
+ /** Whether that node is a remote one (the media crosses a node boundary
25345
+ * before it reaches this session). Null when unresolved. */
25346
+ ingestIsRemote: boolean().nullable(),
25347
+ /** Codec off the source, and the one negotiated with this client. */
25348
+ sourceCodec: string().nullable(),
25349
+ negotiatedCodec: string().nullable(),
25350
+ /** Whether the EGRESS leg re-encodes for this client or forwards the
25351
+ * source bytes untouched. */
25352
+ egressLeg: _enum([
25353
+ "copy",
25354
+ "transcode",
25355
+ "unknown"
25356
+ ]),
25357
+ /** What the broker itself reports about the rung it is serving. */
25358
+ brokerStatus: string().nullable(),
25359
+ brokerCodec: string().nullable(),
25360
+ brokerBitrateKbps: number().nullable(),
25361
+ brokerInputFps: number().nullable(),
25362
+ brokerUptimeMs: number().nullable(),
25363
+ /** RTP packets this session has sent — the server's own "am I sending?". */
25364
+ rtpPacketsSent: number().nullable(),
25365
+ /** D42: the track may be carrying the recorded feeder, not live. The ladder
25366
+ * is suspended there, so a frozen picture in `recorded` mode means
25367
+ * something entirely different. */
25368
+ trackMode: _enum(["live", "recorded"]).nullable(),
25369
+ lastDecision: WebrtcLadderDecisionSchema.nullable(),
25370
+ tierFailures: array(WebrtcTierFailureSchema),
25371
+ /** Successful promotions in this viewer session (drives the progressive
25372
+ * healthy timer). Null when no ladder is running. */
25373
+ upgradeCount: number().nullable()
25374
+ });
24483
25375
  DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), array(WebrtcStreamChoiceSchema)), method(object({
24484
25376
  deviceId: number().int().nonnegative(),
24485
25377
  target: WebrtcStreamTargetSchema,
@@ -24644,7 +25536,10 @@ DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), ar
24644
25536
  * the same answer as "it ended": either way nothing is holding it up.
24645
25537
  */
24646
25538
  alive: boolean()
24647
- }));
25539
+ })), method(object({
25540
+ deviceId: number().int().nonnegative(),
25541
+ sessionId: string()
25542
+ }), WebrtcSessionDebugSchema, { kind: "query" });
24648
25543
  object({
24649
25544
  /** All accessory children of the parent. */
24650
25545
  childDeviceIds: array(number()).readonly(),
@@ -26470,6 +27365,346 @@ var dayNightCapability = {
26470
27365
  volatileStateFields: ["lastFetchedAt"]
26471
27366
  };
26472
27367
  /**
27368
+ * Vendor-neutral **onboard** recording + storage cap — what the CAMERA
27369
+ * writes to the CAMERA's own card, on the camera's own schedule.
27370
+ *
27371
+ * This is NOT `recording.cap.ts`. That one is CamStack's recorder: our
27372
+ * footage ledger, our storage locations, our retention. This one has a
27373
+ * different authority — the camera's firmware — and per D62 it stores
27374
+ * nothing of its own. Every value here is read from the camera and every
27375
+ * write goes back to the camera; there is no CamStack-side mirror that
27376
+ * could disagree with the device.
27377
+ *
27378
+ * ## One shape, two firmwares
27379
+ *
27380
+ * Measured 2026-09-22 against the live fleet:
27381
+ *
27382
+ * | fact | Hikvision (ISAPI) | Reolink (Baichuan) |
27383
+ * | --- | --- | --- |
27384
+ * | storage | `ContentMgmt/Storage` `<hdd>` rows: status, capacity, freeSpace (MB) | `getHddInfoList` (cmd 102): `mount`, `format`, `capacity` GB + `capacityM` MB remainder |
27385
+ * | tracks | several (101 **and** 103 on both 1436 and 3833), each with its own schedule | one per channel |
27386
+ * | schedule | per track, 7 `ScheduleAction` blocks: DayOfWeek + TimeOfDay range + ONE `ActionRecordingMode` | per trigger type, a 168-char weekly HOUR mask |
27387
+ * | triggers | `CMR`, `MOTION` | `Normal`, `MD`, `people`, `vehicle`, `dog_cat`, `crossline`, `intrude`, `loitering` |
27388
+ * | pre-record | `PreRecordTimeSeconds` | `preRecordTime` |
27389
+ * | post-record | `PostRecordTimeSeconds` | `recordDelayTime` |
27390
+ * | overwrite | per track `LoopEnable` | `cycle`, with `cycleList` enumerating the accepted values |
27391
+ * | segment length | not exposed on V5.7.1 | `packageTime` (minutes) |
27392
+ *
27393
+ * The two schedule models look different and are the same thing in
27394
+ * different coordinates: both answer "for this trigger, during which
27395
+ * weekly windows does the camera record". {@link RecordWindow} is that
27396
+ * question in one shape — Hikvision's ranges map straight onto it,
27397
+ * Reolink's mask expands into hour-aligned windows.
27398
+ *
27399
+ * ## Union, not intersection
27400
+ *
27401
+ * **The same fields exist on every camera.** What differs per device is
27402
+ * which VALUES that device accepts, and that is what {@link
27403
+ * RecordingOnboardOptions} reports — a `{ readable, writable, reason }`
27404
+ * per field plus the schedule's own limits. A control a camera cannot
27405
+ * honour is rendered DISABLED WITH ITS REASON, never missing and never
27406
+ * dead: disabled must not look like broken.
27407
+ *
27408
+ * ## Refusal by name
27409
+ *
27410
+ * A write a camera cannot honour is refused with a sentence the operator
27411
+ * can read — never accepted and dropped. Both providers refuse through
27412
+ * {@link describeOnboardRefusal}, so the vocabulary is one function and
27413
+ * one test, not two hand-written vendor opinions.
27414
+ *
27415
+ * Follows the D14 `deviceConfig` archetype (see `stream-params.cap.ts`):
27416
+ * `getOptions` advertises per-camera availability, `getStatus` (auto-
27417
+ * injected from `status`) reports the live values, and a single
27418
+ * `setSettings` mutation applies a partial change. No hand-written
27419
+ * settings-contribution methods.
27420
+ */
27421
+ /**
27422
+ * What makes the camera start recording during a window.
27423
+ *
27424
+ * The union of both vendors' vocabularies. `continuous` is Hikvision's
27425
+ * `CMR` and Reolink's `Normal`; `motion` is `MOTION` / `MD`. The
27426
+ * object-class triggers are Reolink-only today and the smart-event ones
27427
+ * (`lineCrossing`, `intrusion`, `loitering`) are Reolink-only on the
27428
+ * firmwares measured — a camera that cannot record on a trigger simply
27429
+ * does not list it in `options.schedule.triggers`, and a window naming
27430
+ * it is REFUSED, not dropped.
27431
+ */
27432
+ var RecordTriggerSchema = _enum([
27433
+ "continuous",
27434
+ "motion",
27435
+ "person",
27436
+ "vehicle",
27437
+ "animal",
27438
+ "lineCrossing",
27439
+ "intrusion",
27440
+ "loitering",
27441
+ "alarmInput"
27442
+ ]);
27443
+ /**
27444
+ * One weekly recording window: "on `day`, from `startMinute` to
27445
+ * `endMinute`, record on `trigger`".
27446
+ *
27447
+ * `day` is 0 = Monday … 6 = Sunday (ISO order, which is also the order
27448
+ * both firmwares enumerate). Minutes are local camera time since
27449
+ * midnight; `endMinute` may be 1440, meaning end of day — that is
27450
+ * Hikvision's literal `24:00` and Reolink's 24th mask slot, and
27451
+ * collapsing it to 0 would turn a whole-day window into an empty one.
27452
+ */
27453
+ var RecordWindowSchema = object({
27454
+ trigger: RecordTriggerSchema,
27455
+ day: number().int().min(0).max(6),
27456
+ startMinute: number().int().min(0).max(1439),
27457
+ endMinute: number().int().min(1).max(1440)
27458
+ });
27459
+ /** Status of one physical volume, as the camera itself describes it. */
27460
+ var OnboardStorageVolumeSchema = object({
27461
+ /** The camera's own id for the volume (`hdd/id`, Reolink `HddInfo/number`). */
27462
+ id: string(),
27463
+ /** The camera's own name for it, when it gives one (`hddName`). */
27464
+ label: string().optional(),
27465
+ status: _enum([
27466
+ "ok",
27467
+ "unformatted",
27468
+ "error",
27469
+ "offline",
27470
+ "unknown"
27471
+ ]),
27472
+ /**
27473
+ * Total size in MB, or **null when the camera did not say**.
27474
+ *
27475
+ * Never 0 for an unreadable value: a measurement that failed is not a
27476
+ * measurement (D393), and a card whose size is unknown must not be
27477
+ * rendered as a card of size zero.
27478
+ */
27479
+ capacityMb: number().nullable(),
27480
+ /**
27481
+ * Free space in MB, or null when unknown.
27482
+ *
27483
+ * **Not a proxy for "has footage".** Measured 2026-09-22: 1436 and
27484
+ * 1439 both report exactly 11776 MB free — the fixed reserve a looping
27485
+ * card converges on once it has wrapped. At loop steady state the
27486
+ * number is identical whether the camera recorded yesterday or stopped
27487
+ * a month ago.
27488
+ */
27489
+ freeMb: number().nullable(),
27490
+ /** True when the camera reports the volume writable (`property` RW). */
27491
+ writable: boolean().optional()
27492
+ });
27493
+ /**
27494
+ * What the camera is doing with its own storage, right now.
27495
+ *
27496
+ * Every scalar is nullable and **null means the camera did not answer**,
27497
+ * never a default. A form that seeds `0` from an unanswered read invites
27498
+ * the operator to save that 0 back onto the camera.
27499
+ */
27500
+ var RecordingOnboardStatusSchema = object({
27501
+ storage: discriminatedUnion("kind", [
27502
+ object({
27503
+ kind: literal("present"),
27504
+ volumes: array(OnboardStorageVolumeSchema)
27505
+ }),
27506
+ object({
27507
+ kind: literal("absent"),
27508
+ reason: string()
27509
+ }),
27510
+ object({
27511
+ kind: literal("unknown"),
27512
+ reason: string()
27513
+ })
27514
+ ]),
27515
+ tracks: array(object({
27516
+ id: string(),
27517
+ enabled: boolean(),
27518
+ isVideo: boolean(),
27519
+ /** From the camera's own track description. Null when it does not say. */
27520
+ codec: string().nullable(),
27521
+ resolution: string().nullable(),
27522
+ /** Per-track overwrite flag, where the firmware keeps it per track. */
27523
+ overwriteWhenFull: boolean().nullable()
27524
+ })),
27525
+ /**
27526
+ * The track the write path targets — the enabled VIDEO one. Null when
27527
+ * no track could be identified, which is itself a refusal reason.
27528
+ */
27529
+ primaryTrackId: string().nullable(),
27530
+ /** Master "record to the card at all" switch. */
27531
+ enabled: boolean().nullable(),
27532
+ overwriteWhenFull: boolean().nullable(),
27533
+ preRecordSec: number().nullable(),
27534
+ postRecordSec: number().nullable(),
27535
+ /** Length of one recorded file, in minutes. */
27536
+ segmentMinutes: number().nullable(),
27537
+ /** The primary track's weekly windows, flattened. */
27538
+ windows: array(RecordWindowSchema),
27539
+ /**
27540
+ * How many windows the camera described that CamStack could NOT read —
27541
+ * an unrecognised trigger, an unparseable clock, a weekday it does not
27542
+ * name.
27543
+ *
27544
+ * A dropped window is work the reader threw away, and a schedule that
27545
+ * silently shows fewer rows than the camera holds is how an operator
27546
+ * saves back a schedule shorter than the one they were looking at
27547
+ * (D391). Non-zero means the window list is INCOMPLETE and a write
27548
+ * that replaces it would delete what was not shown — which is why a
27549
+ * provider reporting a non-zero count also reports the schedule as not
27550
+ * writable.
27551
+ */
27552
+ unreadableWindows: number(),
27553
+ /**
27554
+ * The camera is scheduled to record and has NO usable storage.
27555
+ *
27556
+ * A first-class fact because it is the fleet's most common silent
27557
+ * defect: measured 2026-09-22, 1441 and 3831 are both motion-recording
27558
+ * to a card that is not there. Neither the schedule nor the storage
27559
+ * read says anything wrong on its own; only the pair does.
27560
+ */
27561
+ recordingToNowhere: boolean(),
27562
+ lastFetchedAt: number()
27563
+ });
27564
+ /** Numeric range descriptor — `{ min, max, step }` per the getOptions convention. */
27565
+ var RangeSchema = object({
27566
+ min: number(),
27567
+ max: number(),
27568
+ step: number()
27569
+ });
27570
+ /**
27571
+ * The values a camera actually takes for a numeric field, when they are a SET
27572
+ * rather than a range.
27573
+ *
27574
+ * `{min,max,step}` cannot say what these two firmwares do. Measured on 1436
27575
+ * (I91DN) on 2026-09-22 by writing each value and reading it back:
27576
+ *
27577
+ * - pre-record: `0, 5, 10, 15, 20, 25, 30` and `2147483647` (INT32_MAX, the
27578
+ * camera's "no limit" — `-1` and `4294967295` both land on it);
27579
+ * - post-record: `5, 10, 30, 60, 120, 300, 600`.
27580
+ *
27581
+ * Neither is expressible as a step: the first has a sentinel two billion away
27582
+ * from its neighbours, the second doubles and then jumps. A range that tried
27583
+ * would forbid values the camera takes AND permit values it silently replaces
27584
+ * with 5 — wrong in both directions at once.
27585
+ *
27586
+ * `sentinel` names the member that is not a duration, so a surface can render
27587
+ * "no limit" instead of `2147483647` seconds.
27588
+ */
27589
+ var AllowedValuesSchema = object({
27590
+ values: array(number()).min(1),
27591
+ sentinel: object({
27592
+ value: number(),
27593
+ meaning: _enum(["no-limit", "disabled"])
27594
+ }).optional()
27595
+ });
27596
+ /**
27597
+ * Per-field availability on ONE camera.
27598
+ *
27599
+ * The field exists on every camera — this says whether this one can be
27600
+ * read and whether it can be written, and `reason` says why not when
27601
+ * either is false. The UI renders the control DISABLED with the reason
27602
+ * rather than hiding it, so a limitation is legible instead of looking
27603
+ * like a missing feature.
27604
+ */
27605
+ var OnboardFieldSupportSchema = object({
27606
+ readable: boolean(),
27607
+ writable: boolean(),
27608
+ /** Required whenever `readable` or `writable` is false. */
27609
+ reason: string().optional()
27610
+ });
27611
+ /** What this camera's schedule model can express. */
27612
+ var OnboardScheduleSupportSchema = object({
27613
+ support: OnboardFieldSupportSchema,
27614
+ /**
27615
+ * The smallest time step the camera can express, in minutes.
27616
+ *
27617
+ * Hikvision takes arbitrary minutes (`00:05:00`–`23:57:00` observed on
27618
+ * 1436's track 103). Reolink's schedule is a 7×24 HOUR mask, so 60. A
27619
+ * window whose edges are not a multiple of this is REFUSED rather than
27620
+ * quietly rounded — rounding is how an operator's 06:30 becomes 06:00
27621
+ * and nothing says so.
27622
+ */
27623
+ granularityMinutes: number(),
27624
+ /** Triggers this camera can record on. A window naming another is refused. */
27625
+ triggers: array(RecordTriggerSchema),
27626
+ /**
27627
+ * False when the camera stores ONE trigger per time range, so two
27628
+ * windows overlapping on the same day cannot carry different triggers.
27629
+ * True on Reolink, whose mask is per-trigger and independent.
27630
+ */
27631
+ supportsOverlappingTriggers: boolean()
27632
+ });
27633
+ var RecordingOnboardOptionsSchema = object({
27634
+ enabled: OnboardFieldSupportSchema,
27635
+ overwriteWhenFull: OnboardFieldSupportSchema,
27636
+ preRecordSec: OnboardFieldSupportSchema,
27637
+ preRecordSecRange: RangeSchema.optional(),
27638
+ /** Preferred over the range when the camera takes a SET, not a span. */
27639
+ preRecordSecAllowed: AllowedValuesSchema.optional(),
27640
+ postRecordSec: OnboardFieldSupportSchema,
27641
+ postRecordSecRange: RangeSchema.optional(),
27642
+ /** Preferred over the range when the camera takes a SET, not a span. */
27643
+ postRecordSecAllowed: AllowedValuesSchema.optional(),
27644
+ segmentMinutes: OnboardFieldSupportSchema,
27645
+ segmentMinutesRange: RangeSchema.optional(),
27646
+ /** Preferred over the range when the camera takes a SET, not a span. */
27647
+ segmentMinutesAllowed: AllowedValuesSchema.optional(),
27648
+ schedule: OnboardScheduleSupportSchema
27649
+ });
27650
+ /**
27651
+ * A partial change. Every field optional.
27652
+ *
27653
+ * Unlike the other `deviceConfig` caps, a provider here does **NOT**
27654
+ * silently ignore a field it cannot support — it refuses, by name,
27655
+ * through {@link describeOnboardRefusal}. Silence on a recording setting
27656
+ * is the failure D62 exists to prevent: the operator believes the camera
27657
+ * is recording the way the form says, and it is not.
27658
+ */
27659
+ var RecordingOnboardPatchSchema = object({
27660
+ enabled: boolean().optional(),
27661
+ overwriteWhenFull: boolean().optional(),
27662
+ preRecordSec: number().optional(),
27663
+ postRecordSec: number().optional(),
27664
+ segmentMinutes: number().optional(),
27665
+ /** The complete new window set for the primary track — not a delta. */
27666
+ windows: array(RecordWindowSchema).optional()
27667
+ });
27668
+ var recordingOnboardCapability = {
27669
+ name: "recording-onboard",
27670
+ scope: "device",
27671
+ deviceNative: true,
27672
+ mode: "singleton",
27673
+ deviceTypes: [DeviceType.Camera],
27674
+ deviceConfig: { ui: {
27675
+ kind: "derived-form",
27676
+ builderId: "recording-onboard",
27677
+ tab: "recording"
27678
+ } },
27679
+ methods: {
27680
+ getOptions: method(object({ deviceId: number() }), RecordingOnboardOptionsSchema),
27681
+ setSettings: method(object({
27682
+ deviceId: number(),
27683
+ settings: RecordingOnboardPatchSchema
27684
+ }), _void(), {
27685
+ kind: "mutation",
27686
+ auth: "admin"
27687
+ })
27688
+ },
27689
+ status: {
27690
+ schema: RecordingOnboardStatusSchema,
27691
+ kind: "poll"
27692
+ },
27693
+ runtimeState: RecordingOnboardStatusSchema,
27694
+ /**
27695
+ * Runtime-state durability: **restored** — operator-set camera-side
27696
+ * recording config; mutation-driven, and the storage half is the last
27697
+ * thing the camera said about its own card.
27698
+ *
27699
+ * See `RuntimeStateDurability`. Enforced by
27700
+ * `scripts/check-runtime-state-durability.ts`.
27701
+ */
27702
+ durability: "restored",
27703
+ /** Clock fields: written, but excluded from the compare that decides
27704
+ * whether persisting is worth a SQLite commit. */
27705
+ volatileStateFields: ["lastFetchedAt"]
27706
+ };
27707
+ /**
26473
27708
  * Generic device-level status snapshot. Auto-registered by `BaseDevice`
26474
27709
  * for every device, regardless of provider — the kernel needs a uniform
26475
27710
  * cap-keyed slice for the basic device flags every consumer expects to
@@ -29043,16 +30278,37 @@ var GridProfileOfferSchema = object({
29043
30278
  ]),
29044
30279
  offered: boolean(),
29045
30280
  /** Sources that cannot serve it. Empty when it is offered, or when there are no cells. */
29046
- missingSources: array(number().int().positive())
30281
+ missingSources: array(number().int().positive()),
30282
+ /**
30283
+ * The canvas this profile composes onto, `WxH`, or empty when it is not
30284
+ * offered. DERIVED from the cells and the sources' own size at this profile —
30285
+ * it is reported because nothing else in the system would ever say what the
30286
+ * grid came out as, and because it is the number an operator would otherwise
30287
+ * expect to type.
30288
+ */
30289
+ canvas: string(),
30290
+ /**
30291
+ * Whether this profile is PUBLISHED, of the ones the grid could serve.
30292
+ *
30293
+ * A grid's `high` is composed of its sources' `high`, so on a 4K fleet it is
30294
+ * a 4K canvas built from 4K decodes — something to opt into, not something a
30295
+ * viewer's adaptive should be handed by climbing to the top rung it can see.
30296
+ * Default is `mid` + `low`.
30297
+ */
30298
+ published: boolean()
29047
30299
  });
29048
30300
  var GridLayoutViewSchema = object({
29049
30301
  /** The persisted grid row this camera was declared from. */
29050
30302
  instanceId: string(),
29051
30303
  deviceId: number().int().nonnegative(),
29052
30304
  name: string(),
29053
- /** The canvas the operator laid out — this grid's `high`. mid/low are scaled from it. */
29054
- width: number().int(),
29055
- height: number().int(),
30305
+ /**
30306
+ * NO canvas size. A grid's resolution is not authored: each profile derives
30307
+ * its own from the cells and its sources' dimensions. The two numbers that
30308
+ * used to be here were a text field that silently decided both how much the
30309
+ * composite cost and how sharp it was — see `profiles[].canvas` for what it
30310
+ * came out as.
30311
+ */
29056
30312
  fps: number().int(),
29057
30313
  cells: array(GridLayoutCellSchema),
29058
30314
  /** What the catalog will publish, and what it refuses to. Read-only. */
@@ -29061,9 +30317,13 @@ var GridLayoutViewSchema = object({
29061
30317
  var GridLayoutPatchSchema = object({
29062
30318
  deviceId: number().int().nonnegative(),
29063
30319
  name: string().min(1).max(160).optional(),
29064
- width: number().int().min(160).max(7680).optional(),
29065
- height: number().int().min(120).max(4320).optional(),
29066
30320
  fps: number().int().min(1).max(60).optional(),
30321
+ /** Which profiles to publish. See `GridProfileOffer.published`. */
30322
+ publishedProfiles: array(_enum([
30323
+ "high",
30324
+ "mid",
30325
+ "low"
30326
+ ])).max(3).optional(),
29067
30327
  /**
29068
30328
  * The whole cell list at once. A per-cell patch would need an ordering the
29069
30329
  * editor does not have, and a half-applied layout is a picture nobody asked
@@ -31346,6 +32606,25 @@ var ptzAutotrackCapability = {
31346
32606
  scope: "device",
31347
32607
  deviceNative: true,
31348
32608
  mode: "singleton",
32609
+ /**
32610
+ * Two kinds of provider behind one name, the same shape `snapshot` and
32611
+ * `doorbell` use:
32612
+ *
32613
+ * - **Native**: the camera's own firmware autotrack, registered per device by
32614
+ * the driver when the probe says it exists (Reolink: yes; Hikvision:
32615
+ * model-dependent).
32616
+ * - **Wrapper**: the emulated implementation (`@camstack/system`'s `autotrack` builtin),
32617
+ * which follows a subject with `ptz.move` pulses driven by the detection
32618
+ * pipeline. `defaultActive: false` — the operator binds it per camera in
32619
+ * the bindings UI, exactly as `virtual-doorbell` is bound.
32620
+ *
32621
+ * The binding chooses WHICH implementation answers; `setEnabled` stays the
32622
+ * one switch for the function either way. A camera never has both driving its
32623
+ * head, which is the whole reason this is not a second cap with a second
32624
+ * toggle (D62, D520).
32625
+ */
32626
+ kind: "wrapper",
32627
+ defaultActive: false,
31349
32628
  deviceTypes: [DeviceType.Camera],
31350
32629
  deviceConfig: { ui: {
31351
32630
  kind: "widget",
@@ -31828,7 +33107,7 @@ method(object({
31828
33107
  auth: "admin"
31829
33108
  }), method(object({ deviceId: number() }), RecordingConfigSchema, {
31830
33109
  kind: "query",
31831
- auth: "admin"
33110
+ auth: "protected"
31832
33111
  }), method(object({
31833
33112
  deviceId: number(),
31834
33113
  profile: string(),
@@ -32058,13 +33337,101 @@ var ExportStateSchema = _enum([
32058
33337
  "expired",
32059
33338
  "deleted"
32060
33339
  ]);
32061
- /** One export job / history row. */
33340
+ /**
33341
+ * WHAT an export is — the authority, as opposed to the four top-level fields
33342
+ * the Library sorts and labels on (D558 § 2.2).
33343
+ *
33344
+ * Read it through {@link exportSubjectOf}, never off the record directly: the
33345
+ * field is optional for the history rows written before it existed, and that
33346
+ * absence has exactly one interpreter.
33347
+ */
33348
+ var ExportSubjectSchema = discriminatedUnion("kind", [object({
33349
+ kind: literal("footage"),
33350
+ deviceId: number(),
33351
+ profiles: array(string()).min(1),
33352
+ fromMs: number(),
33353
+ toMs: number()
33354
+ }), object({
33355
+ kind: literal("clip"),
33356
+ deviceId: number(),
33357
+ provider: string().min(1),
33358
+ source: string().min(1),
33359
+ sourceLabel: string().min(1),
33360
+ clipId: string().min(1),
33361
+ /**
33362
+ * WHERE IN THE CATALOG to confirm this clip — the day window the surface was
33363
+ * already listing when the operator picked the segment.
33364
+ *
33365
+ * It is **not** a time range control and it never becomes one: the record's
33366
+ * `fromMs`/`toMs` come from the catalog ROW and from nothing a caller
33367
+ * supplied (D558 § 5.4.3), and a window that does not contain the clip is a
33368
+ * `catalog-miss`, not a silently wider search. It exists because
33369
+ * `videoclips.listClips` takes `since`/`until` and has no by-handle twin:
33370
+ * the catalog check D558 asks for is literally that call, and a call needs a
33371
+ * window. The surface has one — `ClipsBrowser`'s `dayWindow`, one local
33372
+ * wall-clock day, which is also the only width measured to be cheap (one
33373
+ * day on 592 lists 171 clips in 712 ms, a busy day 546 in 2.0 s; twenty days
33374
+ * of one child's events took 3.6–5.4 s).
33375
+ */
33376
+ catalogWindow: object({
33377
+ sinceMs: number(),
33378
+ untilMs: number()
33379
+ }),
33380
+ profile: _enum([
33381
+ "high",
33382
+ "mid",
33383
+ "low"
33384
+ ]),
33385
+ /**
33386
+ * The operator's authorisation to wake a sleeping camera for this export.
33387
+ * ONE definition, in the cap that owns the clip read ({@link ClipWakeSchema}),
33388
+ * because the gate that honours it is the clip provider's sleep gate — a
33389
+ * second enum here would be a second contract. Absent by default; never
33390
+ * settable by a scheduler or a retry.
33391
+ *
33392
+ * **One authorised yes is ONE wake.** A failed clip export is retried only by
33393
+ * an operator act that asks again; a queued job that outlived its wake fails
33394
+ * with a reason rather than waking on its turn; and this subject carries
33395
+ * exactly one profile precisely so one tap is never two fetches (D558 § 5.2).
33396
+ */
33397
+ wake: ClipWakeSchema.optional()
33398
+ })]);
33399
+ /**
33400
+ * One export job / history row.
33401
+ *
33402
+ * **`subject` is the AUTHORITY on what was exported. `deviceId`, `profile`,
33403
+ * `fromMs` and `toMs` are its PROJECTION** — kept top-level because the whole
33404
+ * Library sorts and labels on them (`library-items.ts` orders an export by
33405
+ * `fromMs`; `export-format.ts` draws `rangeLabel` from the pair), and a row
33406
+ * that did not fill them would sort under the epoch and render a blank range.
33407
+ * Write to the subject and read from the projection and the two will disagree;
33408
+ * the projection is derived at creation and never edited afterwards.
33409
+ *
33410
+ * And they mean DIFFERENT FACTS for the two kinds, which is the part a reader
33411
+ * who knows only the recording export will get wrong:
33412
+ *
33413
+ * | field | `kind:'footage'` | `kind:'clip'` |
33414
+ * | --- | --- | --- |
33415
+ * | `fromMs`/`toMs` | the stretch the operator ASKED for | the camera's own boundaries, always `clip.timeRange`, never anything a caller supplied |
33416
+ * | `profile` | the stream rendered | the twin actually SERVED (`subject.profile` is the one asked for) |
33417
+ *
33418
+ * Same type, different fact — the shape this repo keeps getting wrong (D385's
33419
+ * two authorities, D224's second copy).
33420
+ */
32062
33421
  var ExportRecordSchema = object({
32063
33422
  id: string(),
32064
33423
  deviceId: number(),
32065
33424
  profile: string(),
32066
33425
  fromMs: number(),
32067
33426
  toMs: number(),
33427
+ /**
33428
+ * What this export IS. Optional ONLY for the rows written before D558: the
33429
+ * store parses every row through this schema on every read, so a required
33430
+ * field would make the export AUDIT — which is the whole reason rows survive
33431
+ * file deletion — unreadable in one release. Absence means `footage`, and
33432
+ * {@link exportSubjectOf} is the one place that says so.
33433
+ */
33434
+ subject: ExportSubjectSchema.optional(),
32068
33435
  options: ExportOptionsSchema,
32069
33436
  state: ExportStateSchema,
32070
33437
  /** 0–100 while rendering; null otherwise. */
@@ -32081,6 +33448,18 @@ var ExportRecordSchema = object({
32081
33448
  /** Failure reason when state is 'failed'; null otherwise. */
32082
33449
  error: string().nullable()
32083
33450
  });
33451
+ _enum([
33452
+ "catalog-miss",
33453
+ "catalog-unreachable",
33454
+ "no-file-for-window",
33455
+ "clip-in-progress",
33456
+ "unsupported-option",
33457
+ "sleeping",
33458
+ "camera-refused",
33459
+ "fetch-failed",
33460
+ "too-large-to-transfer",
33461
+ "wake-expired"
33462
+ ]);
32084
33463
  /** Candidate download URLs (LAN first, then operator extra hosts). */
32085
33464
  var ExportDownloadSchema = object({
32086
33465
  url: string(),
@@ -32099,16 +33478,50 @@ var ExportBytesSchema = object({
32099
33478
  name: string(),
32100
33479
  bytes: number().int().nonnegative()
32101
33480
  });
33481
+ /** Canonical `profiles[]`, falling back to the legacy singular `profile`. */
33482
+ function resolveExportProfiles(input) {
33483
+ if (input.profiles !== void 0 && input.profiles.length > 0) return [...input.profiles];
33484
+ if (typeof input.profile === "string" && input.profile.length > 0) return [input.profile];
33485
+ return [];
33486
+ }
32102
33487
  method(object({
32103
33488
  deviceId: number(),
32104
33489
  /** @deprecated Prefer `profiles`. Kept so timelapse/notifiers keep working. */
32105
33490
  profile: string().optional(),
32106
33491
  profiles: array(string()).min(1).optional(),
32107
- fromMs: number(),
32108
- toMs: number(),
33492
+ /** Footage only — a clip's boundaries are the camera's. */
33493
+ fromMs: number().optional(),
33494
+ toMs: number().optional(),
33495
+ /** What to export. Absent means the legacy flat footage request. */
33496
+ subject: ExportSubjectSchema.optional(),
32109
33497
  options: ExportOptionsSchema
32110
33498
  }).superRefine((v, ctx) => {
32111
- if ((v.profiles !== void 0 && v.profiles.length > 0 ? v.profiles : v.profile !== void 0 ? [v.profile] : []).length < 1) ctx.addIssue({
33499
+ if (v.subject?.kind === "clip") {
33500
+ if (v.subject.deviceId !== v.deviceId) ctx.addIssue({
33501
+ code: ZodIssueCode.custom,
33502
+ message: `subject.deviceId (${v.subject.deviceId}) must equal deviceId (${v.deviceId}) — the top-level field is what per-device scope enforcement reads`,
33503
+ path: ["subject", "deviceId"]
33504
+ });
33505
+ if (v.fromMs !== void 0 || v.toMs !== void 0) ctx.addIssue({
33506
+ code: ZodIssueCode.custom,
33507
+ message: "a clip export asks for no time range: the camera chose the boundaries and they are read from the catalog row",
33508
+ path: ["fromMs"]
33509
+ });
33510
+ return;
33511
+ }
33512
+ if (v.subject?.kind === "footage" && v.subject.deviceId !== v.deviceId) ctx.addIssue({
33513
+ code: ZodIssueCode.custom,
33514
+ message: `subject.deviceId (${v.subject.deviceId}) must equal deviceId (${v.deviceId})`,
33515
+ path: ["subject", "deviceId"]
33516
+ });
33517
+ const fromMs = v.subject?.kind === "footage" ? v.subject.fromMs : v.fromMs;
33518
+ const toMs = v.subject?.kind === "footage" ? v.subject.toMs : v.toMs;
33519
+ if (typeof fromMs !== "number" || typeof toMs !== "number") ctx.addIssue({
33520
+ code: ZodIssueCode.custom,
33521
+ message: "a footage export needs fromMs and toMs",
33522
+ path: ["fromMs"]
33523
+ });
33524
+ if ((v.subject?.kind === "footage" ? [...v.subject.profiles] : resolveExportProfiles(v)).length < 1) ctx.addIssue({
32112
33525
  code: ZodIssueCode.custom,
32113
33526
  message: "pass profiles[] (min 1) or legacy profile",
32114
33527
  path: ["profiles"]
@@ -34920,6 +36333,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
34920
36333
  pressureSensor: pressureSensorCapability,
34921
36334
  privacyMask: privacyMaskCapability,
34922
36335
  ptzAutotrack: ptzAutotrackCapability,
36336
+ recordingOnboard: recordingOnboardCapability,
34923
36337
  recordingSignal: recordingSignalCapability,
34924
36338
  sceneMonitor: sceneMonitorCapability,
34925
36339
  scriptRunner: scriptRunnerCapability,
@@ -36081,7 +37495,1336 @@ var BaseDeviceProvider = class extends BaseAddon {
36081
37495
  DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder;
36082
37496
  new Set(Object.values(DeviceType));
36083
37497
  DeviceFeature.BatteryOperated;
37498
+ /**
37499
+ * The vocabulary, in the order a picker offers it.
37500
+ *
37501
+ * Adding a key here is not enough to make it pickable: both glyph tables must
37502
+ * gain it too, and their tests fail until they do. That is the intended
37503
+ * friction — it is the only thing that keeps the two surfaces honest.
37504
+ */
37505
+ var DISPLAY_ICON_VOCABULARY = [
37506
+ {
37507
+ key: "camera",
37508
+ group: "device",
37509
+ synonyms: [
37510
+ "telecamera",
37511
+ "videocamera",
37512
+ "camera",
37513
+ "cam"
37514
+ ]
37515
+ },
37516
+ {
37517
+ key: "network",
37518
+ group: "device",
37519
+ synonyms: [
37520
+ "rete",
37521
+ "network",
37522
+ "lan"
37523
+ ]
37524
+ },
37525
+ {
37526
+ key: "toggle-left",
37527
+ group: "device",
37528
+ synonyms: [
37529
+ "interruttore",
37530
+ "switch",
37531
+ "toggle"
37532
+ ]
37533
+ },
37534
+ {
37535
+ key: "toggle-right",
37536
+ group: "device",
37537
+ synonyms: [
37538
+ "interruttore",
37539
+ "switch",
37540
+ "toggle",
37541
+ "acceso"
37542
+ ]
37543
+ },
37544
+ {
37545
+ key: "circle-dot",
37546
+ group: "device",
37547
+ synonyms: [
37548
+ "pulsante",
37549
+ "bottone",
37550
+ "button",
37551
+ "dot"
37552
+ ]
37553
+ },
37554
+ {
37555
+ key: "refresh-cw",
37556
+ group: "device",
37557
+ synonyms: [
37558
+ "aggiorna",
37559
+ "ricarica",
37560
+ "refresh",
37561
+ "reload",
37562
+ "ciclo"
37563
+ ]
37564
+ },
37565
+ {
37566
+ key: "box",
37567
+ group: "device",
37568
+ synonyms: [
37569
+ "scatola",
37570
+ "box",
37571
+ "contenitore"
37572
+ ]
37573
+ },
37574
+ {
37575
+ key: "scroll-text",
37576
+ group: "device",
37577
+ synonyms: [
37578
+ "registro",
37579
+ "log",
37580
+ "testo"
37581
+ ]
37582
+ },
37583
+ {
37584
+ key: "workflow",
37585
+ group: "device",
37586
+ synonyms: [
37587
+ "automazione",
37588
+ "flusso",
37589
+ "workflow",
37590
+ "scenario"
37591
+ ]
37592
+ },
37593
+ {
37594
+ key: "lock",
37595
+ group: "device",
37596
+ synonyms: [
37597
+ "serratura",
37598
+ "chiave",
37599
+ "lucchetto",
37600
+ "chiuso",
37601
+ "locked"
37602
+ ]
37603
+ },
37604
+ {
37605
+ key: "lock-open",
37606
+ group: "device",
37607
+ synonyms: [
37608
+ "serratura",
37609
+ "aperta",
37610
+ "sbloccato",
37611
+ "unlocked"
37612
+ ]
37613
+ },
37614
+ {
37615
+ key: "blinds",
37616
+ group: "device",
37617
+ synonyms: [
37618
+ "tapparella",
37619
+ "persiana",
37620
+ "serranda",
37621
+ "avvolgibile",
37622
+ "shutter",
37623
+ "blind",
37624
+ "veneziana"
37625
+ ]
37626
+ },
37627
+ {
37628
+ key: "air-vent",
37629
+ group: "device",
37630
+ synonyms: [
37631
+ "bocchetta",
37632
+ "aria",
37633
+ "ventilazione",
37634
+ "condizionatore",
37635
+ "vent"
37636
+ ]
37637
+ },
37638
+ {
37639
+ key: "monitor-play",
37640
+ group: "device",
37641
+ synonyms: [
37642
+ "schermo",
37643
+ "riproduzione",
37644
+ "player",
37645
+ "display"
37646
+ ]
37647
+ },
37648
+ {
37649
+ key: "sliders-horizontal",
37650
+ group: "device",
37651
+ synonyms: [
37652
+ "regolazione",
37653
+ "cursori",
37654
+ "impostazioni",
37655
+ "slider"
37656
+ ]
37657
+ },
37658
+ {
37659
+ key: "map-pin",
37660
+ group: "device",
37661
+ synonyms: [
37662
+ "posizione",
37663
+ "luogo",
37664
+ "mappa",
37665
+ "pin"
37666
+ ]
37667
+ },
37668
+ {
37669
+ key: "bot",
37670
+ group: "device",
37671
+ synonyms: [
37672
+ "robot",
37673
+ "aspirapolvere",
37674
+ "bot"
37675
+ ]
37676
+ },
37677
+ {
37678
+ key: "tractor",
37679
+ group: "device",
37680
+ synonyms: [
37681
+ "trattore",
37682
+ "tosaerba",
37683
+ "giardino"
37684
+ ]
37685
+ },
37686
+ {
37687
+ key: "image",
37688
+ group: "device",
37689
+ synonyms: [
37690
+ "immagine",
37691
+ "foto",
37692
+ "snapshot"
37693
+ ]
37694
+ },
37695
+ {
37696
+ key: "bone",
37697
+ group: "device",
37698
+ synonyms: ["osso", "cane"]
37699
+ },
37700
+ {
37701
+ key: "door-closed",
37702
+ group: "device",
37703
+ synonyms: [
37704
+ "porta",
37705
+ "chiusa",
37706
+ "contatto"
37707
+ ]
37708
+ },
37709
+ {
37710
+ key: "door-open",
37711
+ group: "device",
37712
+ synonyms: [
37713
+ "porta",
37714
+ "aperta",
37715
+ "contatto",
37716
+ "ingresso",
37717
+ "uscita"
37718
+ ]
37719
+ },
37720
+ {
37721
+ key: "crosshair",
37722
+ group: "device",
37723
+ synonyms: [
37724
+ "mirino",
37725
+ "puntamento",
37726
+ "centro"
37727
+ ]
37728
+ },
37729
+ {
37730
+ key: "eye",
37731
+ group: "device",
37732
+ synonyms: [
37733
+ "occhio",
37734
+ "visibile",
37735
+ "visto"
37736
+ ]
37737
+ },
37738
+ {
37739
+ key: "eye-off",
37740
+ group: "device",
37741
+ synonyms: [
37742
+ "privacy",
37743
+ "nascosto",
37744
+ "oscurato",
37745
+ "mascherato",
37746
+ "invisibile"
37747
+ ]
37748
+ },
37749
+ {
37750
+ key: "smartphone",
37751
+ group: "device",
37752
+ synonyms: [
37753
+ "telefono",
37754
+ "cellulare",
37755
+ "smartphone"
37756
+ ]
37757
+ },
37758
+ {
37759
+ key: "phone",
37760
+ group: "device",
37761
+ synonyms: ["telefono", "chiamata"]
37762
+ },
37763
+ {
37764
+ key: "tablet",
37765
+ group: "device",
37766
+ synonyms: ["tablet", "ipad"]
37767
+ },
37768
+ {
37769
+ key: "laptop",
37770
+ group: "device",
37771
+ synonyms: [
37772
+ "portatile",
37773
+ "laptop",
37774
+ "notebook"
37775
+ ]
37776
+ },
37777
+ {
37778
+ key: "monitor",
37779
+ group: "device",
37780
+ synonyms: ["monitor", "schermo"]
37781
+ },
37782
+ {
37783
+ key: "tv",
37784
+ group: "device",
37785
+ synonyms: ["televisore", "tv"]
37786
+ },
37787
+ {
37788
+ key: "speaker",
37789
+ group: "device",
37790
+ synonyms: [
37791
+ "altoparlante",
37792
+ "cassa",
37793
+ "speaker"
37794
+ ]
37795
+ },
37796
+ {
37797
+ key: "printer",
37798
+ group: "device",
37799
+ synonyms: ["stampante"]
37800
+ },
37801
+ {
37802
+ key: "projector",
37803
+ group: "device",
37804
+ synonyms: ["proiettore"]
37805
+ },
37806
+ {
37807
+ key: "gamepad-2",
37808
+ group: "device",
37809
+ synonyms: [
37810
+ "console",
37811
+ "gioco",
37812
+ "joystick"
37813
+ ]
37814
+ },
37815
+ {
37816
+ key: "radio",
37817
+ group: "device",
37818
+ synonyms: ["radio"]
37819
+ },
37820
+ {
37821
+ key: "router",
37822
+ group: "device",
37823
+ synonyms: ["router", "modem"]
37824
+ },
37825
+ {
37826
+ key: "bluetooth",
37827
+ group: "device",
37828
+ synonyms: ["bluetooth"]
37829
+ },
37830
+ {
37831
+ key: "cpu",
37832
+ group: "device",
37833
+ synonyms: [
37834
+ "processore",
37835
+ "cpu",
37836
+ "chip",
37837
+ "dispositivo"
37838
+ ]
37839
+ },
37840
+ {
37841
+ key: "hard-drive",
37842
+ group: "device",
37843
+ synonyms: [
37844
+ "disco",
37845
+ "hdd",
37846
+ "archiviazione",
37847
+ "storage"
37848
+ ]
37849
+ },
37850
+ {
37851
+ key: "server",
37852
+ group: "device",
37853
+ synonyms: ["server", "nas"]
37854
+ },
37855
+ {
37856
+ key: "mic",
37857
+ group: "device",
37858
+ synonyms: ["microfono", "audio"]
37859
+ },
37860
+ {
37861
+ key: "mic-off",
37862
+ group: "device",
37863
+ synonyms: [
37864
+ "microfono",
37865
+ "spento",
37866
+ "muto"
37867
+ ]
37868
+ },
37869
+ {
37870
+ key: "video",
37871
+ group: "device",
37872
+ synonyms: ["video"]
37873
+ },
37874
+ {
37875
+ key: "video-off",
37876
+ group: "device",
37877
+ synonyms: ["video", "spento"]
37878
+ },
37879
+ {
37880
+ key: "music",
37881
+ group: "device",
37882
+ synonyms: ["musica"]
37883
+ },
37884
+ {
37885
+ key: "plug",
37886
+ group: "device",
37887
+ synonyms: [
37888
+ "presa",
37889
+ "spina",
37890
+ "plug"
37891
+ ]
37892
+ },
37893
+ {
37894
+ key: "plug-zap",
37895
+ group: "device",
37896
+ synonyms: ["presa", "alimentata"]
37897
+ },
37898
+ {
37899
+ key: "power",
37900
+ group: "device",
37901
+ synonyms: [
37902
+ "accensione",
37903
+ "alimentazione",
37904
+ "power"
37905
+ ]
37906
+ },
37907
+ {
37908
+ key: "power-off",
37909
+ group: "device",
37910
+ synonyms: ["spegnimento", "spento"]
37911
+ },
37912
+ {
37913
+ key: "cable",
37914
+ group: "device",
37915
+ synonyms: ["cavo"]
37916
+ },
37917
+ {
37918
+ key: "battery",
37919
+ group: "device",
37920
+ synonyms: ["batteria"]
37921
+ },
37922
+ {
37923
+ key: "battery-charging",
37924
+ group: "device",
37925
+ synonyms: [
37926
+ "batteria",
37927
+ "carica",
37928
+ "ricarica"
37929
+ ]
37930
+ },
37931
+ {
37932
+ key: "battery-full",
37933
+ group: "device",
37934
+ synonyms: [
37935
+ "batteria",
37936
+ "carica",
37937
+ "piena"
37938
+ ]
37939
+ },
37940
+ {
37941
+ key: "battery-low",
37942
+ group: "device",
37943
+ synonyms: ["batteria", "scarica"]
37944
+ },
37945
+ {
37946
+ key: "key",
37947
+ group: "device",
37948
+ synonyms: ["chiave"]
37949
+ },
37950
+ {
37951
+ key: "key-round",
37952
+ group: "device",
37953
+ synonyms: ["chiave", "tonda"]
37954
+ },
37955
+ {
37956
+ key: "mailbox",
37957
+ group: "device",
37958
+ synonyms: [
37959
+ "cassetta",
37960
+ "postale",
37961
+ "posta"
37962
+ ]
37963
+ },
37964
+ {
37965
+ key: "package",
37966
+ group: "device",
37967
+ synonyms: ["pacco", "consegna"]
37968
+ },
37969
+ {
37970
+ key: "shield",
37971
+ group: "device",
37972
+ synonyms: [
37973
+ "scudo",
37974
+ "protezione",
37975
+ "sicurezza",
37976
+ "allarme"
37977
+ ]
37978
+ },
37979
+ {
37980
+ key: "shield-check",
37981
+ group: "device",
37982
+ synonyms: [
37983
+ "protetto",
37984
+ "sicuro",
37985
+ "armato"
37986
+ ]
37987
+ },
37988
+ {
37989
+ key: "shield-off",
37990
+ group: "device",
37991
+ synonyms: [
37992
+ "protezione",
37993
+ "disattivata",
37994
+ "disarmato"
37995
+ ]
37996
+ },
37997
+ {
37998
+ key: "briefcase",
37999
+ group: "device",
38000
+ synonyms: [
38001
+ "ufficio",
38002
+ "lavoro",
38003
+ "valigetta",
38004
+ "studio"
38005
+ ]
38006
+ },
38007
+ {
38008
+ key: "lightbulb",
38009
+ group: "lighting",
38010
+ synonyms: [
38011
+ "luce",
38012
+ "lampadina",
38013
+ "lampada",
38014
+ "light"
38015
+ ]
38016
+ },
38017
+ {
38018
+ key: "lamp",
38019
+ group: "lighting",
38020
+ synonyms: ["lampada", "abatjour"]
38021
+ },
38022
+ {
38023
+ key: "lamp-ceiling",
38024
+ group: "lighting",
38025
+ synonyms: [
38026
+ "plafoniera",
38027
+ "lampadario",
38028
+ "luce",
38029
+ "soffitto"
38030
+ ]
38031
+ },
38032
+ {
38033
+ key: "lamp-desk",
38034
+ group: "lighting",
38035
+ synonyms: [
38036
+ "lampada",
38037
+ "scrivania",
38038
+ "abatjour"
38039
+ ]
38040
+ },
38041
+ {
38042
+ key: "lamp-floor",
38043
+ group: "lighting",
38044
+ synonyms: [
38045
+ "piantana",
38046
+ "lampada",
38047
+ "terra"
38048
+ ]
38049
+ },
38050
+ {
38051
+ key: "flashlight",
38052
+ group: "lighting",
38053
+ synonyms: [
38054
+ "faretto",
38055
+ "torcia",
38056
+ "spot",
38057
+ "faro"
38058
+ ]
38059
+ },
38060
+ {
38061
+ key: "heater",
38062
+ group: "lighting",
38063
+ synonyms: [
38064
+ "riscaldamento",
38065
+ "termosifone",
38066
+ "stufa",
38067
+ "calore"
38068
+ ]
38069
+ },
38070
+ {
38071
+ key: "snowflake",
38072
+ group: "lighting",
38073
+ synonyms: [
38074
+ "freddo",
38075
+ "raffrescamento",
38076
+ "aria",
38077
+ "condizionata",
38078
+ "neve"
38079
+ ]
38080
+ },
38081
+ {
38082
+ key: "thermometer-sun",
38083
+ group: "lighting",
38084
+ synonyms: [
38085
+ "caldo",
38086
+ "temperatura",
38087
+ "alta"
38088
+ ]
38089
+ },
38090
+ {
38091
+ key: "thermometer-snowflake",
38092
+ group: "lighting",
38093
+ synonyms: [
38094
+ "freddo",
38095
+ "temperatura",
38096
+ "bassa"
38097
+ ]
38098
+ },
38099
+ {
38100
+ key: "refrigerator",
38101
+ group: "lighting",
38102
+ synonyms: ["frigorifero", "frigo"]
38103
+ },
38104
+ {
38105
+ key: "microwave",
38106
+ group: "lighting",
38107
+ synonyms: ["microonde", "forno"]
38108
+ },
38109
+ {
38110
+ key: "washing-machine",
38111
+ group: "lighting",
38112
+ synonyms: [
38113
+ "lavatrice",
38114
+ "lavastoviglie",
38115
+ "asciugatrice"
38116
+ ]
38117
+ },
38118
+ {
38119
+ key: "cooking-pot",
38120
+ group: "lighting",
38121
+ synonyms: [
38122
+ "pentola",
38123
+ "cucina",
38124
+ "fornello"
38125
+ ]
38126
+ },
38127
+ {
38128
+ key: "utensils",
38129
+ group: "lighting",
38130
+ synonyms: [
38131
+ "cucina",
38132
+ "posate",
38133
+ "sala",
38134
+ "pranzo"
38135
+ ]
38136
+ },
38137
+ {
38138
+ key: "thermometer",
38139
+ group: "sensing",
38140
+ synonyms: [
38141
+ "temperatura",
38142
+ "termometro",
38143
+ "termostato"
38144
+ ]
38145
+ },
38146
+ {
38147
+ key: "droplets",
38148
+ group: "sensing",
38149
+ synonyms: [
38150
+ "umidita",
38151
+ "acqua",
38152
+ "allagamento",
38153
+ "perdita"
38154
+ ]
38155
+ },
38156
+ {
38157
+ key: "droplet",
38158
+ group: "sensing",
38159
+ synonyms: [
38160
+ "goccia",
38161
+ "acqua",
38162
+ "perdita",
38163
+ "allagamento"
38164
+ ]
38165
+ },
38166
+ {
38167
+ key: "gauge",
38168
+ group: "sensing",
38169
+ synonyms: [
38170
+ "misura",
38171
+ "livello",
38172
+ "pressione",
38173
+ "indicatore"
38174
+ ]
38175
+ },
38176
+ {
38177
+ key: "gauge2",
38178
+ group: "sensing",
38179
+ synonyms: [
38180
+ "misura",
38181
+ "livello",
38182
+ "pressione",
38183
+ "indicatore"
38184
+ ],
38185
+ aliasOf: "gauge"
38186
+ },
38187
+ {
38188
+ key: "sun",
38189
+ group: "sensing",
38190
+ synonyms: [
38191
+ "sole",
38192
+ "luminosita",
38193
+ "giorno",
38194
+ "esterno"
38195
+ ]
38196
+ },
38197
+ {
38198
+ key: "sun-medium",
38199
+ group: "sensing",
38200
+ synonyms: ["sole", "luminosita"]
38201
+ },
38202
+ {
38203
+ key: "sunrise",
38204
+ group: "sensing",
38205
+ synonyms: ["alba"]
38206
+ },
38207
+ {
38208
+ key: "moon",
38209
+ group: "sensing",
38210
+ synonyms: [
38211
+ "notte",
38212
+ "luna",
38213
+ "notturna",
38214
+ "nightvision"
38215
+ ]
38216
+ },
38217
+ {
38218
+ key: "wind",
38219
+ group: "sensing",
38220
+ synonyms: [
38221
+ "vento",
38222
+ "aria",
38223
+ "gas",
38224
+ "monossido",
38225
+ "co"
38226
+ ]
38227
+ },
38228
+ {
38229
+ key: "cloud",
38230
+ group: "sensing",
38231
+ synonyms: ["nuvola", "nuvoloso"]
38232
+ },
38233
+ {
38234
+ key: "cloud-rain",
38235
+ group: "sensing",
38236
+ synonyms: ["pioggia"]
38237
+ },
38238
+ {
38239
+ key: "cloud-sun",
38240
+ group: "sensing",
38241
+ synonyms: [
38242
+ "variabile",
38243
+ "sole",
38244
+ "nuvole"
38245
+ ]
38246
+ },
38247
+ {
38248
+ key: "waves",
38249
+ group: "sensing",
38250
+ synonyms: [
38251
+ "onde",
38252
+ "acqua",
38253
+ "vibrazione"
38254
+ ]
38255
+ },
38256
+ {
38257
+ key: "sprout",
38258
+ group: "sensing",
38259
+ synonyms: [
38260
+ "irrigazione",
38261
+ "pianta",
38262
+ "germoglio",
38263
+ "orto"
38264
+ ]
38265
+ },
38266
+ {
38267
+ key: "leaf",
38268
+ group: "sensing",
38269
+ synonyms: [
38270
+ "foglia",
38271
+ "pianta",
38272
+ "verde"
38273
+ ]
38274
+ },
38275
+ {
38276
+ key: "flower",
38277
+ group: "sensing",
38278
+ synonyms: ["fiore", "pianta"]
38279
+ },
38280
+ {
38281
+ key: "zap",
38282
+ group: "sensing",
38283
+ synonyms: [
38284
+ "energia",
38285
+ "corrente",
38286
+ "consumo",
38287
+ "watt",
38288
+ "elettricita"
38289
+ ]
38290
+ },
38291
+ {
38292
+ key: "flame",
38293
+ group: "sensing",
38294
+ synonyms: [
38295
+ "fiamma",
38296
+ "gas",
38297
+ "fuoco",
38298
+ "incendio"
38299
+ ]
38300
+ },
38301
+ {
38302
+ key: "bell",
38303
+ group: "sensing",
38304
+ synonyms: [
38305
+ "campanello",
38306
+ "suoneria",
38307
+ "notifica",
38308
+ "bell",
38309
+ "chime"
38310
+ ]
38311
+ },
38312
+ {
38313
+ key: "bell-ring",
38314
+ group: "sensing",
38315
+ synonyms: [
38316
+ "campanello",
38317
+ "suona",
38318
+ "doorbell",
38319
+ "notifica"
38320
+ ]
38321
+ },
38322
+ {
38323
+ key: "bell-off",
38324
+ group: "sensing",
38325
+ synonyms: [
38326
+ "silenzioso",
38327
+ "notifiche",
38328
+ "disattivate",
38329
+ "muto"
38330
+ ]
38331
+ },
38332
+ {
38333
+ key: "siren",
38334
+ group: "sensing",
38335
+ synonyms: ["sirena", "allarme"]
38336
+ },
38337
+ {
38338
+ key: "radar",
38339
+ group: "sensing",
38340
+ synonyms: [
38341
+ "movimento",
38342
+ "presenza",
38343
+ "pir",
38344
+ "rilevamento",
38345
+ "radar"
38346
+ ]
38347
+ },
38348
+ {
38349
+ key: "activity",
38350
+ group: "sensing",
38351
+ synonyms: [
38352
+ "attivita",
38353
+ "sensore",
38354
+ "generico"
38355
+ ]
38356
+ },
38357
+ {
38358
+ key: "hash",
38359
+ group: "sensing",
38360
+ synonyms: [
38361
+ "numero",
38362
+ "contatore",
38363
+ "valore"
38364
+ ]
38365
+ },
38366
+ {
38367
+ key: "clock",
38368
+ group: "sensing",
38369
+ synonyms: [
38370
+ "orologio",
38371
+ "ora",
38372
+ "tempo"
38373
+ ]
38374
+ },
38375
+ {
38376
+ key: "timer",
38377
+ group: "sensing",
38378
+ synonyms: [
38379
+ "timer",
38380
+ "conto",
38381
+ "rovescia"
38382
+ ]
38383
+ },
38384
+ {
38385
+ key: "calendar",
38386
+ group: "sensing",
38387
+ synonyms: [
38388
+ "calendario",
38389
+ "data",
38390
+ "pianificazione"
38391
+ ]
38392
+ },
38393
+ {
38394
+ key: "pipette",
38395
+ group: "sensing",
38396
+ synonyms: [
38397
+ "qualita",
38398
+ "aria",
38399
+ "misura",
38400
+ "campione"
38401
+ ]
38402
+ },
38403
+ {
38404
+ key: "fan",
38405
+ group: "sensing",
38406
+ synonyms: [
38407
+ "ventilatore",
38408
+ "ventola",
38409
+ "aria"
38410
+ ]
38411
+ },
38412
+ {
38413
+ key: "bath",
38414
+ group: "sensing",
38415
+ synonyms: [
38416
+ "bagno",
38417
+ "vasca",
38418
+ "doccia"
38419
+ ]
38420
+ },
38421
+ {
38422
+ key: "footprints",
38423
+ group: "sensing",
38424
+ synonyms: [
38425
+ "movimento",
38426
+ "passi",
38427
+ "presenza",
38428
+ "motion"
38429
+ ]
38430
+ },
38431
+ {
38432
+ key: "alarm-smoke",
38433
+ group: "sensing",
38434
+ synonyms: [
38435
+ "fumo",
38436
+ "antincendio",
38437
+ "rilevatore"
38438
+ ]
38439
+ },
38440
+ {
38441
+ key: "shield-alert",
38442
+ group: "sensing",
38443
+ synonyms: [
38444
+ "manomissione",
38445
+ "allarme",
38446
+ "tamper",
38447
+ "avviso"
38448
+ ]
38449
+ },
38450
+ {
38451
+ key: "vibrate",
38452
+ group: "sensing",
38453
+ synonyms: [
38454
+ "vibrazione",
38455
+ "urto",
38456
+ "tamper"
38457
+ ]
38458
+ },
38459
+ {
38460
+ key: "wifi",
38461
+ group: "sensing",
38462
+ synonyms: [
38463
+ "wifi",
38464
+ "rete",
38465
+ "segnale"
38466
+ ]
38467
+ },
38468
+ {
38469
+ key: "wifi-off",
38470
+ group: "sensing",
38471
+ synonyms: [
38472
+ "wifi",
38473
+ "assente",
38474
+ "offline",
38475
+ "disconnesso"
38476
+ ]
38477
+ },
38478
+ {
38479
+ key: "volume-2",
38480
+ group: "sensing",
38481
+ synonyms: [
38482
+ "volume",
38483
+ "audio",
38484
+ "altoparlante",
38485
+ "suono"
38486
+ ]
38487
+ },
38488
+ {
38489
+ key: "volume-x",
38490
+ group: "sensing",
38491
+ synonyms: [
38492
+ "muto",
38493
+ "silenziato",
38494
+ "volume",
38495
+ "spento"
38496
+ ]
38497
+ },
38498
+ {
38499
+ key: "heart",
38500
+ group: "sensing",
38501
+ synonyms: [
38502
+ "cuore",
38503
+ "preferito",
38504
+ "salute"
38505
+ ]
38506
+ },
38507
+ {
38508
+ key: "heart-pulse",
38509
+ group: "sensing",
38510
+ synonyms: [
38511
+ "battito",
38512
+ "salute",
38513
+ "monitoraggio"
38514
+ ]
38515
+ },
38516
+ {
38517
+ key: "star",
38518
+ group: "sensing",
38519
+ synonyms: [
38520
+ "preferito",
38521
+ "stella",
38522
+ "importante"
38523
+ ]
38524
+ },
38525
+ {
38526
+ key: "sparkles",
38527
+ group: "sensing",
38528
+ synonyms: [
38529
+ "automatico",
38530
+ "intelligente",
38531
+ "ai",
38532
+ "scena"
38533
+ ]
38534
+ },
38535
+ {
38536
+ key: "house",
38537
+ group: "place",
38538
+ synonyms: [
38539
+ "casa",
38540
+ "abitazione",
38541
+ "home"
38542
+ ]
38543
+ },
38544
+ {
38545
+ key: "sofa",
38546
+ group: "place",
38547
+ synonyms: [
38548
+ "salone",
38549
+ "soggiorno",
38550
+ "divano",
38551
+ "living"
38552
+ ]
38553
+ },
38554
+ {
38555
+ key: "bed-double",
38556
+ group: "place",
38557
+ synonyms: [
38558
+ "camera",
38559
+ "letto",
38560
+ "matrimoniale"
38561
+ ]
38562
+ },
38563
+ {
38564
+ key: "bed",
38565
+ group: "place",
38566
+ synonyms: [
38567
+ "cameretta",
38568
+ "letto",
38569
+ "singolo"
38570
+ ]
38571
+ },
38572
+ {
38573
+ key: "warehouse",
38574
+ group: "place",
38575
+ synonyms: [
38576
+ "magazzino",
38577
+ "capanno",
38578
+ "deposito",
38579
+ "cantina"
38580
+ ]
38581
+ },
38582
+ {
38583
+ key: "trees",
38584
+ group: "place",
38585
+ synonyms: [
38586
+ "giardino",
38587
+ "alberi",
38588
+ "esterno",
38589
+ "parco"
38590
+ ]
38591
+ },
38592
+ {
38593
+ key: "tree-pine",
38594
+ group: "place",
38595
+ synonyms: [
38596
+ "albero",
38597
+ "pino",
38598
+ "giardino"
38599
+ ]
38600
+ },
38601
+ {
38602
+ key: "fence",
38603
+ group: "place",
38604
+ synonyms: [
38605
+ "recinto",
38606
+ "cancello",
38607
+ "staccionata",
38608
+ "perimetro"
38609
+ ]
38610
+ },
38611
+ {
38612
+ key: "store",
38613
+ group: "place",
38614
+ synonyms: [
38615
+ "negozio",
38616
+ "ingresso",
38617
+ "vetrina"
38618
+ ]
38619
+ },
38620
+ {
38621
+ key: "building",
38622
+ group: "place",
38623
+ synonyms: ["edificio", "palazzo"]
38624
+ },
38625
+ {
38626
+ key: "building-2",
38627
+ group: "place",
38628
+ synonyms: [
38629
+ "edificio",
38630
+ "uffici",
38631
+ "condominio"
38632
+ ]
38633
+ },
38634
+ {
38635
+ key: "mountain",
38636
+ group: "place",
38637
+ synonyms: [
38638
+ "montagna",
38639
+ "esterno",
38640
+ "panorama"
38641
+ ]
38642
+ },
38643
+ {
38644
+ key: "umbrella",
38645
+ group: "place",
38646
+ synonyms: [
38647
+ "tenda",
38648
+ "markise",
38649
+ "ombrellone",
38650
+ "pergola",
38651
+ "veranda"
38652
+ ]
38653
+ },
38654
+ {
38655
+ key: "dumbbell",
38656
+ group: "place",
38657
+ synonyms: ["palestra", "attrezzi"]
38658
+ },
38659
+ {
38660
+ key: "wrench",
38661
+ group: "place",
38662
+ synonyms: [
38663
+ "officina",
38664
+ "manutenzione",
38665
+ "attrezzi",
38666
+ "utenze"
38667
+ ]
38668
+ },
38669
+ {
38670
+ key: "hammer",
38671
+ group: "place",
38672
+ synonyms: [
38673
+ "officina",
38674
+ "martello",
38675
+ "lavori"
38676
+ ]
38677
+ },
38678
+ {
38679
+ key: "drill",
38680
+ group: "place",
38681
+ synonyms: [
38682
+ "officina",
38683
+ "trapano",
38684
+ "lavori"
38685
+ ]
38686
+ },
38687
+ {
38688
+ key: "waves-ladder",
38689
+ group: "place",
38690
+ synonyms: ["piscina", "vasca"]
38691
+ },
38692
+ {
38693
+ key: "circle-parking",
38694
+ group: "place",
38695
+ synonyms: [
38696
+ "parcheggio",
38697
+ "posto",
38698
+ "auto",
38699
+ "garage"
38700
+ ]
38701
+ },
38702
+ {
38703
+ key: "route",
38704
+ group: "place",
38705
+ synonyms: [
38706
+ "percorso",
38707
+ "strada",
38708
+ "vialetto",
38709
+ "tragitto"
38710
+ ]
38711
+ },
38712
+ {
38713
+ key: "car",
38714
+ group: "life",
38715
+ synonyms: [
38716
+ "auto",
38717
+ "macchina",
38718
+ "veicolo",
38719
+ "parcheggio"
38720
+ ]
38721
+ },
38722
+ {
38723
+ key: "bike",
38724
+ group: "life",
38725
+ synonyms: [
38726
+ "bicicletta",
38727
+ "bici",
38728
+ "moto"
38729
+ ]
38730
+ },
38731
+ {
38732
+ key: "bus",
38733
+ group: "life",
38734
+ synonyms: ["autobus", "pullman"]
38735
+ },
38736
+ {
38737
+ key: "truck",
38738
+ group: "life",
38739
+ synonyms: [
38740
+ "camion",
38741
+ "furgone",
38742
+ "consegna"
38743
+ ]
38744
+ },
38745
+ {
38746
+ key: "plane",
38747
+ group: "life",
38748
+ synonyms: ["aereo"]
38749
+ },
38750
+ {
38751
+ key: "train-front",
38752
+ group: "life",
38753
+ synonyms: ["treno"]
38754
+ },
38755
+ {
38756
+ key: "sailboat",
38757
+ group: "life",
38758
+ synonyms: ["barca", "imbarcazione"]
38759
+ },
38760
+ {
38761
+ key: "dog",
38762
+ group: "life",
38763
+ synonyms: ["cane", "animale"]
38764
+ },
38765
+ {
38766
+ key: "cat",
38767
+ group: "life",
38768
+ synonyms: ["gatto", "animale"]
38769
+ },
38770
+ {
38771
+ key: "bird",
38772
+ group: "life",
38773
+ synonyms: ["uccello", "animale"]
38774
+ },
38775
+ {
38776
+ key: "paw-print",
38777
+ group: "life",
38778
+ synonyms: [
38779
+ "animale",
38780
+ "zampa",
38781
+ "pet"
38782
+ ]
38783
+ },
38784
+ {
38785
+ key: "baby",
38786
+ group: "life",
38787
+ synonyms: [
38788
+ "neonato",
38789
+ "bimbo",
38790
+ "cameretta",
38791
+ "babymonitor"
38792
+ ]
38793
+ },
38794
+ {
38795
+ key: "user",
38796
+ group: "life",
38797
+ synonyms: ["persona", "utente"]
38798
+ },
38799
+ {
38800
+ key: "users",
38801
+ group: "life",
38802
+ synonyms: [
38803
+ "persone",
38804
+ "gruppo",
38805
+ "famiglia"
38806
+ ]
38807
+ },
38808
+ {
38809
+ key: "person-standing",
38810
+ group: "life",
38811
+ synonyms: [
38812
+ "persona",
38813
+ "piedi",
38814
+ "presenza"
38815
+ ]
38816
+ }
38817
+ ];
38818
+ DISPLAY_ICON_VOCABULARY.map((e) => e.key);
38819
+ DISPLAY_ICON_VOCABULARY.filter((e) => e.aliasOf === void 0).map((e) => e.key);
38820
+ new Map(DISPLAY_ICON_VOCABULARY.map((e) => [e.key, e]));
36084
38821
  Object.freeze({
38822
+ "accessories.getStatus": {
38823
+ capName: "accessories",
38824
+ capScope: "device",
38825
+ addonId: null,
38826
+ access: "view"
38827
+ },
36085
38828
  "accessories.setChildHidden": {
36086
38829
  capName: "accessories",
36087
38830
  capScope: "device",
@@ -36364,6 +39107,12 @@ Object.freeze({
36364
39107
  addonId: null,
36365
39108
  access: "view"
36366
39109
  },
39110
+ "airQualitySensor.getStatus": {
39111
+ capName: "air-quality-sensor",
39112
+ capScope: "device",
39113
+ addonId: null,
39114
+ access: "view"
39115
+ },
36367
39116
  "alarmPanel.arm": {
36368
39117
  capName: "alarm-panel",
36369
39118
  capScope: "device",
@@ -36376,6 +39125,12 @@ Object.freeze({
36376
39125
  addonId: null,
36377
39126
  access: "create"
36378
39127
  },
39128
+ "alarmPanel.getStatus": {
39129
+ capName: "alarm-panel",
39130
+ capScope: "device",
39131
+ addonId: null,
39132
+ access: "view"
39133
+ },
36379
39134
  "alarmPanel.trigger": {
36380
39135
  capName: "alarm-panel",
36381
39136
  capScope: "device",
@@ -36424,6 +39179,30 @@ Object.freeze({
36424
39179
  addonId: null,
36425
39180
  access: "create"
36426
39181
  },
39182
+ "ambientLightSensor.getStatus": {
39183
+ capName: "ambient-light-sensor",
39184
+ capScope: "device",
39185
+ addonId: null,
39186
+ access: "view"
39187
+ },
39188
+ "audioAnalysis.applyDeviceSettingsPatch": {
39189
+ capName: "audio-analysis",
39190
+ capScope: "device",
39191
+ addonId: null,
39192
+ access: "create"
39193
+ },
39194
+ "audioAnalysis.getDeviceLiveContribution": {
39195
+ capName: "audio-analysis",
39196
+ capScope: "device",
39197
+ addonId: null,
39198
+ access: "view"
39199
+ },
39200
+ "audioAnalysis.getDeviceSettingsContribution": {
39201
+ capName: "audio-analysis",
39202
+ capScope: "device",
39203
+ addonId: null,
39204
+ access: "view"
39205
+ },
36427
39206
  "audioAnalysis.resolveDeviceSettings": {
36428
39207
  capName: "audio-analysis",
36429
39208
  capScope: "device",
@@ -36586,6 +39365,12 @@ Object.freeze({
36586
39365
  addonId: null,
36587
39366
  access: "create"
36588
39367
  },
39368
+ "automationControl.getStatus": {
39369
+ capName: "automation-control",
39370
+ capScope: "device",
39371
+ addonId: null,
39372
+ access: "view"
39373
+ },
36589
39374
  "automationControl.trigger": {
36590
39375
  capName: "automation-control",
36591
39376
  capScope: "device",
@@ -36682,12 +39467,30 @@ Object.freeze({
36682
39467
  addonId: null,
36683
39468
  access: "create"
36684
39469
  },
39470
+ "battery.getStatus": {
39471
+ capName: "battery",
39472
+ capScope: "device",
39473
+ addonId: null,
39474
+ access: "view"
39475
+ },
36685
39476
  "battery.wakeForStream": {
36686
39477
  capName: "battery",
36687
39478
  capScope: "device",
36688
39479
  addonId: null,
36689
39480
  access: "create"
36690
39481
  },
39482
+ "binary.getStatus": {
39483
+ capName: "binary",
39484
+ capScope: "device",
39485
+ addonId: null,
39486
+ access: "view"
39487
+ },
39488
+ "brightness.getStatus": {
39489
+ capName: "brightness",
39490
+ capScope: "device",
39491
+ addonId: null,
39492
+ access: "view"
39493
+ },
36691
39494
  "brightness.setBrightness": {
36692
39495
  capName: "brightness",
36693
39496
  capScope: "device",
@@ -36802,6 +39605,30 @@ Object.freeze({
36802
39605
  addonId: null,
36803
39606
  access: "view"
36804
39607
  },
39608
+ "cameraCredentials.getStatus": {
39609
+ capName: "camera-credentials",
39610
+ capScope: "device",
39611
+ addonId: null,
39612
+ access: "view"
39613
+ },
39614
+ "cameraGridLayout.applyDeviceSettingsPatch": {
39615
+ capName: "camera-grid-layout",
39616
+ capScope: "device",
39617
+ addonId: null,
39618
+ access: "create"
39619
+ },
39620
+ "cameraGridLayout.getDeviceLiveContribution": {
39621
+ capName: "camera-grid-layout",
39622
+ capScope: "device",
39623
+ addonId: null,
39624
+ access: "view"
39625
+ },
39626
+ "cameraGridLayout.getDeviceSettingsContribution": {
39627
+ capName: "camera-grid-layout",
39628
+ capScope: "device",
39629
+ addonId: null,
39630
+ access: "view"
39631
+ },
36805
39632
  "cameraGridLayout.getLayout": {
36806
39633
  capName: "camera-grid-layout",
36807
39634
  capScope: "device",
@@ -36814,6 +39641,24 @@ Object.freeze({
36814
39641
  addonId: null,
36815
39642
  access: "create"
36816
39643
  },
39644
+ "cameraPipelineConfig.applyDeviceSettingsPatch": {
39645
+ capName: "camera-pipeline-config",
39646
+ capScope: "device",
39647
+ addonId: null,
39648
+ access: "create"
39649
+ },
39650
+ "cameraPipelineConfig.getDeviceLiveContribution": {
39651
+ capName: "camera-pipeline-config",
39652
+ capScope: "device",
39653
+ addonId: null,
39654
+ access: "view"
39655
+ },
39656
+ "cameraPipelineConfig.getDeviceSettingsContribution": {
39657
+ capName: "camera-pipeline-config",
39658
+ capScope: "device",
39659
+ addonId: null,
39660
+ access: "view"
39661
+ },
36817
39662
  "cameraStreams.getBrokerStreams": {
36818
39663
  capName: "camera-streams",
36819
39664
  capScope: "device",
@@ -36844,6 +39689,18 @@ Object.freeze({
36844
39689
  addonId: null,
36845
39690
  access: "view"
36846
39691
  },
39692
+ "carbonMonoxide.getStatus": {
39693
+ capName: "carbon-monoxide",
39694
+ capScope: "device",
39695
+ addonId: null,
39696
+ access: "view"
39697
+ },
39698
+ "climateControl.getStatus": {
39699
+ capName: "climate-control",
39700
+ capScope: "device",
39701
+ addonId: null,
39702
+ access: "view"
39703
+ },
36847
39704
  "climateControl.setFanMode": {
36848
39705
  capName: "climate-control",
36849
39706
  capScope: "device",
@@ -36892,6 +39749,12 @@ Object.freeze({
36892
39749
  addonId: null,
36893
39750
  access: "create"
36894
39751
  },
39752
+ "color.getStatus": {
39753
+ capName: "color",
39754
+ capScope: "device",
39755
+ addonId: null,
39756
+ access: "view"
39757
+ },
36895
39758
  "color.setColor": {
36896
39759
  capName: "color",
36897
39760
  capScope: "device",
@@ -36910,12 +39773,36 @@ Object.freeze({
36910
39773
  addonId: null,
36911
39774
  access: "create"
36912
39775
  },
39776
+ "connectivity.getStatus": {
39777
+ capName: "connectivity",
39778
+ capScope: "device",
39779
+ addonId: null,
39780
+ access: "view"
39781
+ },
39782
+ "consumables.getStatus": {
39783
+ capName: "consumables",
39784
+ capScope: "device",
39785
+ addonId: null,
39786
+ access: "view"
39787
+ },
36913
39788
  "consumables.reset": {
36914
39789
  capName: "consumables",
36915
39790
  capScope: "device",
36916
39791
  addonId: null,
36917
39792
  access: "delete"
36918
39793
  },
39794
+ "contact.getStatus": {
39795
+ capName: "contact",
39796
+ capScope: "device",
39797
+ addonId: null,
39798
+ access: "view"
39799
+ },
39800
+ "control.getStatus": {
39801
+ capName: "control",
39802
+ capScope: "device",
39803
+ addonId: null,
39804
+ access: "view"
39805
+ },
36919
39806
  "control.setValue": {
36920
39807
  capName: "control",
36921
39808
  capScope: "device",
@@ -36982,6 +39869,12 @@ Object.freeze({
36982
39869
  addonId: null,
36983
39870
  access: "create"
36984
39871
  },
39872
+ "cover.getStatus": {
39873
+ capName: "cover",
39874
+ capScope: "device",
39875
+ addonId: null,
39876
+ access: "view"
39877
+ },
36985
39878
  "cover.open": {
36986
39879
  capName: "cover",
36987
39880
  capScope: "device",
@@ -37114,6 +40007,12 @@ Object.freeze({
37114
40007
  addonId: null,
37115
40008
  access: "view"
37116
40009
  },
40010
+ "dayNight.getStatus": {
40011
+ capName: "day-night",
40012
+ capScope: "device",
40013
+ addonId: null,
40014
+ access: "view"
40015
+ },
37117
40016
  "dayNight.setSettings": {
37118
40017
  capName: "day-night",
37119
40018
  capScope: "device",
@@ -37204,6 +40103,30 @@ Object.freeze({
37204
40103
  addonId: null,
37205
40104
  access: "view"
37206
40105
  },
40106
+ "detectionPipeline.applyDeviceSettingsPatch": {
40107
+ capName: "detection-pipeline",
40108
+ capScope: "device",
40109
+ addonId: null,
40110
+ access: "create"
40111
+ },
40112
+ "detectionPipeline.getDeviceLiveContribution": {
40113
+ capName: "detection-pipeline",
40114
+ capScope: "device",
40115
+ addonId: null,
40116
+ access: "view"
40117
+ },
40118
+ "detectionPipeline.getDeviceSettingsContribution": {
40119
+ capName: "detection-pipeline",
40120
+ capScope: "device",
40121
+ addonId: null,
40122
+ access: "view"
40123
+ },
40124
+ "deviceAdminLink.getAdminLink": {
40125
+ capName: "device-admin-link",
40126
+ capScope: "device",
40127
+ addonId: null,
40128
+ access: "view"
40129
+ },
37207
40130
  "deviceAdoption.adopt": {
37208
40131
  capName: "device-adoption",
37209
40132
  capScope: "system",
@@ -37216,6 +40139,12 @@ Object.freeze({
37216
40139
  addonId: null,
37217
40140
  access: "view"
37218
40141
  },
40142
+ "deviceAdoption.getStatus": {
40143
+ capName: "device-adoption",
40144
+ capScope: "system",
40145
+ addonId: null,
40146
+ access: "view"
40147
+ },
37219
40148
  "deviceAdoption.listCandidateFilters": {
37220
40149
  capName: "device-adoption",
37221
40150
  capScope: "system",
@@ -37252,6 +40181,12 @@ Object.freeze({
37252
40181
  addonId: null,
37253
40182
  access: "create"
37254
40183
  },
40184
+ "deviceDiscovery.getStatus": {
40185
+ capName: "device-discovery",
40186
+ capScope: "device",
40187
+ addonId: null,
40188
+ access: "view"
40189
+ },
37255
40190
  "deviceDiscovery.listDiscovered": {
37256
40191
  capName: "device-discovery",
37257
40192
  capScope: "device",
@@ -37270,12 +40205,30 @@ Object.freeze({
37270
40205
  addonId: null,
37271
40206
  access: "create"
37272
40207
  },
40208
+ "deviceExport.applyDeviceSettingsPatch": {
40209
+ capName: "device-export",
40210
+ capScope: "system",
40211
+ addonId: null,
40212
+ access: "create"
40213
+ },
37273
40214
  "deviceExport.exposeDevice": {
37274
40215
  capName: "device-export",
37275
40216
  capScope: "system",
37276
40217
  addonId: null,
37277
40218
  access: "create"
37278
40219
  },
40220
+ "deviceExport.getDeviceLiveContribution": {
40221
+ capName: "device-export",
40222
+ capScope: "system",
40223
+ addonId: null,
40224
+ access: "view"
40225
+ },
40226
+ "deviceExport.getDeviceSettingsContribution": {
40227
+ capName: "device-export",
40228
+ capScope: "system",
40229
+ addonId: null,
40230
+ access: "view"
40231
+ },
37279
40232
  "deviceExport.getStatus": {
37280
40233
  capName: "device-export",
37281
40234
  capScope: "system",
@@ -37372,6 +40325,12 @@ Object.freeze({
37372
40325
  addonId: null,
37373
40326
  access: "create"
37374
40327
  },
40328
+ "deviceManager.applyDeviceSettingsPatch": {
40329
+ capName: "device-manager",
40330
+ capScope: "system",
40331
+ addonId: null,
40332
+ access: "create"
40333
+ },
37375
40334
  "deviceManager.applyInitialMeta": {
37376
40335
  capName: "device-manager",
37377
40336
  capScope: "system",
@@ -37474,6 +40433,12 @@ Object.freeze({
37474
40433
  addonId: null,
37475
40434
  access: "view"
37476
40435
  },
40436
+ "deviceManager.getDeviceLiveContribution": {
40437
+ capName: "device-manager",
40438
+ capScope: "system",
40439
+ addonId: null,
40440
+ access: "view"
40441
+ },
37477
40442
  "deviceManager.getDeviceLiveInfoAggregate": {
37478
40443
  capName: "device-manager",
37479
40444
  capScope: "system",
@@ -37486,6 +40451,12 @@ Object.freeze({
37486
40451
  addonId: null,
37487
40452
  access: "view"
37488
40453
  },
40454
+ "deviceManager.getDeviceSettingsContribution": {
40455
+ capName: "device-manager",
40456
+ capScope: "system",
40457
+ addonId: null,
40458
+ access: "view"
40459
+ },
37489
40460
  "deviceManager.getDeviceStatusAggregate": {
37490
40461
  capName: "device-manager",
37491
40462
  capScope: "system",
@@ -37978,6 +40949,36 @@ Object.freeze({
37978
40949
  addonId: null,
37979
40950
  access: "create"
37980
40951
  },
40952
+ "deviceStatus.getStatus": {
40953
+ capName: "device-status",
40954
+ capScope: "device",
40955
+ addonId: null,
40956
+ access: "view"
40957
+ },
40958
+ "doorbell.applyDeviceSettingsPatch": {
40959
+ capName: "doorbell",
40960
+ capScope: "device",
40961
+ addonId: null,
40962
+ access: "create"
40963
+ },
40964
+ "doorbell.getDeviceLiveContribution": {
40965
+ capName: "doorbell",
40966
+ capScope: "device",
40967
+ addonId: null,
40968
+ access: "view"
40969
+ },
40970
+ "doorbell.getDeviceSettingsContribution": {
40971
+ capName: "doorbell",
40972
+ capScope: "device",
40973
+ addonId: null,
40974
+ access: "view"
40975
+ },
40976
+ "doorbell.getStatus": {
40977
+ capName: "doorbell",
40978
+ capScope: "device",
40979
+ addonId: null,
40980
+ access: "view"
40981
+ },
37981
40982
  "embeddingEncoder.encode": {
37982
40983
  capName: "embedding-encoder",
37983
40984
  capScope: "system",
@@ -37996,6 +40997,18 @@ Object.freeze({
37996
40997
  addonId: null,
37997
40998
  access: "view"
37998
40999
  },
41000
+ "enumSensor.getStatus": {
41001
+ capName: "enum-sensor",
41002
+ capScope: "device",
41003
+ addonId: null,
41004
+ access: "view"
41005
+ },
41006
+ "eventEmitter.getStatus": {
41007
+ capName: "event-emitter",
41008
+ capScope: "device",
41009
+ addonId: null,
41010
+ access: "view"
41011
+ },
37999
41012
  "events.getEventClipUrl": {
38000
41013
  capName: "events",
38001
41014
  capScope: "device",
@@ -38116,6 +41129,12 @@ Object.freeze({
38116
41129
  addonId: null,
38117
41130
  access: "view"
38118
41131
  },
41132
+ "fanControl.getStatus": {
41133
+ capName: "fan-control",
41134
+ capScope: "device",
41135
+ addonId: null,
41136
+ access: "view"
41137
+ },
38119
41138
  "fanControl.setDirection": {
38120
41139
  capName: "fan-control",
38121
41140
  capScope: "device",
@@ -38140,6 +41159,12 @@ Object.freeze({
38140
41159
  addonId: null,
38141
41160
  access: "create"
38142
41161
  },
41162
+ "featureProbe.getStatus": {
41163
+ capName: "feature-probe",
41164
+ capScope: "device",
41165
+ addonId: null,
41166
+ access: "view"
41167
+ },
38143
41168
  "filesystemBrowse.browse": {
38144
41169
  capName: "filesystem-browse",
38145
41170
  capScope: "system",
@@ -38158,6 +41183,24 @@ Object.freeze({
38158
41183
  addonId: null,
38159
41184
  access: "view"
38160
41185
  },
41186
+ "flood.getStatus": {
41187
+ capName: "flood",
41188
+ capScope: "device",
41189
+ addonId: null,
41190
+ access: "view"
41191
+ },
41192
+ "gas.getStatus": {
41193
+ capName: "gas",
41194
+ capScope: "device",
41195
+ addonId: null,
41196
+ access: "view"
41197
+ },
41198
+ "humidifier.getStatus": {
41199
+ capName: "humidifier",
41200
+ capScope: "device",
41201
+ addonId: null,
41202
+ access: "view"
41203
+ },
38161
41204
  "humidifier.setMode": {
38162
41205
  capName: "humidifier",
38163
41206
  capScope: "device",
@@ -38176,12 +41219,30 @@ Object.freeze({
38176
41219
  addonId: null,
38177
41220
  access: "create"
38178
41221
  },
41222
+ "humiditySensor.getStatus": {
41223
+ capName: "humidity-sensor",
41224
+ capScope: "device",
41225
+ addonId: null,
41226
+ access: "view"
41227
+ },
41228
+ "image.getStatus": {
41229
+ capName: "image",
41230
+ capScope: "device",
41231
+ addonId: null,
41232
+ access: "view"
41233
+ },
38179
41234
  "imageSettings.getOptions": {
38180
41235
  capName: "image-settings",
38181
41236
  capScope: "device",
38182
41237
  addonId: null,
38183
41238
  access: "view"
38184
41239
  },
41240
+ "imageSettings.getStatus": {
41241
+ capName: "image-settings",
41242
+ capScope: "device",
41243
+ addonId: null,
41244
+ access: "view"
41245
+ },
38185
41246
  "imageSettings.setSettings": {
38186
41247
  capName: "image-settings",
38187
41248
  capScope: "device",
@@ -38254,6 +41315,12 @@ Object.freeze({
38254
41315
  addonId: null,
38255
41316
  access: "create"
38256
41317
  },
41318
+ "intercom.getStatus": {
41319
+ capName: "intercom",
41320
+ capScope: "device",
41321
+ addonId: null,
41322
+ access: "view"
41323
+ },
38257
41324
  "intercom.handleAnswer": {
38258
41325
  capName: "intercom",
38259
41326
  capScope: "device",
@@ -38290,6 +41357,12 @@ Object.freeze({
38290
41357
  addonId: null,
38291
41358
  access: "create"
38292
41359
  },
41360
+ "lawnMowerControl.getStatus": {
41361
+ capName: "lawn-mower-control",
41362
+ capScope: "device",
41363
+ addonId: null,
41364
+ access: "view"
41365
+ },
38293
41366
  "lawnMowerControl.pause": {
38294
41367
  capName: "lawn-mower-control",
38295
41368
  capScope: "device",
@@ -38572,6 +41645,12 @@ Object.freeze({
38572
41645
  addonId: null,
38573
41646
  access: "create"
38574
41647
  },
41648
+ "lockControl.getStatus": {
41649
+ capName: "lock-control",
41650
+ capScope: "device",
41651
+ addonId: null,
41652
+ access: "view"
41653
+ },
38575
41654
  "lockControl.lock": {
38576
41655
  capName: "lock-control",
38577
41656
  capScope: "device",
@@ -38620,6 +41699,12 @@ Object.freeze({
38620
41699
  addonId: null,
38621
41700
  access: "view"
38622
41701
  },
41702
+ "mediaPlayer.getStatus": {
41703
+ capName: "media-player",
41704
+ capScope: "device",
41705
+ addonId: null,
41706
+ access: "view"
41707
+ },
38623
41708
  "mediaPlayer.next": {
38624
41709
  capName: "media-player",
38625
41710
  capScope: "device",
@@ -38812,6 +41897,12 @@ Object.freeze({
38812
41897
  addonId: null,
38813
41898
  access: "create"
38814
41899
  },
41900
+ "motion.getStatus": {
41901
+ capName: "motion",
41902
+ capScope: "device",
41903
+ addonId: null,
41904
+ access: "view"
41905
+ },
38815
41906
  "motion.isDetected": {
38816
41907
  capName: "motion",
38817
41908
  capScope: "device",
@@ -38824,6 +41915,24 @@ Object.freeze({
38824
41915
  addonId: null,
38825
41916
  access: "create"
38826
41917
  },
41918
+ "motionDetection.applyDeviceSettingsPatch": {
41919
+ capName: "motion-detection",
41920
+ capScope: "device",
41921
+ addonId: null,
41922
+ access: "create"
41923
+ },
41924
+ "motionDetection.getDeviceLiveContribution": {
41925
+ capName: "motion-detection",
41926
+ capScope: "device",
41927
+ addonId: null,
41928
+ access: "view"
41929
+ },
41930
+ "motionDetection.getDeviceSettingsContribution": {
41931
+ capName: "motion-detection",
41932
+ capScope: "device",
41933
+ addonId: null,
41934
+ access: "view"
41935
+ },
38827
41936
  "motionDetection.removeCamera": {
38828
41937
  capName: "motion-detection",
38829
41938
  capScope: "device",
@@ -38836,6 +41945,12 @@ Object.freeze({
38836
41945
  addonId: null,
38837
41946
  access: "delete"
38838
41947
  },
41948
+ "motionTrigger.getStatus": {
41949
+ capName: "motion-trigger",
41950
+ capScope: "device",
41951
+ addonId: null,
41952
+ access: "view"
41953
+ },
38839
41954
  "motionTrigger.setMotionTrigger": {
38840
41955
  capName: "motion-trigger",
38841
41956
  capScope: "device",
@@ -38848,6 +41963,12 @@ Object.freeze({
38848
41963
  addonId: null,
38849
41964
  access: "view"
38850
41965
  },
41966
+ "motionZones.getStatus": {
41967
+ capName: "motion-zones",
41968
+ capScope: "device",
41969
+ addonId: null,
41970
+ access: "view"
41971
+ },
38851
41972
  "motionZones.setZone": {
38852
41973
  capName: "motion-zones",
38853
41974
  capScope: "device",
@@ -38908,6 +42029,12 @@ Object.freeze({
38908
42029
  addonId: null,
38909
42030
  access: "view"
38910
42031
  },
42032
+ "nativeObjectDetection.getStatus": {
42033
+ capName: "native-object-detection",
42034
+ capScope: "device",
42035
+ addonId: null,
42036
+ access: "view"
42037
+ },
38911
42038
  "nativeObjectDetection.setEnabled": {
38912
42039
  capName: "native-object-detection",
38913
42040
  capScope: "device",
@@ -38926,6 +42053,12 @@ Object.freeze({
38926
42053
  addonId: null,
38927
42054
  access: "view"
38928
42055
  },
42056
+ "navigation.getStatus": {
42057
+ capName: "navigation",
42058
+ capScope: "device",
42059
+ addonId: null,
42060
+ access: "view"
42061
+ },
38929
42062
  "navigation.goToPoint": {
38930
42063
  capName: "navigation",
38931
42064
  capScope: "device",
@@ -39010,6 +42143,12 @@ Object.freeze({
39010
42143
  addonId: null,
39011
42144
  access: "create"
39012
42145
  },
42146
+ "networkLink.getStatus": {
42147
+ capName: "network-link",
42148
+ capScope: "device",
42149
+ addonId: null,
42150
+ access: "view"
42151
+ },
39013
42152
  "networkQuality.getAllStats": {
39014
42153
  capName: "network-quality",
39015
42154
  capScope: "system",
@@ -39268,18 +42407,36 @@ Object.freeze({
39268
42407
  addonId: null,
39269
42408
  access: "create"
39270
42409
  },
42410
+ "notifier.getStatus": {
42411
+ capName: "notifier",
42412
+ capScope: "device",
42413
+ addonId: null,
42414
+ access: "view"
42415
+ },
39271
42416
  "notifier.send": {
39272
42417
  capName: "notifier",
39273
42418
  capScope: "device",
39274
42419
  addonId: null,
39275
42420
  access: "create"
39276
42421
  },
42422
+ "numericSensor.getStatus": {
42423
+ capName: "numeric-sensor",
42424
+ capScope: "device",
42425
+ addonId: null,
42426
+ access: "view"
42427
+ },
39277
42428
  "oauthIntegration.getDescriptor": {
39278
42429
  capName: "oauth-integration",
39279
42430
  capScope: "system",
39280
42431
  addonId: null,
39281
42432
  access: "view"
39282
42433
  },
42434
+ "osd.getStatus": {
42435
+ capName: "osd",
42436
+ capScope: "device",
42437
+ addonId: null,
42438
+ access: "view"
42439
+ },
39283
42440
  "osd.setOverlay": {
39284
42441
  capName: "osd",
39285
42442
  capScope: "device",
@@ -39352,6 +42509,12 @@ Object.freeze({
39352
42509
  addonId: null,
39353
42510
  access: "create"
39354
42511
  },
42512
+ "petFeeder.getStatus": {
42513
+ capName: "pet-feeder",
42514
+ capScope: "device",
42515
+ addonId: null,
42516
+ access: "view"
42517
+ },
39355
42518
  "petFeeder.markFoodReplenished": {
39356
42519
  capName: "pet-feeder",
39357
42520
  capScope: "device",
@@ -39394,6 +42557,12 @@ Object.freeze({
39394
42557
  addonId: null,
39395
42558
  access: "create"
39396
42559
  },
42560
+ "pipelineAnalytics.applyDeviceSettingsPatch": {
42561
+ capName: "pipeline-analytics",
42562
+ capScope: "device",
42563
+ addonId: null,
42564
+ access: "create"
42565
+ },
39397
42566
  "pipelineAnalytics.cancelRelocateMedia": {
39398
42567
  capName: "pipeline-analytics",
39399
42568
  capScope: "device",
@@ -39460,6 +42629,18 @@ Object.freeze({
39460
42629
  addonId: null,
39461
42630
  access: "view"
39462
42631
  },
42632
+ "pipelineAnalytics.getDeviceLiveContribution": {
42633
+ capName: "pipeline-analytics",
42634
+ capScope: "device",
42635
+ addonId: null,
42636
+ access: "view"
42637
+ },
42638
+ "pipelineAnalytics.getDeviceSettingsContribution": {
42639
+ capName: "pipeline-analytics",
42640
+ capScope: "device",
42641
+ addonId: null,
42642
+ access: "view"
42643
+ },
39463
42644
  "pipelineAnalytics.getEventDensity": {
39464
42645
  capName: "pipeline-analytics",
39465
42646
  capScope: "device",
@@ -39670,6 +42851,12 @@ Object.freeze({
39670
42851
  addonId: null,
39671
42852
  access: "view"
39672
42853
  },
42854
+ "pipelineAnalytics.ownersWithMedia": {
42855
+ capName: "pipeline-analytics",
42856
+ capScope: "device",
42857
+ addonId: null,
42858
+ access: "view"
42859
+ },
39673
42860
  "pipelineAnalytics.pauseForStorageMigration": {
39674
42861
  capName: "pipeline-analytics",
39675
42862
  capScope: "device",
@@ -40024,6 +43211,12 @@ Object.freeze({
40024
43211
  addonId: null,
40025
43212
  access: "view"
40026
43213
  },
43214
+ "pipelineOrchestrator.applyDeviceSettingsPatch": {
43215
+ capName: "pipeline-orchestrator",
43216
+ capScope: "system",
43217
+ addonId: null,
43218
+ access: "create"
43219
+ },
40027
43220
  "pipelineOrchestrator.assignAudio": {
40028
43221
  capName: "pipeline-orchestrator",
40029
43222
  capScope: "system",
@@ -40114,6 +43307,18 @@ Object.freeze({
40114
43307
  addonId: null,
40115
43308
  access: "view"
40116
43309
  },
43310
+ "pipelineOrchestrator.getDeviceLiveContribution": {
43311
+ capName: "pipeline-orchestrator",
43312
+ capScope: "system",
43313
+ addonId: null,
43314
+ access: "view"
43315
+ },
43316
+ "pipelineOrchestrator.getDeviceSettingsContribution": {
43317
+ capName: "pipeline-orchestrator",
43318
+ capScope: "system",
43319
+ addonId: null,
43320
+ access: "view"
43321
+ },
40117
43322
  "pipelineOrchestrator.getGlobalMetrics": {
40118
43323
  capName: "pipeline-orchestrator",
40119
43324
  capScope: "system",
@@ -40522,12 +43727,36 @@ Object.freeze({
40522
43727
  addonId: null,
40523
43728
  access: "view"
40524
43729
  },
43730
+ "powerMeter.getStatus": {
43731
+ capName: "power-meter",
43732
+ capScope: "device",
43733
+ addonId: null,
43734
+ access: "view"
43735
+ },
43736
+ "presence.getStatus": {
43737
+ capName: "presence",
43738
+ capScope: "device",
43739
+ addonId: null,
43740
+ access: "view"
43741
+ },
43742
+ "pressureSensor.getStatus": {
43743
+ capName: "pressure-sensor",
43744
+ capScope: "device",
43745
+ addonId: null,
43746
+ access: "view"
43747
+ },
40525
43748
  "privacyMask.getOptions": {
40526
43749
  capName: "privacy-mask",
40527
43750
  capScope: "device",
40528
43751
  addonId: null,
40529
43752
  access: "view"
40530
43753
  },
43754
+ "privacyMask.getStatus": {
43755
+ capName: "privacy-mask",
43756
+ capScope: "device",
43757
+ addonId: null,
43758
+ access: "view"
43759
+ },
40531
43760
  "privacyMask.setAudioEnabled": {
40532
43761
  capName: "privacy-mask",
40533
43762
  capScope: "device",
@@ -40588,6 +43817,12 @@ Object.freeze({
40588
43817
  addonId: null,
40589
43818
  access: "view"
40590
43819
  },
43820
+ "ptz.getStatus": {
43821
+ capName: "ptz",
43822
+ capScope: "device",
43823
+ addonId: null,
43824
+ access: "view"
43825
+ },
40591
43826
  "ptz.goHome": {
40592
43827
  capName: "ptz",
40593
43828
  capScope: "device",
@@ -40666,6 +43901,12 @@ Object.freeze({
40666
43901
  addonId: null,
40667
43902
  access: "create"
40668
43903
  },
43904
+ "recording.applyDeviceSettingsPatch": {
43905
+ capName: "recording",
43906
+ capScope: "system",
43907
+ addonId: null,
43908
+ access: "create"
43909
+ },
40669
43910
  "recording.cancelRelocateJob": {
40670
43911
  capName: "recording",
40671
43912
  capScope: "system",
@@ -40714,6 +43955,18 @@ Object.freeze({
40714
43955
  addonId: null,
40715
43956
  access: "view"
40716
43957
  },
43958
+ "recording.getDeviceLiveContribution": {
43959
+ capName: "recording",
43960
+ capScope: "system",
43961
+ addonId: null,
43962
+ access: "view"
43963
+ },
43964
+ "recording.getDeviceSettingsContribution": {
43965
+ capName: "recording",
43966
+ capScope: "system",
43967
+ addonId: null,
43968
+ access: "view"
43969
+ },
40717
43970
  "recording.getPlacement": {
40718
43971
  capName: "recording",
40719
43972
  capScope: "system",
@@ -40732,6 +43985,12 @@ Object.freeze({
40732
43985
  addonId: null,
40733
43986
  access: "view"
40734
43987
  },
43988
+ "recording.getStatus": {
43989
+ capName: "recording",
43990
+ capScope: "system",
43991
+ addonId: null,
43992
+ access: "view"
43993
+ },
40735
43994
  "recording.getStorageMigrationMoveStatus": {
40736
43995
  capName: "recording",
40737
43996
  capScope: "system",
@@ -40906,6 +44165,24 @@ Object.freeze({
40906
44165
  addonId: null,
40907
44166
  access: "view"
40908
44167
  },
44168
+ "recordingOnboard.getOptions": {
44169
+ capName: "recording-onboard",
44170
+ capScope: "device",
44171
+ addonId: null,
44172
+ access: "view"
44173
+ },
44174
+ "recordingOnboard.getStatus": {
44175
+ capName: "recording-onboard",
44176
+ capScope: "device",
44177
+ addonId: null,
44178
+ access: "view"
44179
+ },
44180
+ "recordingOnboard.setSettings": {
44181
+ capName: "recording-onboard",
44182
+ capScope: "device",
44183
+ addonId: null,
44184
+ access: "create"
44185
+ },
40909
44186
  "recordingSignal.getStatus": {
40910
44187
  capName: "recording-signal",
40911
44188
  capScope: "device",
@@ -40936,6 +44213,12 @@ Object.freeze({
40936
44213
  addonId: null,
40937
44214
  access: "delete"
40938
44215
  },
44216
+ "sceneMonitor.getStatus": {
44217
+ capName: "scene-monitor",
44218
+ capScope: "device",
44219
+ addonId: null,
44220
+ access: "view"
44221
+ },
40939
44222
  "sceneMonitor.listScenes": {
40940
44223
  capName: "scene-monitor",
40941
44224
  capScope: "device",
@@ -40966,6 +44249,12 @@ Object.freeze({
40966
44249
  addonId: null,
40967
44250
  access: "create"
40968
44251
  },
44252
+ "scriptRunner.getStatus": {
44253
+ capName: "script-runner",
44254
+ capScope: "device",
44255
+ addonId: null,
44256
+ access: "view"
44257
+ },
40969
44258
  "scriptRunner.run": {
40970
44259
  capName: "script-runner",
40971
44260
  capScope: "device",
@@ -41098,6 +44387,12 @@ Object.freeze({
41098
44387
  addonId: null,
41099
44388
  access: "create"
41100
44389
  },
44390
+ "smoke.getStatus": {
44391
+ capName: "smoke",
44392
+ capScope: "device",
44393
+ addonId: null,
44394
+ access: "view"
44395
+ },
41101
44396
  "smtpProvider.getStatus": {
41102
44397
  capName: "smtp-provider",
41103
44398
  capScope: "system",
@@ -41116,12 +44411,30 @@ Object.freeze({
41116
44411
  addonId: null,
41117
44412
  access: "view"
41118
44413
  },
44414
+ "snapshot.applyDeviceSettingsPatch": {
44415
+ capName: "snapshot",
44416
+ capScope: "device",
44417
+ addonId: null,
44418
+ access: "create"
44419
+ },
41119
44420
  "snapshot.getDebugState": {
41120
44421
  capName: "snapshot",
41121
44422
  capScope: "device",
41122
44423
  addonId: null,
41123
44424
  access: "view"
41124
44425
  },
44426
+ "snapshot.getDeviceLiveContribution": {
44427
+ capName: "snapshot",
44428
+ capScope: "device",
44429
+ addonId: null,
44430
+ access: "view"
44431
+ },
44432
+ "snapshot.getDeviceSettingsContribution": {
44433
+ capName: "snapshot",
44434
+ capScope: "device",
44435
+ addonId: null,
44436
+ access: "view"
44437
+ },
41125
44438
  "snapshot.getSnapshot": {
41126
44439
  capName: "snapshot",
41127
44440
  capScope: "device",
@@ -41140,6 +44453,12 @@ Object.freeze({
41140
44453
  addonId: null,
41141
44454
  access: "view"
41142
44455
  },
44456
+ "snapshot.getStatus": {
44457
+ capName: "snapshot",
44458
+ capScope: "device",
44459
+ addonId: null,
44460
+ access: "view"
44461
+ },
41143
44462
  "snapshot.invalidateCache": {
41144
44463
  capName: "snapshot",
41145
44464
  capScope: "device",
@@ -41476,6 +44795,12 @@ Object.freeze({
41476
44795
  addonId: null,
41477
44796
  access: "create"
41478
44797
  },
44798
+ "streamBroker.applyDeviceSettingsPatch": {
44799
+ capName: "stream-broker",
44800
+ capScope: "system",
44801
+ addonId: null,
44802
+ access: "create"
44803
+ },
41479
44804
  "streamBroker.assignProfile": {
41480
44805
  capName: "stream-broker",
41481
44806
  capScope: "system",
@@ -41512,6 +44837,18 @@ Object.freeze({
41512
44837
  addonId: null,
41513
44838
  access: "view"
41514
44839
  },
44840
+ "streamBroker.getDeviceLiveContribution": {
44841
+ capName: "stream-broker",
44842
+ capScope: "system",
44843
+ addonId: null,
44844
+ access: "view"
44845
+ },
44846
+ "streamBroker.getDeviceSettingsContribution": {
44847
+ capName: "stream-broker",
44848
+ capScope: "system",
44849
+ addonId: null,
44850
+ access: "view"
44851
+ },
41515
44852
  "streamBroker.getPreBufferInfo": {
41516
44853
  capName: "stream-broker",
41517
44854
  capScope: "system",
@@ -41710,6 +45047,12 @@ Object.freeze({
41710
45047
  addonId: null,
41711
45048
  access: "view"
41712
45049
  },
45050
+ "streamParams.getStatus": {
45051
+ capName: "stream-params",
45052
+ capScope: "device",
45053
+ addonId: null,
45054
+ access: "view"
45055
+ },
41713
45056
  "streamParams.setProfile": {
41714
45057
  capName: "stream-params",
41715
45058
  capScope: "device",
@@ -41722,6 +45065,12 @@ Object.freeze({
41722
45065
  addonId: null,
41723
45066
  access: "view"
41724
45067
  },
45068
+ "switch.getStatus": {
45069
+ capName: "switch",
45070
+ capScope: "device",
45071
+ addonId: null,
45072
+ access: "view"
45073
+ },
41725
45074
  "switch.setState": {
41726
45075
  capName: "switch",
41727
45076
  capScope: "device",
@@ -41818,6 +45167,18 @@ Object.freeze({
41818
45167
  addonId: null,
41819
45168
  access: "create"
41820
45169
  },
45170
+ "tamper.getStatus": {
45171
+ capName: "tamper",
45172
+ capScope: "device",
45173
+ addonId: null,
45174
+ access: "view"
45175
+ },
45176
+ "temperatureSensor.getStatus": {
45177
+ capName: "temperature-sensor",
45178
+ capScope: "device",
45179
+ addonId: null,
45180
+ access: "view"
45181
+ },
41821
45182
  "terminalSession.adoptLegacyMonitor": {
41822
45183
  capName: "terminal-session",
41823
45184
  capScope: "system",
@@ -41914,6 +45275,12 @@ Object.freeze({
41914
45275
  addonId: null,
41915
45276
  access: "view"
41916
45277
  },
45278
+ "update.getStatus": {
45279
+ capName: "update",
45280
+ capScope: "device",
45281
+ addonId: null,
45282
+ access: "view"
45283
+ },
41917
45284
  "update.installUpdate": {
41918
45285
  capName: "update",
41919
45286
  capScope: "device",
@@ -42136,6 +45503,12 @@ Object.freeze({
42136
45503
  addonId: null,
42137
45504
  access: "create"
42138
45505
  },
45506
+ "vacuumControl.getStatus": {
45507
+ capName: "vacuum-control",
45508
+ capScope: "device",
45509
+ addonId: null,
45510
+ access: "view"
45511
+ },
42139
45512
  "vacuumControl.locate": {
42140
45513
  capName: "vacuum-control",
42141
45514
  capScope: "device",
@@ -42178,6 +45551,12 @@ Object.freeze({
42178
45551
  addonId: null,
42179
45552
  access: "create"
42180
45553
  },
45554
+ "valve.getStatus": {
45555
+ capName: "valve",
45556
+ capScope: "device",
45557
+ addonId: null,
45558
+ access: "view"
45559
+ },
42181
45560
  "valve.open": {
42182
45561
  capName: "valve",
42183
45562
  capScope: "device",
@@ -42250,6 +45629,18 @@ Object.freeze({
42250
45629
  addonId: null,
42251
45630
  access: "create"
42252
45631
  },
45632
+ "vibration.getStatus": {
45633
+ capName: "vibration",
45634
+ capScope: "device",
45635
+ addonId: null,
45636
+ access: "view"
45637
+ },
45638
+ "videoclips.dialClipStream": {
45639
+ capName: "videoclips",
45640
+ capScope: "device",
45641
+ addonId: null,
45642
+ access: "view"
45643
+ },
42253
45644
  "videoclips.getClipPlayback": {
42254
45645
  capName: "videoclips",
42255
45646
  capScope: "device",
@@ -42262,6 +45653,18 @@ Object.freeze({
42262
45653
  addonId: null,
42263
45654
  access: "view"
42264
45655
  },
45656
+ "videoclips.listSources": {
45657
+ capName: "videoclips",
45658
+ capScope: "device",
45659
+ addonId: null,
45660
+ access: "view"
45661
+ },
45662
+ "videoclips.readClipBytes": {
45663
+ capName: "videoclips",
45664
+ capScope: "device",
45665
+ addonId: null,
45666
+ access: "view"
45667
+ },
42265
45668
  "viewerUi.getStaticDir": {
42266
45669
  capName: "viewer-ui",
42267
45670
  capScope: "system",
@@ -42274,6 +45677,12 @@ Object.freeze({
42274
45677
  addonId: null,
42275
45678
  access: "view"
42276
45679
  },
45680
+ "waterHeater.getStatus": {
45681
+ capName: "water-heater",
45682
+ capScope: "device",
45683
+ addonId: null,
45684
+ access: "view"
45685
+ },
42277
45686
  "waterHeater.setAway": {
42278
45687
  capName: "water-heater",
42279
45688
  capScope: "device",
@@ -42292,6 +45701,12 @@ Object.freeze({
42292
45701
  addonId: null,
42293
45702
  access: "create"
42294
45703
  },
45704
+ "weather.getStatus": {
45705
+ capName: "weather",
45706
+ capScope: "device",
45707
+ addonId: null,
45708
+ access: "view"
45709
+ },
42295
45710
  "webrtcSession.addIceCandidate": {
42296
45711
  capName: "webrtc-session",
42297
45712
  capScope: "device",
@@ -42316,6 +45731,12 @@ Object.freeze({
42316
45731
  addonId: null,
42317
45732
  access: "view"
42318
45733
  },
45734
+ "webrtcSession.getSessionDebug": {
45735
+ capName: "webrtc-session",
45736
+ capScope: "device",
45737
+ addonId: null,
45738
+ access: "view"
45739
+ },
42319
45740
  "webrtcSession.getSessionState": {
42320
45741
  capName: "webrtc-session",
42321
45742
  capScope: "device",
@@ -42648,6 +46069,11 @@ Object.freeze({
42648
46069
  form: "single",
42649
46070
  optional: true
42650
46071
  }],
46072
+ "deviceAdminLink.getAdminLink": [{
46073
+ name: "deviceId",
46074
+ form: "single",
46075
+ optional: false
46076
+ }],
42651
46077
  "deviceAdoption.release": [{
42652
46078
  name: "camDeviceId",
42653
46079
  form: "single",
@@ -43558,6 +46984,11 @@ Object.freeze({
43558
46984
  form: "single",
43559
46985
  optional: false
43560
46986
  }],
46987
+ "pipelineAnalytics.ownersWithMedia": [{
46988
+ name: "deviceId",
46989
+ form: "single",
46990
+ optional: false
46991
+ }],
43561
46992
  "pipelineAnalytics.proposeRetrainAnnotations": [{
43562
46993
  name: "deviceId",
43563
46994
  form: "single",
@@ -44028,6 +47459,16 @@ Object.freeze({
44028
47459
  form: "single",
44029
47460
  optional: true
44030
47461
  }],
47462
+ "recordingOnboard.getOptions": [{
47463
+ name: "deviceId",
47464
+ form: "single",
47465
+ optional: false
47466
+ }],
47467
+ "recordingOnboard.setSettings": [{
47468
+ name: "deviceId",
47469
+ form: "single",
47470
+ optional: false
47471
+ }],
44031
47472
  "recordingSignal.getStatus": [{
44032
47473
  name: "deviceId",
44033
47474
  form: "single",
@@ -44254,6 +47695,11 @@ Object.freeze({
44254
47695
  form: "single",
44255
47696
  optional: false
44256
47697
  }],
47698
+ "videoclips.dialClipStream": [{
47699
+ name: "deviceId",
47700
+ form: "single",
47701
+ optional: false
47702
+ }],
44257
47703
  "videoclips.getClipPlayback": [{
44258
47704
  name: "deviceId",
44259
47705
  form: "single",
@@ -44264,6 +47710,16 @@ Object.freeze({
44264
47710
  form: "single",
44265
47711
  optional: false
44266
47712
  }],
47713
+ "videoclips.listSources": [{
47714
+ name: "deviceId",
47715
+ form: "single",
47716
+ optional: false
47717
+ }],
47718
+ "videoclips.readClipBytes": [{
47719
+ name: "deviceId",
47720
+ form: "single",
47721
+ optional: false
47722
+ }],
44267
47723
  "waterHeater.setAway": [{
44268
47724
  name: "deviceId",
44269
47725
  form: "single",
@@ -44299,6 +47755,11 @@ Object.freeze({
44299
47755
  form: "single",
44300
47756
  optional: false
44301
47757
  }],
47758
+ "webrtcSession.getSessionDebug": [{
47759
+ name: "deviceId",
47760
+ form: "single",
47761
+ optional: false
47762
+ }],
44302
47763
  "webrtcSession.getSessionState": [{
44303
47764
  name: "deviceId",
44304
47765
  form: "single",
@@ -70871,6 +74332,7 @@ exports.DeviceType = DeviceType;
70871
74332
  exports.EventCategory = EventCategory;
70872
74333
  exports.HomematicProviderAddon = HomematicProviderAddon;
70873
74334
  exports.buildConnectionFormSchema = buildConnectionFormSchema;
74335
+ exports.buildDeviceAdminUrl = buildDeviceAdminUrl;
70874
74336
  exports.climateControlCapability = climateControlCapability;
70875
74337
  exports.coverCapability = coverCapability;
70876
74338
  exports.createEvent = createEvent;
@@ -70878,6 +74340,7 @@ exports.decodeClimate = decodeClimate;
70878
74340
  exports.decodeCover = decodeCover;
70879
74341
  exports.decodeLock = decodeLock;
70880
74342
  exports.deriveHmEntitySpecs = deriveHmEntitySpecs;
74343
+ exports.deviceAdminLinkCapability = deviceAdminLinkCapability;
70881
74344
  exports.eventEmitterCapability = eventEmitterCapability;
70882
74345
  exports.homematicAddonConfigSchema = homematicAddonConfigSchema;
70883
74346
  exports.homematicConfigSchema = homematicConfigSchema;