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