@camstack/addon-provider-unifi 0.2.114 → 0.2.116
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/addon.js +3658 -115
- package/dist/addon.mjs +3659 -116
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
package/dist/addon.mjs
CHANGED
|
@@ -5359,7 +5359,7 @@ var ZodIssueCode = {
|
|
|
5359
5359
|
var ZodFirstPartyTypeKind;
|
|
5360
5360
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5361
5361
|
//#endregion
|
|
5362
|
-
//#region ../types/dist/sleep-
|
|
5362
|
+
//#region ../types/dist/sleep-i3eUVc-d.mjs
|
|
5363
5363
|
/**
|
|
5364
5364
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5365
5365
|
* window to float samples (D455).
|
|
@@ -6501,6 +6501,17 @@ var CamProfileSchema = _enum([
|
|
|
6501
6501
|
"mid",
|
|
6502
6502
|
"low"
|
|
6503
6503
|
]);
|
|
6504
|
+
/**
|
|
6505
|
+
* Which rung a per-camera consumer listens on: an explicit profile, or `auto`.
|
|
6506
|
+
*
|
|
6507
|
+
* `auto` is a REAL value, not an absent one — it means "resolve it by the rule
|
|
6508
|
+
* that consumer owns". It lives beside `CamProfile` because a rung is the only
|
|
6509
|
+
* operator-facing way to name a stream: a cam-stream id is broker-internal and
|
|
6510
|
+
* moves when a slot is reassigned, so a stored id silently stops meaning what
|
|
6511
|
+
* the operator picked. Today's one user is the audio analyser, whose rule is
|
|
6512
|
+
* "the lowest AVAILABLE native rung" (D537).
|
|
6513
|
+
*/
|
|
6514
|
+
var AudioStreamPickSchema = union([CamProfileSchema, literal("auto")]);
|
|
6504
6515
|
var CamStreamKindSchema = _enum([
|
|
6505
6516
|
"pull-rtsp",
|
|
6506
6517
|
"pull-rtmp",
|
|
@@ -6919,6 +6930,18 @@ function systemMethod(input, output, options) {
|
|
|
6919
6930
|
systemOnly: true
|
|
6920
6931
|
};
|
|
6921
6932
|
}
|
|
6933
|
+
/**
|
|
6934
|
+
* A method a SOURCE of a collection cap may legitimately not serve — OPTIONAL
|
|
6935
|
+
* on `InferProvider`. The `providerOptional: true` literal is what
|
|
6936
|
+
* `InferProvider` keys on; see {@link CapabilityMethodSchema.providerOptional}
|
|
6937
|
+
* for when this is the honest answer and when it is a soft stub.
|
|
6938
|
+
*/
|
|
6939
|
+
function optionalMethod(input, output, options) {
|
|
6940
|
+
return {
|
|
6941
|
+
...method(input, output, options),
|
|
6942
|
+
providerOptional: true
|
|
6943
|
+
};
|
|
6944
|
+
}
|
|
6922
6945
|
/** Shorthand to define an event schema */
|
|
6923
6946
|
function event(data) {
|
|
6924
6947
|
return { data };
|
|
@@ -7349,6 +7372,8 @@ var EVENT_OWNER_TYPES = [
|
|
|
7349
7372
|
* nothing failing until a caller asked.
|
|
7350
7373
|
*/
|
|
7351
7374
|
var EventOwnerTypeSchema = _enum(EVENT_OWNER_TYPES);
|
|
7375
|
+
/** The same list as a Zod enum, for the cap input that carries it. */
|
|
7376
|
+
var MediaPresenceOwnerKindSchema = _enum([...EVENT_OWNER_TYPES, "track"]);
|
|
7352
7377
|
new Set(EVENT_OWNER_TYPES);
|
|
7353
7378
|
var EncodeProfileSchema = object({
|
|
7354
7379
|
video: object({
|
|
@@ -7721,6 +7746,7 @@ var CameraSwitchIdSchema = _enum([
|
|
|
7721
7746
|
"device-audio",
|
|
7722
7747
|
"broker-audio",
|
|
7723
7748
|
"audio-analysis",
|
|
7749
|
+
"event-saving",
|
|
7724
7750
|
"recording",
|
|
7725
7751
|
"notifications"
|
|
7726
7752
|
]);
|
|
@@ -7756,10 +7782,20 @@ var CameraSwitchAuthoritySchema = discriminatedUnion("kind", [
|
|
|
7756
7782
|
var CameraSwitchUnavailableReasonSchema = _enum([
|
|
7757
7783
|
"no-provider",
|
|
7758
7784
|
"source-unreachable",
|
|
7759
|
-
"not-configured",
|
|
7760
7785
|
"device-disabled"
|
|
7761
7786
|
]);
|
|
7762
7787
|
/**
|
|
7788
|
+
* Something true about a switch that is nonetheless OFFERED.
|
|
7789
|
+
*
|
|
7790
|
+
* The distinction from {@link CameraSwitchUnavailableReasonSchema} is the
|
|
7791
|
+
* whole point: a reason replaces the control, a warning travels WITH it. The
|
|
7792
|
+
* privacy mask taught us the difference — greying the one button that starts
|
|
7793
|
+
* the feature teaches the operator the feature is broken, while a live toggle
|
|
7794
|
+
* that flips nothing teaches them the switch is broken. Neither is true; the
|
|
7795
|
+
* honest surface is a working toggle that says what is missing.
|
|
7796
|
+
*/
|
|
7797
|
+
var CameraSwitchWarningSchema = _enum(["no-privacy-zones"]);
|
|
7798
|
+
/**
|
|
7763
7799
|
* One switch, resolved for one camera.
|
|
7764
7800
|
*
|
|
7765
7801
|
* `label` and `costWhenOff` travel ON THE WIRE rather than being looked up
|
|
@@ -7778,6 +7814,11 @@ var CameraSwitchSchema = object({
|
|
|
7778
7814
|
/** False = do not render a control. `unavailableReason` says why. */
|
|
7779
7815
|
available: boolean(),
|
|
7780
7816
|
unavailableReason: CameraSwitchUnavailableReasonSchema.optional(),
|
|
7817
|
+
/**
|
|
7818
|
+
* Present on an AVAILABLE switch only. Rendered beside the live control;
|
|
7819
|
+
* never a substitute for one.
|
|
7820
|
+
*/
|
|
7821
|
+
warning: CameraSwitchWarningSchema.optional(),
|
|
7781
7822
|
/** Current state. Meaningless when `available` is false — read it as `true`. */
|
|
7782
7823
|
enabled: boolean(),
|
|
7783
7824
|
authority: CameraSwitchAuthoritySchema
|
|
@@ -8195,11 +8236,31 @@ var RecordingBandTriggersSchema = RecordingTriggersSchema;
|
|
|
8195
8236
|
* band engine's `applies` rule). `start`/`end` are `HH:MM`; an `end <= start`
|
|
8196
8237
|
* span wraps past midnight (handled by the band engine).
|
|
8197
8238
|
*/
|
|
8239
|
+
/**
|
|
8240
|
+
* Whether a band's footage carries sound.
|
|
8241
|
+
*
|
|
8242
|
+
* The recorder has always attached with `audio: 'aac'`, so every band of every
|
|
8243
|
+
* camera recorded sound whether anyone wanted it or not. The broker already
|
|
8244
|
+
* serves the other answer — `audio: 'none'` takes the video-only `/muted`
|
|
8245
|
+
* restream — so this was a choice the product could always have offered.
|
|
8246
|
+
*
|
|
8247
|
+
* It belongs on the BAND rather than the camera because one camera can want
|
|
8248
|
+
* both: silent across the shop floor during opening hours, sound overnight.
|
|
8249
|
+
* And it applies to BOTH modes — an events band firing on motion has the same
|
|
8250
|
+
* question to answer as a continuous one.
|
|
8251
|
+
*/
|
|
8252
|
+
var RecordingBandAudioSchema = _enum(["on", "muted"]);
|
|
8198
8253
|
var RecordingBandSchema = object({
|
|
8199
8254
|
days: array(RecordingWeekdaySchema),
|
|
8200
8255
|
start: string().regex(HHMM),
|
|
8201
8256
|
end: string().regex(HHMM),
|
|
8202
8257
|
mode: RecordingBandModeSchema,
|
|
8258
|
+
/**
|
|
8259
|
+
* Sound, or silence. OPTIONAL and absent-means-`on`: every recording
|
|
8260
|
+
* written before this existed carried sound, and a band whose key is
|
|
8261
|
+
* missing must keep meaning what it meant. See {@link RecordingBandAudioSchema}.
|
|
8262
|
+
*/
|
|
8263
|
+
audio: RecordingBandAudioSchema.optional(),
|
|
8203
8264
|
triggers: RecordingBandTriggersSchema.optional(),
|
|
8204
8265
|
/**
|
|
8205
8266
|
* RETIRED (D381). A band no longer carries a pre-buffer of its own.
|
|
@@ -13483,6 +13544,210 @@ method(object({ integrationId: string() }), object({ filters: array(AdoptionFilt
|
|
|
13483
13544
|
auth: "admin"
|
|
13484
13545
|
});
|
|
13485
13546
|
/**
|
|
13547
|
+
* device-admin-link — "this device has a management page of its own, and here
|
|
13548
|
+
* is its address".
|
|
13549
|
+
*
|
|
13550
|
+
* ## Why this is not a `deviceConfig` cap
|
|
13551
|
+
*
|
|
13552
|
+
* There is nothing to edit. A `deviceConfig` cap (D14) exists so the framework
|
|
13553
|
+
* can DERIVE a settings form from `getOptions` + `getStatus` and route a flat
|
|
13554
|
+
* patch back through a setter; it costs a `builderId` reducer in
|
|
13555
|
+
* `device-config-contribution.ts` and a `*-config-schema.ts` beside it, and it
|
|
13556
|
+
* renders a form section. This cap answers ONE question with ONE read and
|
|
13557
|
+
* renders a button. Nothing about it is a form, so it carries no `deviceConfig`
|
|
13558
|
+
* block, no `settings`, no `runtimeState` and no reducer — exactly like
|
|
13559
|
+
* `reboot`, the other pure-RPC device-native cap.
|
|
13560
|
+
*
|
|
13561
|
+
* ## Absent, and the difference between "no page" and "we cannot say"
|
|
13562
|
+
*
|
|
13563
|
+
* The two are answered at DIFFERENT layers, on purpose:
|
|
13564
|
+
*
|
|
13565
|
+
* - **"We cannot say"** → the provider never registers the cap for that
|
|
13566
|
+
* device. A VeSync humidifier, a Petkit feeder, a Dreame vacuum and a Dreo
|
|
13567
|
+
* fan are reached only through a vendor cloud; there is no address to hand
|
|
13568
|
+
* out and no page to open. A Tuya plug, a Wyze camera and a Gree air
|
|
13569
|
+
* conditioner DO have a LAN IP, and still have no HTTP management page
|
|
13570
|
+
* behind it. None of them register, so `deviceManager.getBindings` never
|
|
13571
|
+
* lists the cap and no surface asks.
|
|
13572
|
+
* - **"This device has no page, and I know that"** → the provider registers
|
|
13573
|
+
* and `getAdminLink` returns `null`. This is the answer for a device whose
|
|
13574
|
+
* sibling DOES have a page: a Reolink battery camera reached over UDP by
|
|
13575
|
+
* `uid` with a blank `host`, an Ecowitt gateway configured in `listener`
|
|
13576
|
+
* transport, a Home Assistant broker authenticated by supervisor token
|
|
13577
|
+
* (which carries no `baseUrl` at all).
|
|
13578
|
+
*
|
|
13579
|
+
* Both draw NOTHING. A button that opens a browser error is worse than no
|
|
13580
|
+
* button, and D62 is the same rule from the other side: an off switch is
|
|
13581
|
+
* reported off, never made to look broken. There is no third state where the
|
|
13582
|
+
* UI renders a disabled button "because the device might have a page".
|
|
13583
|
+
*
|
|
13584
|
+
* ## The URL never carries credentials
|
|
13585
|
+
*
|
|
13586
|
+
* Not in userinfo, not in a query string. Every provider builds through
|
|
13587
|
+
* `buildDeviceAdminUrl` (`device-admin-link-url.ts`), which takes host, port,
|
|
13588
|
+
* scheme and path as separate arguments — there is no parameter a secret could
|
|
13589
|
+
* arrive in — and re-checks its own output for the `scheme://user:pass@` shape
|
|
13590
|
+
* that `scripts/check-no-credential-urls-in-fixtures.ts` bans from recorded
|
|
13591
|
+
* output. `scripts/check-admin-link-builder-is-the-only-url-source.ts` is what
|
|
13592
|
+
* keeps providers from hand-rolling one anyway.
|
|
13593
|
+
*
|
|
13594
|
+
* This matters here more than anywhere else in the repo, because every provider
|
|
13595
|
+
* that knows a device's host knows its PASSWORD too: `{ host, port, username,
|
|
13596
|
+
* password }` sit in one object on Hikvision, Amcrest, Reolink and ONVIF alike,
|
|
13597
|
+
* and `http://admin:hunter2@192.168.50.139/` is a URL a browser accepts. The
|
|
13598
|
+
* camera's own page will ask for its own login. That is correct, and pre-
|
|
13599
|
+
* filling it is the operator's business, not ours.
|
|
13600
|
+
*
|
|
13601
|
+
* ## It is a LAN fact
|
|
13602
|
+
*
|
|
13603
|
+
* The URL addresses the device where the NODE can see it. It is not proxied,
|
|
13604
|
+
* not made reachable from outside, and not sent anywhere. A surface renders it
|
|
13605
|
+
* as a link the operator's own browser follows, on the operator's own network,
|
|
13606
|
+
* or renders nothing.
|
|
13607
|
+
*/
|
|
13608
|
+
/**
|
|
13609
|
+
* Whose page is it. The distinction is for the OPERATOR, who needs to know
|
|
13610
|
+
* before clicking whether he is about to land on a camera's own web server or
|
|
13611
|
+
* inside Home Assistant.
|
|
13612
|
+
*/
|
|
13613
|
+
var AdminLinkTargetEnum = _enum(["device", "integration"]);
|
|
13614
|
+
var DeviceAdminLinkSchema = object({
|
|
13615
|
+
/**
|
|
13616
|
+
* Absolute `http(s)://` URL. Built by `buildDeviceAdminUrl` and therefore
|
|
13617
|
+
* free of userinfo and of any credential-shaped query key.
|
|
13618
|
+
*/
|
|
13619
|
+
url: string(),
|
|
13620
|
+
/**
|
|
13621
|
+
* What the surface calls it — "Web UI", "Home Assistant", "UniFi controller".
|
|
13622
|
+
* The PROVIDER names it, because only the provider knows what the page is;
|
|
13623
|
+
* a UI that invented the label from the addon id would call the Home
|
|
13624
|
+
* Assistant device page "Provider Homeassistant".
|
|
13625
|
+
*/
|
|
13626
|
+
label: string(),
|
|
13627
|
+
target: AdminLinkTargetEnum,
|
|
13628
|
+
/**
|
|
13629
|
+
* Host the URL points at, without scheme, port or path — for the tooltip, so
|
|
13630
|
+
* an operator can see WHERE the button goes before he follows it. Redundant
|
|
13631
|
+
* with `url` by construction; carried separately so no surface has to parse
|
|
13632
|
+
* a URL to show it.
|
|
13633
|
+
*/
|
|
13634
|
+
host: string()
|
|
13635
|
+
});
|
|
13636
|
+
var deviceAdminLinkCapability = {
|
|
13637
|
+
name: "device-admin-link",
|
|
13638
|
+
scope: "device",
|
|
13639
|
+
deviceNative: true,
|
|
13640
|
+
mode: "singleton",
|
|
13641
|
+
methods: {
|
|
13642
|
+
/**
|
|
13643
|
+
* The device's management page, or `null` when this device has none.
|
|
13644
|
+
*
|
|
13645
|
+
* `auth: 'admin'` deliberately. This is administration, not actuation —
|
|
13646
|
+
* the same bucket as `reboot` and `camera-credentials`, and explicitly NOT
|
|
13647
|
+
* the actuation set `scripts/check-actuation-not-admin.ts` protects (D403).
|
|
13648
|
+
* The URL is also a statement about the LAN, which a household member with
|
|
13649
|
+
* a `view` grant on a light has no reason to be handed.
|
|
13650
|
+
*
|
|
13651
|
+
* The surfaces gate on the QUERY, never on a role they guessed: a caller
|
|
13652
|
+
* without the right loses the query and draws nothing, which is the same
|
|
13653
|
+
* thing a device with no page draws. There is no path on which a button
|
|
13654
|
+
* appears and then fails — the D403 failure mode, from the other end.
|
|
13655
|
+
*/
|
|
13656
|
+
getAdminLink: method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema.nullable(), { auth: "admin" }) }
|
|
13657
|
+
};
|
|
13658
|
+
/**
|
|
13659
|
+
* Query keys that may never appear on an admin link. A credential smuggled as
|
|
13660
|
+
* `?password=` is the same leak as userinfo, in a form the userinfo check
|
|
13661
|
+
* cannot see: it survives copy-paste, referrer headers and proxy logs
|
|
13662
|
+
* identically.
|
|
13663
|
+
*/
|
|
13664
|
+
var CREDENTIAL_QUERY_KEYS = [
|
|
13665
|
+
"password",
|
|
13666
|
+
"passwd",
|
|
13667
|
+
"pwd",
|
|
13668
|
+
"pass",
|
|
13669
|
+
"user",
|
|
13670
|
+
"username",
|
|
13671
|
+
"usr",
|
|
13672
|
+
"login",
|
|
13673
|
+
"token",
|
|
13674
|
+
"access_token",
|
|
13675
|
+
"auth",
|
|
13676
|
+
"authorization",
|
|
13677
|
+
"apikey",
|
|
13678
|
+
"api_key",
|
|
13679
|
+
"secret",
|
|
13680
|
+
"credential",
|
|
13681
|
+
"credentials",
|
|
13682
|
+
"session",
|
|
13683
|
+
"sessionid",
|
|
13684
|
+
"key"
|
|
13685
|
+
];
|
|
13686
|
+
/**
|
|
13687
|
+
* The same userinfo shape `scripts/check-no-credential-urls-in-fixtures.ts`
|
|
13688
|
+
* scans recorded fixtures for, applied to what we are about to EMIT. Kept
|
|
13689
|
+
* structurally identical on purpose: a URL this function returns is a URL that
|
|
13690
|
+
* guard would pass.
|
|
13691
|
+
*/
|
|
13692
|
+
var CREDENTIAL_URL = /[a-z][a-z0-9+.-]*:\/\/[^/\s:@'"]+:[^/\s:@'"]+@/i;
|
|
13693
|
+
/** A host that is safe to place in an authority component verbatim. */
|
|
13694
|
+
var PLAIN_HOST = /^[a-zA-Z0-9._-]+$/;
|
|
13695
|
+
/** A bracketed IPv6 literal, the only other authority form we emit. */
|
|
13696
|
+
var IPV6_HOST = /^\[[0-9a-fA-F:.]+\]$/;
|
|
13697
|
+
function defaultPortFor(scheme) {
|
|
13698
|
+
return scheme === "https" ? 443 : 80;
|
|
13699
|
+
}
|
|
13700
|
+
/**
|
|
13701
|
+
* Build the admin-page URL, or refuse by name.
|
|
13702
|
+
*
|
|
13703
|
+
* The refusal is never thrown: a provider answering "no page for this device"
|
|
13704
|
+
* is an ordinary answer (`getAdminLink` → `null`), and a throw here would turn
|
|
13705
|
+
* a missing host on one device into a failed query for the whole surface.
|
|
13706
|
+
*/
|
|
13707
|
+
function buildDeviceAdminUrl(parts) {
|
|
13708
|
+
const host = parts.host.trim();
|
|
13709
|
+
if (host.length === 0) return {
|
|
13710
|
+
ok: false,
|
|
13711
|
+
reason: "empty-host"
|
|
13712
|
+
};
|
|
13713
|
+
if (host.includes("@")) return {
|
|
13714
|
+
ok: false,
|
|
13715
|
+
reason: "host-carries-userinfo"
|
|
13716
|
+
};
|
|
13717
|
+
if (!PLAIN_HOST.test(host) && !IPV6_HOST.test(host)) return {
|
|
13718
|
+
ok: false,
|
|
13719
|
+
reason: "host-not-plain"
|
|
13720
|
+
};
|
|
13721
|
+
if (parts.port !== null) {
|
|
13722
|
+
if (!Number.isInteger(parts.port) || parts.port < 1 || parts.port > 65535) return {
|
|
13723
|
+
ok: false,
|
|
13724
|
+
reason: "port-out-of-range"
|
|
13725
|
+
};
|
|
13726
|
+
}
|
|
13727
|
+
if (!parts.path.startsWith("/")) return {
|
|
13728
|
+
ok: false,
|
|
13729
|
+
reason: "path-not-absolute"
|
|
13730
|
+
};
|
|
13731
|
+
const query = parts.query ?? {};
|
|
13732
|
+
for (const key of Object.keys(query)) if (CREDENTIAL_QUERY_KEYS.includes(key.toLowerCase())) return {
|
|
13733
|
+
ok: false,
|
|
13734
|
+
reason: "credential-query-key"
|
|
13735
|
+
};
|
|
13736
|
+
const authority = parts.port === null || parts.port === defaultPortFor(parts.scheme) ? host : `${host}:${String(parts.port)}`;
|
|
13737
|
+
const search = new URLSearchParams();
|
|
13738
|
+
for (const [key, value] of Object.entries(query)) search.append(key, value);
|
|
13739
|
+
const suffix = search.size > 0 ? `?${search.toString()}` : "";
|
|
13740
|
+
const url = `${parts.scheme}://${authority}${parts.path}${suffix}`;
|
|
13741
|
+
if (CREDENTIAL_URL.test(url)) return {
|
|
13742
|
+
ok: false,
|
|
13743
|
+
reason: "userinfo-in-result"
|
|
13744
|
+
};
|
|
13745
|
+
return {
|
|
13746
|
+
ok: true,
|
|
13747
|
+
url
|
|
13748
|
+
};
|
|
13749
|
+
}
|
|
13750
|
+
/**
|
|
13486
13751
|
* `device-export` — collection cap for addons that export camstack
|
|
13487
13752
|
* devices to external ecosystems (HomeAssistant via MQTT discovery,
|
|
13488
13753
|
* HomeKit/HAP, Alexa Smart Home, …).
|
|
@@ -20249,6 +20514,7 @@ var MediaFileKindEnum = _enum([
|
|
|
20249
20514
|
"keyFrameSmall",
|
|
20250
20515
|
"thumbnailSmall",
|
|
20251
20516
|
"confirmationCrop",
|
|
20517
|
+
"debugCrop",
|
|
20252
20518
|
"sceneFrame"
|
|
20253
20519
|
]);
|
|
20254
20520
|
/**
|
|
@@ -21328,7 +21594,11 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
21328
21594
|
ownerType: EventOwnerTypeSchema,
|
|
21329
21595
|
eventId: number().int(),
|
|
21330
21596
|
deviceId: number()
|
|
21331
|
-
}), array(MediaFileInfoSchema).readonly()), method(
|
|
21597
|
+
}), array(MediaFileInfoSchema).readonly()), method(object({
|
|
21598
|
+
deviceId: number(),
|
|
21599
|
+
ownerKind: MediaPresenceOwnerKindSchema,
|
|
21600
|
+
ownerIds: array(string()).max(5e3)
|
|
21601
|
+
}), array(string()).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
|
|
21332
21602
|
kind: "mutation",
|
|
21333
21603
|
auth: "admin"
|
|
21334
21604
|
}), method(RebuildObjectEmbeddingsInput, RebuildObjectEmbeddingsResultSchema, {
|
|
@@ -21913,6 +22183,18 @@ var RunnerCameraConfigSchema = object({
|
|
|
21913
22183
|
motionStreamId: string(),
|
|
21914
22184
|
detectionStreamId: string(),
|
|
21915
22185
|
/**
|
|
22186
|
+
* Which rung this camera's audio analysis listens on — `auto` (the lowest
|
|
22187
|
+
* AVAILABLE native rung) or an explicit one.
|
|
22188
|
+
*
|
|
22189
|
+
* Not consumed by the runner: carried here so it travels with the camera's
|
|
22190
|
+
* config the way `audioMotionWindowMs` does, and so the orchestrator's
|
|
22191
|
+
* watchdog resolves the same stream the audio subscription did. OPTIONAL
|
|
22192
|
+
* with no default — absent means `auto`, which is what every camera written
|
|
22193
|
+
* before this field had, and an absent optional adds nothing to the
|
|
22194
|
+
* generated cap-input defaults (D537).
|
|
22195
|
+
*/
|
|
22196
|
+
audioStreamPick: AudioStreamPickSchema.optional(),
|
|
22197
|
+
/**
|
|
21916
22198
|
* Motion sources that can activate the pipeline. At least one must
|
|
21917
22199
|
* be present. Multiple sources OR-ed: any one firing arms the
|
|
21918
22200
|
* cooldown timer and transitions watching → active. The same
|
|
@@ -22983,10 +23265,9 @@ var ServerBootModeSchema = _enum([
|
|
|
22983
23265
|
* - `idle` / `checking` / `staging` — steady / in-flight registry work.
|
|
22984
23266
|
* - `pending-restart` — a version is staged and the node has NOT yet
|
|
22985
23267
|
* restarted onto it (still running the OLD version).
|
|
22986
|
-
* - `awaiting-confirmation` —
|
|
22987
|
-
*
|
|
22988
|
-
*
|
|
22989
|
-
* manually restarted, or the probation boot auto-rolls-back.
|
|
23268
|
+
* - `awaiting-confirmation` — RETIRED with the probation boot (single-copy
|
|
23269
|
+
* collapse, 2026-07-18). Kept in the enum so an older client still parses;
|
|
23270
|
+
* no provider emits it.
|
|
22990
23271
|
*/
|
|
22991
23272
|
var ServerUpdateStateSchema = _enum([
|
|
22992
23273
|
"idle",
|
|
@@ -23075,12 +23356,35 @@ var ServerRollbackInfoSchema = object({
|
|
|
23075
23356
|
var ServerPackageStatusSchema = object({
|
|
23076
23357
|
/** Root package name (`@camstack/server` on the hub). */
|
|
23077
23358
|
packageName: string(),
|
|
23078
|
-
/**
|
|
23359
|
+
/**
|
|
23360
|
+
* Version of the code THIS PROCESS loaded — captured once, at boot, from the
|
|
23361
|
+
* package.json of the copy the code was loaded from, and never re-read. A
|
|
23362
|
+
* copy replaced on disk under a live process must not move this number;
|
|
23363
|
+
* that divergence is what `installedVersion` is for. Before 2026-09-20 this
|
|
23364
|
+
* was a lazy disk read that would have followed such a swap silently.
|
|
23365
|
+
*/
|
|
23079
23366
|
runningVersion: string().nullable(),
|
|
23080
23367
|
/** Node.js runtime version the node's process runs on (`process.versions.node`). */
|
|
23081
23368
|
nodeRuntimeVersion: string().nullable(),
|
|
23082
23369
|
/** Active data-dir root version; null when booted from seed/workspace. */
|
|
23083
23370
|
activeVersion: string().nullable(),
|
|
23371
|
+
/**
|
|
23372
|
+
* Version of the root package in the data-root `current/` copy RIGHT NOW —
|
|
23373
|
+
* what the next boot would load if nothing is staged. Normally equal to
|
|
23374
|
+
* `runningVersion`; differs only when the copy on disk changed under the
|
|
23375
|
+
* live process. `null` when there is no data-root copy (baked seed or
|
|
23376
|
+
* workspace boot). Optional for version skew: an older provider omits it.
|
|
23377
|
+
*/
|
|
23378
|
+
installedVersion: string().nullable().optional(),
|
|
23379
|
+
/**
|
|
23380
|
+
* True when the code that would run after a restart is NOT the code running
|
|
23381
|
+
* now: a version is staged (`pendingVersion`), or `installedVersion` differs
|
|
23382
|
+
* from `runningVersion`. Derived on every read, never stored. This is the
|
|
23383
|
+
* one field that lets an operator or a script say "1.2.353 is installed and
|
|
23384
|
+
* will run after a restart" instead of reading it off a number that means
|
|
23385
|
+
* "installed". Optional for version skew.
|
|
23386
|
+
*/
|
|
23387
|
+
restartRequired: boolean().optional(),
|
|
23084
23388
|
/** N-1 version kept for rollback; null when no previous version exists. */
|
|
23085
23389
|
previousVersion: string().nullable(),
|
|
23086
23390
|
/** Version of the immutable baked seed closure (image fallback). */
|
|
@@ -23115,19 +23419,71 @@ var ServerPackageStatusSchema = object({
|
|
|
23115
23419
|
});
|
|
23116
23420
|
var ServerUpdateCheckResultSchema = object({
|
|
23117
23421
|
packageName: string(),
|
|
23422
|
+
/** Same meaning as on the status payload: what THIS process loaded. */
|
|
23118
23423
|
runningVersion: string().nullable(),
|
|
23119
23424
|
latestVersion: string().nullable(),
|
|
23425
|
+
/**
|
|
23426
|
+
* `latestVersion` is newer than `runningVersion` AND is not the version
|
|
23427
|
+
* already staged on this node. A staged-and-waiting version is not "an
|
|
23428
|
+
* update available" — applying it again is refused (`already-staged`), and
|
|
23429
|
+
* a button that announced "updating…" on this boolean then got refused
|
|
23430
|
+
* would be lying. It is reported through `pendingVersion` instead.
|
|
23431
|
+
*/
|
|
23120
23432
|
updateAvailable: boolean(),
|
|
23121
23433
|
checkedAtMs: number(),
|
|
23122
23434
|
/** Non-null when the registry lookup failed (offline, bad registry, …). */
|
|
23123
|
-
error: string().nullable()
|
|
23435
|
+
error: string().nullable(),
|
|
23436
|
+
/**
|
|
23437
|
+
* Version staged on this node and awaiting its restart, when one is. Before
|
|
23438
|
+
* 2026-09-20 a check answered `{runningVersion, updateAvailable:false}` for a
|
|
23439
|
+
* node whose staged closure had never booted, and the caller had no way to
|
|
23440
|
+
* see the difference. Optional for version skew.
|
|
23441
|
+
*/
|
|
23442
|
+
pendingVersion: string().nullable().optional(),
|
|
23443
|
+
/** See the status payload: staged, or installed-on-disk ≠ running. Optional for skew. */
|
|
23444
|
+
restartRequired: boolean().optional()
|
|
23124
23445
|
});
|
|
23446
|
+
/**
|
|
23447
|
+
* What an action DID — the discriminant `accepted` cannot carry. Before
|
|
23448
|
+
* 2026-09-20 `applyServerUpdate` answered `accepted:false` both for a refusal
|
|
23449
|
+
* where nothing was wrong (already current, already staged) and for a staging
|
|
23450
|
+
* install that died on `npm error code ECONNRESET`; the only difference was
|
|
23451
|
+
* prose in `message`, and an operator restarted a container on the strength of
|
|
23452
|
+
* it, booting the previous version.
|
|
23453
|
+
*
|
|
23454
|
+
* - `staged` — the closure is installed and validated; the node
|
|
23455
|
+
* restarts to apply it (`restarting:true`). NOTHING
|
|
23456
|
+
* runs the new code until that restart completes.
|
|
23457
|
+
* - `restart-scheduled` — a plain, version-preserving restart was accepted.
|
|
23458
|
+
* - `already-current` — the target IS the running version; nothing done.
|
|
23459
|
+
* - `refused` — dropped by a gate; `reason` names which.
|
|
23460
|
+
* - `failed` — attempted and did not land; `error` says why.
|
|
23461
|
+
*/
|
|
23462
|
+
var ServerUpdateOutcomeSchema = _enum([
|
|
23463
|
+
"staged",
|
|
23464
|
+
"restart-scheduled",
|
|
23465
|
+
"already-current",
|
|
23466
|
+
"refused",
|
|
23467
|
+
"failed"
|
|
23468
|
+
]);
|
|
23125
23469
|
var ServerUpdateActionResultSchema = object({
|
|
23126
23470
|
accepted: boolean(),
|
|
23127
23471
|
targetVersion: string().nullable(),
|
|
23128
23472
|
/** True when a graceful restart was scheduled to apply the change. */
|
|
23129
23473
|
restarting: boolean(),
|
|
23130
|
-
message: string()
|
|
23474
|
+
message: string(),
|
|
23475
|
+
/** See {@link ServerUpdateOutcomeSchema}. Optional for version skew. */
|
|
23476
|
+
outcome: ServerUpdateOutcomeSchema.optional(),
|
|
23477
|
+
/**
|
|
23478
|
+
* Stable, greppable cause for `refused` / `failed` (the engine's
|
|
23479
|
+
* `UpdateRefusalReason`, `staging-failed`, …). `null` on success.
|
|
23480
|
+
*/
|
|
23481
|
+
reason: string().nullable().optional(),
|
|
23482
|
+
/**
|
|
23483
|
+
* The failure itself, verbatim enough to act on (the npm output for a
|
|
23484
|
+
* staging install). Non-null only for `failed`.
|
|
23485
|
+
*/
|
|
23486
|
+
error: string().nullable().optional()
|
|
23131
23487
|
});
|
|
23132
23488
|
method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), ServerUpdateCheckResultSchema, {
|
|
23133
23489
|
kind: "mutation",
|
|
@@ -24236,6 +24592,18 @@ method(VectorDeclareIndexInputSchema, _void(), {
|
|
|
24236
24592
|
kind: "mutation",
|
|
24237
24593
|
auth: "admin"
|
|
24238
24594
|
}), method(VectorStatsInputSchema, VectorStatsResultSchema, { auth: "admin" });
|
|
24595
|
+
_enum([
|
|
24596
|
+
"queue-full",
|
|
24597
|
+
"camera-backoff",
|
|
24598
|
+
"sleeping",
|
|
24599
|
+
"camera-refused",
|
|
24600
|
+
"no-keyframe",
|
|
24601
|
+
"no-catalog-row",
|
|
24602
|
+
"unsupported",
|
|
24603
|
+
"unknown-device",
|
|
24604
|
+
"uid-missing"
|
|
24605
|
+
]);
|
|
24606
|
+
_enum(["deferred", "final"]);
|
|
24239
24607
|
var ClipSchema = object({
|
|
24240
24608
|
/** Opaque, provider-namespaced id. The default provider encodes the time
|
|
24241
24609
|
* window so `getClipPlayback` is self-contained (no event re-query). */
|
|
@@ -24310,29 +24678,455 @@ var ClipSchema = object({
|
|
|
24310
24678
|
holes: array(object({
|
|
24311
24679
|
startMs: number(),
|
|
24312
24680
|
endMs: number()
|
|
24313
|
-
})).optional()
|
|
24681
|
+
})).optional(),
|
|
24682
|
+
/**
|
|
24683
|
+
* A URL that MAY produce this clip's still, asked only for rows actually on
|
|
24684
|
+
* screen. The opposite of {@link ClipSchema.thumbnail}: that one is VOUCHED
|
|
24685
|
+
* (a JPEG is already on disk), this one is an offer. The route answers 200
|
|
24686
|
+
* with the image, or 204 with `x-camstack-reason` when it could not mint one
|
|
24687
|
+
* — a surface latches that refusal to the instant rather than retrying.
|
|
24688
|
+
*
|
|
24689
|
+
* Never both: a clip with a vouched `thumbnail` needs no mint.
|
|
24690
|
+
*/
|
|
24691
|
+
thumbnailMint: string().optional(),
|
|
24692
|
+
/**
|
|
24693
|
+
* Why no still will be produced for this clip RIGHT NOW — set when the
|
|
24694
|
+
* provider already knows, so the surface draws the glyph and the reason
|
|
24695
|
+
* instead of firing a mint that cannot succeed.
|
|
24696
|
+
*
|
|
24697
|
+
* `sleeping` — a standalone battery camera; a read would be a wake (D549 4).
|
|
24698
|
+
* `camera-refused` — the camera answered the CoverPreview with a refusal.
|
|
24699
|
+
* `no-keyframe` — the window holds no decodable I-frame (an event that sits
|
|
24700
|
+
* inside no file is the measured case).
|
|
24701
|
+
* `unsupported` — this source cannot mint stills at all.
|
|
24702
|
+
*/
|
|
24703
|
+
thumbnailUnavailable: object({ reason: _enum([
|
|
24704
|
+
"sleeping",
|
|
24705
|
+
"camera-refused",
|
|
24706
|
+
"no-keyframe",
|
|
24707
|
+
"unsupported"
|
|
24708
|
+
]) }).optional(),
|
|
24709
|
+
/**
|
|
24710
|
+
* When the catalog this row came from was last CONFIRMED against the device.
|
|
24711
|
+
* Absent means "this row was read live". A persisted catalog served while a
|
|
24712
|
+
* camera sleeps carries the age it really has — a cached list is never drawn
|
|
24713
|
+
* as current (D549 13).
|
|
24714
|
+
*/
|
|
24715
|
+
catalogAsOf: number().optional(),
|
|
24716
|
+
/**
|
|
24717
|
+
* The camera's OWN type strings for this clip, all of them, unmapped
|
|
24718
|
+
* (`md`, `people`, `dog_cat`, `sched`, …). Kept beside {@link labels}
|
|
24719
|
+
* because a firmware inventing a type must not vanish: the mapping into our
|
|
24720
|
+
* filter vocabulary is lossy on purpose and this is the lossless copy
|
|
24721
|
+
* (D549 20).
|
|
24722
|
+
*/
|
|
24723
|
+
nativeTypes: array(string()).optional(),
|
|
24724
|
+
/**
|
|
24725
|
+
* What the subject DID — `crossline`, `intrude`, `loitering`. A behaviour
|
|
24726
|
+
* travels BESIDE a class, never instead of one, and the class filter ignores
|
|
24727
|
+
* it: "a person crossed a line" is still a person (D549 20).
|
|
24728
|
+
*/
|
|
24729
|
+
behaviours: array(string()).optional(),
|
|
24730
|
+
/**
|
|
24731
|
+
* The same recording as two files — the sub twin (what the row and its
|
|
24732
|
+
* thumbnail are) and its main twin, matched at listing time so a quality
|
|
24733
|
+
* change never re-searches the camera (D549 15). `getClipPlayback`'s
|
|
24734
|
+
* `profile` picks between them: `low | mid` → sub, `high` → main.
|
|
24735
|
+
*/
|
|
24736
|
+
streams: object({
|
|
24737
|
+
sub: object({
|
|
24738
|
+
id: string(),
|
|
24739
|
+
bytes: number().optional()
|
|
24740
|
+
}).optional(),
|
|
24741
|
+
main: object({
|
|
24742
|
+
id: string(),
|
|
24743
|
+
bytes: number().optional()
|
|
24744
|
+
}).optional()
|
|
24745
|
+
}).optional(),
|
|
24746
|
+
/** The camera says it holds a sub-stream copy of this recording. */
|
|
24747
|
+
supportSub: boolean().optional(),
|
|
24748
|
+
/**
|
|
24749
|
+
* Whether this row has bytes behind it. ABSENT means yes — every clip that
|
|
24750
|
+
* IS a file is playable, and only a source that lists EVENTS can produce a
|
|
24751
|
+
* row with nothing to play (the measured case: 1 of 29 hub events on 3628
|
|
24752
|
+
* fell inside no file). Such a row is shown, never dropped and never offered
|
|
24753
|
+
* as playable-then-failing (D549 19).
|
|
24754
|
+
*/
|
|
24755
|
+
playable: boolean().optional(),
|
|
24756
|
+
/** Why {@link playable} is false, verbatim (`no-file-for-window`). */
|
|
24757
|
+
unplayableReason: string().optional(),
|
|
24758
|
+
/**
|
|
24759
|
+
* This clip is STILL BEING WRITTEN, so {@link ClipSchema.timeRange}`.endMs`
|
|
24760
|
+
* is not its end.
|
|
24761
|
+
*
|
|
24762
|
+
* Absent — the common case — means the row is closed and its `endMs` is the
|
|
24763
|
+
* end of the recording. Present and `true` means the source told us the file
|
|
24764
|
+
* has no end yet, and the `endMs` we carry is whatever the camera's index
|
|
24765
|
+
* entry happened to hold: on a Reolink E1 Outdoor PoE (592, measured
|
|
24766
|
+
* 2026-09-20) the newest file `…_192927_000000_…_0.mp4` reported an `endTime`
|
|
24767
|
+
* of 19:29:56 and STILL reported it eight minutes later, while the file went
|
|
24768
|
+
* on growing. A surface that drew 29 s there was lying about a clip that
|
|
24769
|
+
* plays for minutes, on the one row the operator looks at first.
|
|
24770
|
+
*
|
|
24771
|
+
* `endMs` is deliberately still a number: it is the best bound anything has
|
|
24772
|
+
* for a mint window or a byte fetch, and every consumer already requires it.
|
|
24773
|
+
* This flag says what it is WORTH, and a duration is not drawn from it.
|
|
24774
|
+
*/
|
|
24775
|
+
inProgress: boolean().optional()
|
|
24314
24776
|
});
|
|
24315
24777
|
var ClipPlaybackSchema = object({
|
|
24316
|
-
/**
|
|
24778
|
+
/**
|
|
24779
|
+
* A media URL through the hub data-plane. {@link ClipPlaybackSchema.format}
|
|
24780
|
+
* says what kind — an HLS master playlist for a recording-derived clip, a
|
|
24781
|
+
* progressive MP4 for a native file the vendor addon muxed and serves with
|
|
24782
|
+
* `Range`. The URL is NOT a credential: both planes are registered
|
|
24783
|
+
* `access: 'authenticated'` and the hub's `/addon/<id>/<prefix>` proxy takes
|
|
24784
|
+
* the session cookie, so nothing is appended to it (D549 22).
|
|
24785
|
+
*/
|
|
24317
24786
|
playbackUrl: string(),
|
|
24787
|
+
/**
|
|
24788
|
+
* How to play {@link playbackUrl}. Absent means `hls` — the shape every
|
|
24789
|
+
* existing consumer already assumes. A player that branches on this is the
|
|
24790
|
+
* one change a native clip needs; a player that ignores it will hand an MP4
|
|
24791
|
+
* to hls.js and fail parsing it as a manifest.
|
|
24792
|
+
*/
|
|
24793
|
+
format: _enum(["hls", "mp4"]).optional(),
|
|
24794
|
+
/**
|
|
24795
|
+
* Which twin was actually served. A clip that holds only one stream answers
|
|
24796
|
+
* with the one it has, and the surface SAYS so — a missing main twin is
|
|
24797
|
+
* never served silently as if it were the asked-for quality (D549 15).
|
|
24798
|
+
*/
|
|
24799
|
+
served: CamProfileSchema.optional(),
|
|
24318
24800
|
/** Optional LAN/remote alternates for the same clip. */
|
|
24319
24801
|
playbackEndpoints: array(string()).optional(),
|
|
24320
24802
|
token: string().optional()
|
|
24321
24803
|
});
|
|
24804
|
+
/**
|
|
24805
|
+
* Why a source cannot answer right now — per SOURCE, never fleet-wide.
|
|
24806
|
+
*
|
|
24807
|
+
* `ok` is the only state whose clip list may be read as complete. The other
|
|
24808
|
+
* three exist because an empty list from a sleeping camera reads as "this
|
|
24809
|
+
* camera has no recordings", which is the defect this whole line of work is
|
|
24810
|
+
* about: a battery camera nobody woke (`sleeping`), a camera that could not be
|
|
24811
|
+
* reached at all (`unreachable`, also what a provider that THREW reports), a
|
|
24812
|
+
* camera whose SD card is not mounted (`no-storage`, measured on 640 —
|
|
24813
|
+
* `HddInfo mount 0`, honestly nothing to list rather than "no clips"), and a
|
|
24814
|
+
* camera whose OWN index disagrees with its OWN calendar (`index-empty`,
|
|
24815
|
+
* measured on 618: the calendar marks 3–20 September, days 6–20 list zero
|
|
24816
|
+
* files on both streams and both filters, with 177 GB free). That last one is
|
|
24817
|
+
* a defect ON THE CAMERA, and the only honest thing a provider can do is say
|
|
24818
|
+
* which days it asked for and got nothing — "no clips" would be a lie about a
|
|
24819
|
+
* card that is full of them.
|
|
24820
|
+
*/
|
|
24821
|
+
/**
|
|
24822
|
+
* The operator's authorisation to WAKE a sleeping camera for one clip read.
|
|
24823
|
+
*
|
|
24824
|
+
* ONE value, absent by default, and it is a `force`-shaped operator signal in
|
|
24825
|
+
* exactly the sense `snapshot-wake-gate.ts` uses the word: *"`snapshot.getSnapshot`'s
|
|
24826
|
+
* `force` flag, and nothing else… a background caller must never set it…
|
|
24827
|
+
* Stale but honest beats woken"*. A scheduler, a retry, a reconcile and a
|
|
24828
|
+
* prefetch never set it; a surface sets it only behind the same confirm the
|
|
24829
|
+
* "Wake and refresh" gesture uses, and it is refused below 15 % battery
|
|
24830
|
+
* exactly as that gesture is.
|
|
24831
|
+
*
|
|
24832
|
+
* The gate is decided BEFORE `getApi()`, because on UDP the login IS the wake
|
|
24833
|
+
* (D549) — a read that opened a session and then checked would have woken the
|
|
24834
|
+
* camera to find out it was not allowed to.
|
|
24835
|
+
*
|
|
24836
|
+
* **One authorised yes is one wake.** `next-natural` — take the clip the next
|
|
24837
|
+
* time the camera is awake for its own reasons — is deliberately not a member:
|
|
24838
|
+
* it was proposed, it is free on the battery, and the operator declined it on
|
|
24839
|
+
* 2026-09-20 (*"Quando un export viene richiesto si sveglia la camera."*,
|
|
24840
|
+
* D558 "Considered and not taken").
|
|
24841
|
+
*/
|
|
24842
|
+
var ClipWakeSchema = _enum(["authorised"]);
|
|
24843
|
+
/**
|
|
24844
|
+
* A clip's finished bytes, inline — the twin of `recordingExport.readExportBytes`.
|
|
24845
|
+
*
|
|
24846
|
+
* `bytes` is the DECODED length, so nobody infers it from the base64 length,
|
|
24847
|
+
* and `served` says which twin the caller actually got.
|
|
24848
|
+
*/
|
|
24849
|
+
var ClipBytesSchema = object({
|
|
24850
|
+
base64: string(),
|
|
24851
|
+
contentType: string(),
|
|
24852
|
+
/** Suggested filename, extension included. */
|
|
24853
|
+
name: string(),
|
|
24854
|
+
bytes: number().int().nonnegative(),
|
|
24855
|
+
/**
|
|
24856
|
+
* Which twin was actually served — the same contract
|
|
24857
|
+
* {@link ClipPlaybackSchema.served} carries, and REQUIRED here because the
|
|
24858
|
+
* export record persists it: a row read a week later must say the same thing
|
|
24859
|
+
* the panel said at the moment of the tap. A missing main twin is never
|
|
24860
|
+
* served silently as if it were the asked-for quality (D549 15).
|
|
24861
|
+
*/
|
|
24862
|
+
served: CamProfileSchema,
|
|
24863
|
+
/**
|
|
24864
|
+
* The DECODED duration of the delivered file, when the fetch measured one.
|
|
24865
|
+
*
|
|
24866
|
+
* A clip fetch verifies its own completion against the catalog row and
|
|
24867
|
+
* retries a materially short pass (D568); this is that measurement, carried
|
|
24868
|
+
* so a consumer can say the same thing rather than re-deriving it. Absent
|
|
24869
|
+
* when the producer did not measure — never zero, which would say the file
|
|
24870
|
+
* is empty.
|
|
24871
|
+
*/
|
|
24872
|
+
durationMs: number().positive().optional()
|
|
24873
|
+
});
|
|
24874
|
+
/**
|
|
24875
|
+
* Where a clip's STREAM can be dialled (D597) — the answer to
|
|
24876
|
+
* {@link videoclipsCapability.methods.dialClipStream}.
|
|
24877
|
+
*
|
|
24878
|
+
* `url` is plain `http://` on the provider's own token-gated listener, one
|
|
24879
|
+
* shot, short-lived. It is NOT the hub-proxied `/addon/<id>/clip-stream/…`
|
|
24880
|
+
* path: that plane takes only a user credential, which an addon does not
|
|
24881
|
+
* hold, and the framework offers no addon→addon byte transport — the one
|
|
24882
|
+
* precedent is the broker's own restream URLs (`getStreamWithCodec`), which
|
|
24883
|
+
* a consumer dials directly, and this is that precedent one addon over.
|
|
24884
|
+
*/
|
|
24885
|
+
/**
|
|
24886
|
+
* The audio a clip stream can be asked for, and the audio it carries.
|
|
24887
|
+
*
|
|
24888
|
+
* The camera stores AAC and no WebRTC session speaks it, so a stream that
|
|
24889
|
+
* carries sound carries it TRANSCODED, muxed as a second track beside the
|
|
24890
|
+
* video by the one ffmpeg the provider already runs (D600). `opus` is
|
|
24891
|
+
* 48 kHz mono, `pcmu` is 8 kHz µ-law — the two codecs a broker session
|
|
24892
|
+
* negotiates — and `none` is a stream asked for, or served, without sound.
|
|
24893
|
+
*/
|
|
24894
|
+
var ClipStreamAudioSchema = _enum([
|
|
24895
|
+
"opus",
|
|
24896
|
+
"pcmu",
|
|
24897
|
+
"none"
|
|
24898
|
+
]);
|
|
24899
|
+
/**
|
|
24900
|
+
* Why a stream that was ASKED for sound is not carrying it (D600).
|
|
24901
|
+
*
|
|
24902
|
+
* Only ever set beside `servedAudio: 'none'` after a non-`none` ask. Absent
|
|
24903
|
+
* means the ask was honoured, or there was no ask.
|
|
24904
|
+
*
|
|
24905
|
+
* - `not-requested` — the caller asked for `none`.
|
|
24906
|
+
* - `unsupported-ask` — the provider cannot produce that codec.
|
|
24907
|
+
*
|
|
24908
|
+
* Whether the RECORDING has sound at all is not knowable here: the camera
|
|
24909
|
+
* declares no audio track before its first frame arrives. That answer is in
|
|
24910
|
+
* the stream itself — an fMP4 with no audio track — and the broker reads it
|
|
24911
|
+
* there. A dial that says `opus` promises the codec, never the content.
|
|
24912
|
+
*/
|
|
24913
|
+
var ClipStreamAudioReasonSchema = _enum(["not-requested", "unsupported-ask"]);
|
|
24914
|
+
var ClipStreamDialSchema = object({
|
|
24915
|
+
url: string().min(1),
|
|
24916
|
+
/** After this the token is gone and the URL answers 404 `dial-expired`. */
|
|
24917
|
+
expiresAtMs: number(),
|
|
24918
|
+
/** What the stream IS — a consumer that cannot honour it must not dial. */
|
|
24919
|
+
transport: literal("forward-only"),
|
|
24920
|
+
/**
|
|
24921
|
+
* Which twin the stream ACTUALLY carries — not what was asked for.
|
|
24922
|
+
*
|
|
24923
|
+
* The stream serves every profile (D599), and a recording whose main twin
|
|
24924
|
+
* does not exist is streamed as its sub copy, which this field reports as
|
|
24925
|
+
* `low`. That is the whole reason the dial no longer refuses `high`:
|
|
24926
|
+
* stating what was served is what makes accepting honest, where a silent
|
|
24927
|
+
* substitution would be the 33x lie D576 forbids. Stated rather than
|
|
24928
|
+
* assumed, the way {@link ClipPlaybackSchema.served} is (D549 15).
|
|
24929
|
+
*/
|
|
24930
|
+
served: CamProfileSchema,
|
|
24931
|
+
/**
|
|
24932
|
+
* Which audio the stream will ACTUALLY carry — `served`'s companion for
|
|
24933
|
+
* sound, and the same reasoning (D599, D600).
|
|
24934
|
+
*
|
|
24935
|
+
* The broker asks for the codec its WebRTC session negotiated, and the
|
|
24936
|
+
* provider either muxes that codec or carries none. Stating it is what
|
|
24937
|
+
* lets the broker wire the matching sink before the first byte, and what
|
|
24938
|
+
* keeps a session from being handed a codec it cannot send — the audio
|
|
24939
|
+
* shape of the substitution `served` exists to prevent.
|
|
24940
|
+
*
|
|
24941
|
+
* It is a promise about the CODEC, not about the content: a recording with
|
|
24942
|
+
* no microphone track answers `opus` here and then arrives with no audio
|
|
24943
|
+
* track at all. That second answer is in the stream, and the broker reads
|
|
24944
|
+
* it from the `moov`.
|
|
24945
|
+
*/
|
|
24946
|
+
servedAudio: ClipStreamAudioSchema,
|
|
24947
|
+
/** Why `servedAudio` is `none` although sound was asked for. */
|
|
24948
|
+
audioReason: ClipStreamAudioReasonSchema.optional()
|
|
24949
|
+
});
|
|
24950
|
+
var ClipSourceAvailabilitySchema = object({
|
|
24951
|
+
state: _enum([
|
|
24952
|
+
"ok",
|
|
24953
|
+
"sleeping",
|
|
24954
|
+
"unreachable",
|
|
24955
|
+
"no-storage",
|
|
24956
|
+
"index-empty"
|
|
24957
|
+
]),
|
|
24958
|
+
/** Free text, shown verbatim. Names the camera's own refusal when there is one. */
|
|
24959
|
+
reason: string().optional(),
|
|
24960
|
+
/** When this source's catalog was last CONFIRMED. A cached list is never
|
|
24961
|
+
* drawn as current: the surface shows the age whenever it is older than the
|
|
24962
|
+
* refresh interval. */
|
|
24963
|
+
catalogAsOf: number().optional()
|
|
24964
|
+
});
|
|
24965
|
+
/**
|
|
24966
|
+
* One SOURCE of clips for a camera — a row of the picker, and the namespace
|
|
24967
|
+
* every clip id from it is prefixed with (`analytics`,
|
|
24968
|
+
* `native:reolink:onboard`, `hksv`, …).
|
|
24969
|
+
*
|
|
24970
|
+
* A provider lists the sources IT serves for that device, and answers for each
|
|
24971
|
+
* of them whether it can answer at all. A provider with nothing to offer on a
|
|
24972
|
+
* camera returns `[]` — it is not that camera's business.
|
|
24973
|
+
*/
|
|
24974
|
+
var ClipSourceSchema = object({
|
|
24975
|
+
/** The value this source stamps on {@link ClipSchema.source}, and the prefix
|
|
24976
|
+
* of every clip id it mints. `getClipPlayback` routes on it. */
|
|
24977
|
+
source: string(),
|
|
24978
|
+
/** Operator-facing name of the source ("CamStack recordings", "SD card"). */
|
|
24979
|
+
label: string(),
|
|
24980
|
+
/**
|
|
24981
|
+
* The addon that SERVES this row.
|
|
24982
|
+
*
|
|
24983
|
+
* A surface cannot otherwise resolve a source to who answers it, and the
|
|
24984
|
+
* alternative — a `native:reolink:* → provider-reolink` table inside the
|
|
24985
|
+
* widget — is a second authority on provider identity living in the one
|
|
24986
|
+
* package with no business knowing it, wrong the day a third source appears
|
|
24987
|
+
* (D557). One addon may serve SEVERAL sources, which is why the per-device
|
|
24988
|
+
* switch is keyed by SOURCE and not by this (D555) — `addon-provider-reolink`
|
|
24989
|
+
* served a hub child two of them until the two views were measured to be one
|
|
24990
|
+
* store read twice (D555) and then read ONE way (D565).
|
|
24991
|
+
*
|
|
24992
|
+
* Optional for version skew only. The collection dispatcher stamps it from
|
|
24993
|
+
* the registry, so a row that travelled through the fan-out carries the
|
|
24994
|
+
* authoritative id whatever the provider filled in.
|
|
24995
|
+
*/
|
|
24996
|
+
addonId: string().optional(),
|
|
24997
|
+
availability: ClipSourceAvailabilitySchema
|
|
24998
|
+
});
|
|
24322
24999
|
DeviceType.Camera, method(object({
|
|
24323
25000
|
deviceId: number(),
|
|
24324
25001
|
since: number(),
|
|
24325
25002
|
until: number(),
|
|
24326
|
-
limit: number().int().positive().optional()
|
|
25003
|
+
limit: number().int().positive().optional(),
|
|
25004
|
+
/**
|
|
25005
|
+
* WHICH provider to ask — the `addonId` a {@link ClipSourceSchema} row
|
|
25006
|
+
* carries, never a source id and never a list. **Required** (D554 amended).
|
|
25007
|
+
*
|
|
25008
|
+
* A provider the device is not bound to is refused by name rather than
|
|
25009
|
+
* answered by another one (D552's `rejectUnresolvedAddonPin` rule).
|
|
25010
|
+
*
|
|
25011
|
+
* It was optional, documented as "absent means the device's BOUND
|
|
25012
|
+
* provider, which is CamStack on every camera". No code implemented
|
|
25013
|
+
* that. Measured on the live hub 2026-09-20 — device 592, bound to
|
|
25014
|
+
* `recorder` AND `provider-reolink` — a bare call with `limit: 3`
|
|
25015
|
+
* answered SIX rows, three from each source, merged newest-first:
|
|
25016
|
+
* `device-collection-dispatch.ts` simply left the fan-out un-narrowed,
|
|
25017
|
+
* so absence bought the union this method exists not to be, and
|
|
25018
|
+
* `limit` meant `limit × sources`.
|
|
25019
|
+
*
|
|
25020
|
+
* There is nothing to restore the default to. `getBindings` answers a
|
|
25021
|
+
* collection cap with a DERIVED, PLURAL set (D554 amended, step 0);
|
|
25022
|
+
* `setWrapperActive` — the singleton authority that could name one —
|
|
25023
|
+
* throws for a collection cap by design. Naming CamStack here instead
|
|
25024
|
+
* would privilege one addon by id inside a surface whose premise is
|
|
25025
|
+
* that sources are peers, and would be wrong on the first camera with
|
|
25026
|
+
* no recorder binding. So absence is REFUSED, in the schema, where the
|
|
25027
|
+
* generated types make it unomittable rather than merely discouraged.
|
|
25028
|
+
*
|
|
25029
|
+
* The default belongs to the SURFACE, which has the `listSources` rows
|
|
25030
|
+
* and can say which one it picked (D569 § 1–3; the viewer already
|
|
25031
|
+
* always sends this).
|
|
25032
|
+
*
|
|
25033
|
+
* It replaced `sources?: string[]`, a VIEW filter over source ids that
|
|
25034
|
+
* assumed the answer was a fan-out over everything a camera has. The
|
|
25035
|
+
* operator settled otherwise on 2026-09-20 — *"l'utilizzatore è uno
|
|
25036
|
+
* solo"* — so the list is asked of one provider at a time and there is
|
|
25037
|
+
* nothing to filter out of it.
|
|
25038
|
+
*/
|
|
25039
|
+
provider: string().min(1)
|
|
24327
25040
|
}), array(ClipSchema).readonly(), {
|
|
24328
25041
|
kind: "query",
|
|
24329
|
-
auth: "
|
|
25042
|
+
auth: "protected"
|
|
25043
|
+
}), method(object({ deviceId: number() }), array(ClipSourceSchema).readonly(), {
|
|
25044
|
+
kind: "query",
|
|
25045
|
+
auth: "protected"
|
|
24330
25046
|
}), method(object({
|
|
24331
25047
|
deviceId: number(),
|
|
24332
|
-
clipId: string()
|
|
25048
|
+
clipId: string(),
|
|
25049
|
+
/**
|
|
25050
|
+
* Which twin to serve, on the ONE quality scale the system already has
|
|
25051
|
+
* (`CamProfileSchema`). `low | mid` → the sub file, `high` → the main
|
|
25052
|
+
* twin; both ids are already on the row so this never re-searches the
|
|
25053
|
+
* camera. Absent means the provider's own default (the sub file, which
|
|
25054
|
+
* every source is measured to hold).
|
|
25055
|
+
*
|
|
25056
|
+
* `auto` is deliberately NOT accepted here: a stored file has no
|
|
25057
|
+
* broker session, so the adaptive tier cannot be resolved for it. The
|
|
25058
|
+
* viewer resolves `auto` to a profile the same way live does, before
|
|
25059
|
+
* it calls (D549 15).
|
|
25060
|
+
*/
|
|
25061
|
+
profile: CamProfileSchema.optional()
|
|
24333
25062
|
}), ClipPlaybackSchema, {
|
|
24334
25063
|
kind: "query",
|
|
24335
|
-
auth: "
|
|
25064
|
+
auth: "protected"
|
|
25065
|
+
}), optionalMethod(object({
|
|
25066
|
+
deviceId: number(),
|
|
25067
|
+
clipId: string().min(1),
|
|
25068
|
+
/**
|
|
25069
|
+
* WHICH provider holds the bytes — the `addonId` a
|
|
25070
|
+
* {@link ClipSourceSchema} row carries. **Required**, for the reason
|
|
25071
|
+
* `listClips` states one method above and for a second one this method
|
|
25072
|
+
* learned the hard way.
|
|
25073
|
+
*
|
|
25074
|
+
* The caller is the stream broker, and the broker lives in
|
|
25075
|
+
* `addon-pipeline` — the same addon that registers the RECORDER's
|
|
25076
|
+
* videoclips provider. With no provider named, the in-process
|
|
25077
|
+
* resolution handed it its own sibling, which implements this method
|
|
25078
|
+
* solely to explain that an analytics visit has no file behind it. So
|
|
25079
|
+
* every onboard clip answered with that sentence, a flash of video and
|
|
25080
|
+
* nothing more, while the SAME call from outside the addon reached the
|
|
25081
|
+
* right provider and returned the bytes (measured on 3628,
|
|
25082
|
+
* 2026-09-21).
|
|
25083
|
+
*/
|
|
25084
|
+
provider: string().min(1),
|
|
25085
|
+
/**
|
|
25086
|
+
* Which twin to fetch, on the mapping D549 15 already fixed:
|
|
25087
|
+
* `low | mid` → the sub file, `high` → the main twin. `auto` is not
|
|
25088
|
+
* accepted here for the same reason it is not accepted by
|
|
25089
|
+
* `getClipPlayback` — a stored file has no broker session, so the
|
|
25090
|
+
* adaptive tier cannot be resolved for it.
|
|
25091
|
+
*/
|
|
25092
|
+
profile: CamProfileSchema.optional(),
|
|
25093
|
+
/**
|
|
25094
|
+
* The CALLER's byte bound, so an over-size clip is refused before it is
|
|
25095
|
+
* read and encoded rather than after. Capped by
|
|
25096
|
+
* {@link VIDEOCLIPS_MAX_READ_BYTES} whatever is passed; absent means
|
|
25097
|
+
* that ceiling.
|
|
25098
|
+
*/
|
|
25099
|
+
maxBytes: number().int().positive().optional(),
|
|
25100
|
+
/**
|
|
25101
|
+
* The operator's authorisation to wake a sleeping camera for this
|
|
25102
|
+
* read. Absent — the default — means a sleeping standalone battery
|
|
25103
|
+
* camera is REFUSED by name, before any session is opened.
|
|
25104
|
+
*/
|
|
25105
|
+
wake: ClipWakeSchema.optional()
|
|
25106
|
+
}), ClipBytesSchema, {
|
|
25107
|
+
kind: "query",
|
|
25108
|
+
auth: "protected"
|
|
25109
|
+
}), optionalMethod(object({
|
|
25110
|
+
deviceId: number(),
|
|
25111
|
+
clipId: string().min(1),
|
|
25112
|
+
/** WHICH provider holds the camera — see `readClipBytes.provider`. */
|
|
25113
|
+
provider: string().min(1),
|
|
25114
|
+
/** Which twin — `high` → the main file, everything else the sub (D599). */
|
|
25115
|
+
profile: CamProfileSchema.optional(),
|
|
25116
|
+
/**
|
|
25117
|
+
* Which audio codec the caller's session negotiated, so the provider
|
|
25118
|
+
* can mux it (D600). The ask travels on the DIAL because the mux is
|
|
25119
|
+
* spawned once, before the first byte, and cannot gain a track later.
|
|
25120
|
+
*
|
|
25121
|
+
* ABSENT IS `none`, never a guess: a caller that does not state an
|
|
25122
|
+
* ask is served the video-only stream that shipped first, so a broker
|
|
25123
|
+
* older than this field keeps working unchanged (D382 — an unstated
|
|
25124
|
+
* ask is zero).
|
|
25125
|
+
*/
|
|
25126
|
+
audio: ClipStreamAudioSchema.optional()
|
|
25127
|
+
}), ClipStreamDialSchema, {
|
|
25128
|
+
kind: "query",
|
|
25129
|
+
auth: "protected"
|
|
24336
25130
|
});
|
|
24337
25131
|
/**
|
|
24338
25132
|
* Optional client-side hints sent at session creation to help the provider
|
|
@@ -24404,6 +25198,104 @@ var WebrtcStreamChoiceSchema = object({
|
|
|
24404
25198
|
/** Bitrate in kbps (null for adaptive). */
|
|
24405
25199
|
bitrateKbps: number().nullable()
|
|
24406
25200
|
});
|
|
25201
|
+
/**
|
|
25202
|
+
* The last move the adaptive ladder made on this session, and the numbers it
|
|
25203
|
+
* rested on. Mirrors `LadderDecision` in the stream-broker's
|
|
25204
|
+
* `adaptive-controller.ts` — the log line's evidence, made readable.
|
|
25205
|
+
*
|
|
25206
|
+
* Nullability is load-bearing: `lossRatio: null` means the loss tracker had
|
|
25207
|
+
* no window to report, which is NOT a link with zero loss.
|
|
25208
|
+
*/
|
|
25209
|
+
var WebrtcLadderDecisionSchema = object({
|
|
25210
|
+
/** `loss` — RTCP / client-reported packet loss; `stall` — the picture
|
|
25211
|
+
* stopped moving; `healthy` — a promotion. */
|
|
25212
|
+
cause: _enum([
|
|
25213
|
+
"loss",
|
|
25214
|
+
"stall",
|
|
25215
|
+
"healthy"
|
|
25216
|
+
]),
|
|
25217
|
+
/** The rung the ladder moved TO. Null for a move that changed no rung
|
|
25218
|
+
* (an MTU reduction, a forced transcode-down at the bottom). */
|
|
25219
|
+
tier: CamProfileSchema.nullable(),
|
|
25220
|
+
/** Whether that move forced the egress transcode-down rung. */
|
|
25221
|
+
forcedTranscodeDown: boolean(),
|
|
25222
|
+
/** Loss ratio over the tracker's window at the moment of the decision. */
|
|
25223
|
+
lossRatio: number().nullable(),
|
|
25224
|
+
/** The client's reported downlink at that moment, in kbps. */
|
|
25225
|
+
clientKbps: number().nullable(),
|
|
25226
|
+
/** How long the stall detector had been seeing nothing arrive. */
|
|
25227
|
+
stalledForMs: number().nullable(),
|
|
25228
|
+
/** Whether the broker WAS sending while that was measured — the half that
|
|
25229
|
+
* separates "the camera stopped" from "the link stopped". */
|
|
25230
|
+
serverSending: boolean().nullable(),
|
|
25231
|
+
/** Epoch ms. The overlay prints its age; a decision from 40 minutes ago
|
|
25232
|
+
* must not read like one from this second. */
|
|
25233
|
+
at: number()
|
|
25234
|
+
});
|
|
25235
|
+
/** Per-tier count of MEASURED downgrades this viewer session has suffered. */
|
|
25236
|
+
var WebrtcTierFailureSchema = object({
|
|
25237
|
+
tier: CamProfileSchema,
|
|
25238
|
+
failures: number().int().nonnegative()
|
|
25239
|
+
});
|
|
25240
|
+
/**
|
|
25241
|
+
* What the SERVER decided for one WebRTC session — the half the client cannot
|
|
25242
|
+
* measure. See `getSessionDebug`.
|
|
25243
|
+
*/
|
|
25244
|
+
var WebrtcSessionDebugSchema = object({
|
|
25245
|
+
sessionId: string(),
|
|
25246
|
+
/** Whether the session still exists here. Everything below is null when
|
|
25247
|
+
* it does not — a dead session has no measurements, not zero ones. */
|
|
25248
|
+
alive: boolean(),
|
|
25249
|
+
/** Epoch ms this snapshot was taken, so the overlay can show its age. */
|
|
25250
|
+
observedAt: number(),
|
|
25251
|
+
/** The broker actually feeding this session (`<deviceId>/<camStreamId>`). */
|
|
25252
|
+
brokerId: string().nullable(),
|
|
25253
|
+
camStreamId: string().nullable(),
|
|
25254
|
+
/** `derived` = this hub BUILT the rung being served (a transcode/scale of
|
|
25255
|
+
* another stream); `native` = the camera's own encode. */
|
|
25256
|
+
sourceKind: _enum([
|
|
25257
|
+
"native",
|
|
25258
|
+
"derived",
|
|
25259
|
+
"unknown"
|
|
25260
|
+
]),
|
|
25261
|
+
/** Whether the ladder is running at all (an `adaptive` target). */
|
|
25262
|
+
adaptive: boolean(),
|
|
25263
|
+
/** The rung `auto` ACTUALLY resolved to right now. Null when the broker
|
|
25264
|
+
* is bound to no profile slot (a manually-activated stream). */
|
|
25265
|
+
servedTier: CamProfileSchema.nullable(),
|
|
25266
|
+
/** Which node ingests the source. Null when unresolved. */
|
|
25267
|
+
ingestNodeId: string().nullable(),
|
|
25268
|
+
/** Whether that node is a remote one (the media crosses a node boundary
|
|
25269
|
+
* before it reaches this session). Null when unresolved. */
|
|
25270
|
+
ingestIsRemote: boolean().nullable(),
|
|
25271
|
+
/** Codec off the source, and the one negotiated with this client. */
|
|
25272
|
+
sourceCodec: string().nullable(),
|
|
25273
|
+
negotiatedCodec: string().nullable(),
|
|
25274
|
+
/** Whether the EGRESS leg re-encodes for this client or forwards the
|
|
25275
|
+
* source bytes untouched. */
|
|
25276
|
+
egressLeg: _enum([
|
|
25277
|
+
"copy",
|
|
25278
|
+
"transcode",
|
|
25279
|
+
"unknown"
|
|
25280
|
+
]),
|
|
25281
|
+
/** What the broker itself reports about the rung it is serving. */
|
|
25282
|
+
brokerStatus: string().nullable(),
|
|
25283
|
+
brokerCodec: string().nullable(),
|
|
25284
|
+
brokerBitrateKbps: number().nullable(),
|
|
25285
|
+
brokerInputFps: number().nullable(),
|
|
25286
|
+
brokerUptimeMs: number().nullable(),
|
|
25287
|
+
/** RTP packets this session has sent — the server's own "am I sending?". */
|
|
25288
|
+
rtpPacketsSent: number().nullable(),
|
|
25289
|
+
/** D42: the track may be carrying the recorded feeder, not live. The ladder
|
|
25290
|
+
* is suspended there, so a frozen picture in `recorded` mode means
|
|
25291
|
+
* something entirely different. */
|
|
25292
|
+
trackMode: _enum(["live", "recorded"]).nullable(),
|
|
25293
|
+
lastDecision: WebrtcLadderDecisionSchema.nullable(),
|
|
25294
|
+
tierFailures: array(WebrtcTierFailureSchema),
|
|
25295
|
+
/** Successful promotions in this viewer session (drives the progressive
|
|
25296
|
+
* healthy timer). Null when no ladder is running. */
|
|
25297
|
+
upgradeCount: number().nullable()
|
|
25298
|
+
});
|
|
24407
25299
|
DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), array(WebrtcStreamChoiceSchema)), method(object({
|
|
24408
25300
|
deviceId: number().int().nonnegative(),
|
|
24409
25301
|
target: WebrtcStreamTargetSchema,
|
|
@@ -24568,7 +25460,10 @@ DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), ar
|
|
|
24568
25460
|
* the same answer as "it ended": either way nothing is holding it up.
|
|
24569
25461
|
*/
|
|
24570
25462
|
alive: boolean()
|
|
24571
|
-
}))
|
|
25463
|
+
})), method(object({
|
|
25464
|
+
deviceId: number().int().nonnegative(),
|
|
25465
|
+
sessionId: string()
|
|
25466
|
+
}), WebrtcSessionDebugSchema, { kind: "query" });
|
|
24572
25467
|
object({
|
|
24573
25468
|
/** All accessory children of the parent. */
|
|
24574
25469
|
childDeviceIds: array(number()).readonly(),
|
|
@@ -26411,6 +27306,346 @@ var dayNightCapability = {
|
|
|
26411
27306
|
volatileStateFields: ["lastFetchedAt"]
|
|
26412
27307
|
};
|
|
26413
27308
|
/**
|
|
27309
|
+
* Vendor-neutral **onboard** recording + storage cap — what the CAMERA
|
|
27310
|
+
* writes to the CAMERA's own card, on the camera's own schedule.
|
|
27311
|
+
*
|
|
27312
|
+
* This is NOT `recording.cap.ts`. That one is CamStack's recorder: our
|
|
27313
|
+
* footage ledger, our storage locations, our retention. This one has a
|
|
27314
|
+
* different authority — the camera's firmware — and per D62 it stores
|
|
27315
|
+
* nothing of its own. Every value here is read from the camera and every
|
|
27316
|
+
* write goes back to the camera; there is no CamStack-side mirror that
|
|
27317
|
+
* could disagree with the device.
|
|
27318
|
+
*
|
|
27319
|
+
* ## One shape, two firmwares
|
|
27320
|
+
*
|
|
27321
|
+
* Measured 2026-09-22 against the live fleet:
|
|
27322
|
+
*
|
|
27323
|
+
* | fact | Hikvision (ISAPI) | Reolink (Baichuan) |
|
|
27324
|
+
* | --- | --- | --- |
|
|
27325
|
+
* | storage | `ContentMgmt/Storage` `<hdd>` rows: status, capacity, freeSpace (MB) | `getHddInfoList` (cmd 102): `mount`, `format`, `capacity` GB + `capacityM` MB remainder |
|
|
27326
|
+
* | tracks | several (101 **and** 103 on both 1436 and 3833), each with its own schedule | one per channel |
|
|
27327
|
+
* | schedule | per track, 7 `ScheduleAction` blocks: DayOfWeek + TimeOfDay range + ONE `ActionRecordingMode` | per trigger type, a 168-char weekly HOUR mask |
|
|
27328
|
+
* | triggers | `CMR`, `MOTION` | `Normal`, `MD`, `people`, `vehicle`, `dog_cat`, `crossline`, `intrude`, `loitering` |
|
|
27329
|
+
* | pre-record | `PreRecordTimeSeconds` | `preRecordTime` |
|
|
27330
|
+
* | post-record | `PostRecordTimeSeconds` | `recordDelayTime` |
|
|
27331
|
+
* | overwrite | per track `LoopEnable` | `cycle`, with `cycleList` enumerating the accepted values |
|
|
27332
|
+
* | segment length | not exposed on V5.7.1 | `packageTime` (minutes) |
|
|
27333
|
+
*
|
|
27334
|
+
* The two schedule models look different and are the same thing in
|
|
27335
|
+
* different coordinates: both answer "for this trigger, during which
|
|
27336
|
+
* weekly windows does the camera record". {@link RecordWindow} is that
|
|
27337
|
+
* question in one shape — Hikvision's ranges map straight onto it,
|
|
27338
|
+
* Reolink's mask expands into hour-aligned windows.
|
|
27339
|
+
*
|
|
27340
|
+
* ## Union, not intersection
|
|
27341
|
+
*
|
|
27342
|
+
* **The same fields exist on every camera.** What differs per device is
|
|
27343
|
+
* which VALUES that device accepts, and that is what {@link
|
|
27344
|
+
* RecordingOnboardOptions} reports — a `{ readable, writable, reason }`
|
|
27345
|
+
* per field plus the schedule's own limits. A control a camera cannot
|
|
27346
|
+
* honour is rendered DISABLED WITH ITS REASON, never missing and never
|
|
27347
|
+
* dead: disabled must not look like broken.
|
|
27348
|
+
*
|
|
27349
|
+
* ## Refusal by name
|
|
27350
|
+
*
|
|
27351
|
+
* A write a camera cannot honour is refused with a sentence the operator
|
|
27352
|
+
* can read — never accepted and dropped. Both providers refuse through
|
|
27353
|
+
* {@link describeOnboardRefusal}, so the vocabulary is one function and
|
|
27354
|
+
* one test, not two hand-written vendor opinions.
|
|
27355
|
+
*
|
|
27356
|
+
* Follows the D14 `deviceConfig` archetype (see `stream-params.cap.ts`):
|
|
27357
|
+
* `getOptions` advertises per-camera availability, `getStatus` (auto-
|
|
27358
|
+
* injected from `status`) reports the live values, and a single
|
|
27359
|
+
* `setSettings` mutation applies a partial change. No hand-written
|
|
27360
|
+
* settings-contribution methods.
|
|
27361
|
+
*/
|
|
27362
|
+
/**
|
|
27363
|
+
* What makes the camera start recording during a window.
|
|
27364
|
+
*
|
|
27365
|
+
* The union of both vendors' vocabularies. `continuous` is Hikvision's
|
|
27366
|
+
* `CMR` and Reolink's `Normal`; `motion` is `MOTION` / `MD`. The
|
|
27367
|
+
* object-class triggers are Reolink-only today and the smart-event ones
|
|
27368
|
+
* (`lineCrossing`, `intrusion`, `loitering`) are Reolink-only on the
|
|
27369
|
+
* firmwares measured — a camera that cannot record on a trigger simply
|
|
27370
|
+
* does not list it in `options.schedule.triggers`, and a window naming
|
|
27371
|
+
* it is REFUSED, not dropped.
|
|
27372
|
+
*/
|
|
27373
|
+
var RecordTriggerSchema = _enum([
|
|
27374
|
+
"continuous",
|
|
27375
|
+
"motion",
|
|
27376
|
+
"person",
|
|
27377
|
+
"vehicle",
|
|
27378
|
+
"animal",
|
|
27379
|
+
"lineCrossing",
|
|
27380
|
+
"intrusion",
|
|
27381
|
+
"loitering",
|
|
27382
|
+
"alarmInput"
|
|
27383
|
+
]);
|
|
27384
|
+
/**
|
|
27385
|
+
* One weekly recording window: "on `day`, from `startMinute` to
|
|
27386
|
+
* `endMinute`, record on `trigger`".
|
|
27387
|
+
*
|
|
27388
|
+
* `day` is 0 = Monday … 6 = Sunday (ISO order, which is also the order
|
|
27389
|
+
* both firmwares enumerate). Minutes are local camera time since
|
|
27390
|
+
* midnight; `endMinute` may be 1440, meaning end of day — that is
|
|
27391
|
+
* Hikvision's literal `24:00` and Reolink's 24th mask slot, and
|
|
27392
|
+
* collapsing it to 0 would turn a whole-day window into an empty one.
|
|
27393
|
+
*/
|
|
27394
|
+
var RecordWindowSchema = object({
|
|
27395
|
+
trigger: RecordTriggerSchema,
|
|
27396
|
+
day: number().int().min(0).max(6),
|
|
27397
|
+
startMinute: number().int().min(0).max(1439),
|
|
27398
|
+
endMinute: number().int().min(1).max(1440)
|
|
27399
|
+
});
|
|
27400
|
+
/** Status of one physical volume, as the camera itself describes it. */
|
|
27401
|
+
var OnboardStorageVolumeSchema = object({
|
|
27402
|
+
/** The camera's own id for the volume (`hdd/id`, Reolink `HddInfo/number`). */
|
|
27403
|
+
id: string(),
|
|
27404
|
+
/** The camera's own name for it, when it gives one (`hddName`). */
|
|
27405
|
+
label: string().optional(),
|
|
27406
|
+
status: _enum([
|
|
27407
|
+
"ok",
|
|
27408
|
+
"unformatted",
|
|
27409
|
+
"error",
|
|
27410
|
+
"offline",
|
|
27411
|
+
"unknown"
|
|
27412
|
+
]),
|
|
27413
|
+
/**
|
|
27414
|
+
* Total size in MB, or **null when the camera did not say**.
|
|
27415
|
+
*
|
|
27416
|
+
* Never 0 for an unreadable value: a measurement that failed is not a
|
|
27417
|
+
* measurement (D393), and a card whose size is unknown must not be
|
|
27418
|
+
* rendered as a card of size zero.
|
|
27419
|
+
*/
|
|
27420
|
+
capacityMb: number().nullable(),
|
|
27421
|
+
/**
|
|
27422
|
+
* Free space in MB, or null when unknown.
|
|
27423
|
+
*
|
|
27424
|
+
* **Not a proxy for "has footage".** Measured 2026-09-22: 1436 and
|
|
27425
|
+
* 1439 both report exactly 11776 MB free — the fixed reserve a looping
|
|
27426
|
+
* card converges on once it has wrapped. At loop steady state the
|
|
27427
|
+
* number is identical whether the camera recorded yesterday or stopped
|
|
27428
|
+
* a month ago.
|
|
27429
|
+
*/
|
|
27430
|
+
freeMb: number().nullable(),
|
|
27431
|
+
/** True when the camera reports the volume writable (`property` RW). */
|
|
27432
|
+
writable: boolean().optional()
|
|
27433
|
+
});
|
|
27434
|
+
/**
|
|
27435
|
+
* What the camera is doing with its own storage, right now.
|
|
27436
|
+
*
|
|
27437
|
+
* Every scalar is nullable and **null means the camera did not answer**,
|
|
27438
|
+
* never a default. A form that seeds `0` from an unanswered read invites
|
|
27439
|
+
* the operator to save that 0 back onto the camera.
|
|
27440
|
+
*/
|
|
27441
|
+
var RecordingOnboardStatusSchema = object({
|
|
27442
|
+
storage: discriminatedUnion("kind", [
|
|
27443
|
+
object({
|
|
27444
|
+
kind: literal("present"),
|
|
27445
|
+
volumes: array(OnboardStorageVolumeSchema)
|
|
27446
|
+
}),
|
|
27447
|
+
object({
|
|
27448
|
+
kind: literal("absent"),
|
|
27449
|
+
reason: string()
|
|
27450
|
+
}),
|
|
27451
|
+
object({
|
|
27452
|
+
kind: literal("unknown"),
|
|
27453
|
+
reason: string()
|
|
27454
|
+
})
|
|
27455
|
+
]),
|
|
27456
|
+
tracks: array(object({
|
|
27457
|
+
id: string(),
|
|
27458
|
+
enabled: boolean(),
|
|
27459
|
+
isVideo: boolean(),
|
|
27460
|
+
/** From the camera's own track description. Null when it does not say. */
|
|
27461
|
+
codec: string().nullable(),
|
|
27462
|
+
resolution: string().nullable(),
|
|
27463
|
+
/** Per-track overwrite flag, where the firmware keeps it per track. */
|
|
27464
|
+
overwriteWhenFull: boolean().nullable()
|
|
27465
|
+
})),
|
|
27466
|
+
/**
|
|
27467
|
+
* The track the write path targets — the enabled VIDEO one. Null when
|
|
27468
|
+
* no track could be identified, which is itself a refusal reason.
|
|
27469
|
+
*/
|
|
27470
|
+
primaryTrackId: string().nullable(),
|
|
27471
|
+
/** Master "record to the card at all" switch. */
|
|
27472
|
+
enabled: boolean().nullable(),
|
|
27473
|
+
overwriteWhenFull: boolean().nullable(),
|
|
27474
|
+
preRecordSec: number().nullable(),
|
|
27475
|
+
postRecordSec: number().nullable(),
|
|
27476
|
+
/** Length of one recorded file, in minutes. */
|
|
27477
|
+
segmentMinutes: number().nullable(),
|
|
27478
|
+
/** The primary track's weekly windows, flattened. */
|
|
27479
|
+
windows: array(RecordWindowSchema),
|
|
27480
|
+
/**
|
|
27481
|
+
* How many windows the camera described that CamStack could NOT read —
|
|
27482
|
+
* an unrecognised trigger, an unparseable clock, a weekday it does not
|
|
27483
|
+
* name.
|
|
27484
|
+
*
|
|
27485
|
+
* A dropped window is work the reader threw away, and a schedule that
|
|
27486
|
+
* silently shows fewer rows than the camera holds is how an operator
|
|
27487
|
+
* saves back a schedule shorter than the one they were looking at
|
|
27488
|
+
* (D391). Non-zero means the window list is INCOMPLETE and a write
|
|
27489
|
+
* that replaces it would delete what was not shown — which is why a
|
|
27490
|
+
* provider reporting a non-zero count also reports the schedule as not
|
|
27491
|
+
* writable.
|
|
27492
|
+
*/
|
|
27493
|
+
unreadableWindows: number(),
|
|
27494
|
+
/**
|
|
27495
|
+
* The camera is scheduled to record and has NO usable storage.
|
|
27496
|
+
*
|
|
27497
|
+
* A first-class fact because it is the fleet's most common silent
|
|
27498
|
+
* defect: measured 2026-09-22, 1441 and 3831 are both motion-recording
|
|
27499
|
+
* to a card that is not there. Neither the schedule nor the storage
|
|
27500
|
+
* read says anything wrong on its own; only the pair does.
|
|
27501
|
+
*/
|
|
27502
|
+
recordingToNowhere: boolean(),
|
|
27503
|
+
lastFetchedAt: number()
|
|
27504
|
+
});
|
|
27505
|
+
/** Numeric range descriptor — `{ min, max, step }` per the getOptions convention. */
|
|
27506
|
+
var RangeSchema = object({
|
|
27507
|
+
min: number(),
|
|
27508
|
+
max: number(),
|
|
27509
|
+
step: number()
|
|
27510
|
+
});
|
|
27511
|
+
/**
|
|
27512
|
+
* The values a camera actually takes for a numeric field, when they are a SET
|
|
27513
|
+
* rather than a range.
|
|
27514
|
+
*
|
|
27515
|
+
* `{min,max,step}` cannot say what these two firmwares do. Measured on 1436
|
|
27516
|
+
* (I91DN) on 2026-09-22 by writing each value and reading it back:
|
|
27517
|
+
*
|
|
27518
|
+
* - pre-record: `0, 5, 10, 15, 20, 25, 30` and `2147483647` (INT32_MAX, the
|
|
27519
|
+
* camera's "no limit" — `-1` and `4294967295` both land on it);
|
|
27520
|
+
* - post-record: `5, 10, 30, 60, 120, 300, 600`.
|
|
27521
|
+
*
|
|
27522
|
+
* Neither is expressible as a step: the first has a sentinel two billion away
|
|
27523
|
+
* from its neighbours, the second doubles and then jumps. A range that tried
|
|
27524
|
+
* would forbid values the camera takes AND permit values it silently replaces
|
|
27525
|
+
* with 5 — wrong in both directions at once.
|
|
27526
|
+
*
|
|
27527
|
+
* `sentinel` names the member that is not a duration, so a surface can render
|
|
27528
|
+
* "no limit" instead of `2147483647` seconds.
|
|
27529
|
+
*/
|
|
27530
|
+
var AllowedValuesSchema = object({
|
|
27531
|
+
values: array(number()).min(1),
|
|
27532
|
+
sentinel: object({
|
|
27533
|
+
value: number(),
|
|
27534
|
+
meaning: _enum(["no-limit", "disabled"])
|
|
27535
|
+
}).optional()
|
|
27536
|
+
});
|
|
27537
|
+
/**
|
|
27538
|
+
* Per-field availability on ONE camera.
|
|
27539
|
+
*
|
|
27540
|
+
* The field exists on every camera — this says whether this one can be
|
|
27541
|
+
* read and whether it can be written, and `reason` says why not when
|
|
27542
|
+
* either is false. The UI renders the control DISABLED with the reason
|
|
27543
|
+
* rather than hiding it, so a limitation is legible instead of looking
|
|
27544
|
+
* like a missing feature.
|
|
27545
|
+
*/
|
|
27546
|
+
var OnboardFieldSupportSchema = object({
|
|
27547
|
+
readable: boolean(),
|
|
27548
|
+
writable: boolean(),
|
|
27549
|
+
/** Required whenever `readable` or `writable` is false. */
|
|
27550
|
+
reason: string().optional()
|
|
27551
|
+
});
|
|
27552
|
+
/** What this camera's schedule model can express. */
|
|
27553
|
+
var OnboardScheduleSupportSchema = object({
|
|
27554
|
+
support: OnboardFieldSupportSchema,
|
|
27555
|
+
/**
|
|
27556
|
+
* The smallest time step the camera can express, in minutes.
|
|
27557
|
+
*
|
|
27558
|
+
* Hikvision takes arbitrary minutes (`00:05:00`–`23:57:00` observed on
|
|
27559
|
+
* 1436's track 103). Reolink's schedule is a 7×24 HOUR mask, so 60. A
|
|
27560
|
+
* window whose edges are not a multiple of this is REFUSED rather than
|
|
27561
|
+
* quietly rounded — rounding is how an operator's 06:30 becomes 06:00
|
|
27562
|
+
* and nothing says so.
|
|
27563
|
+
*/
|
|
27564
|
+
granularityMinutes: number(),
|
|
27565
|
+
/** Triggers this camera can record on. A window naming another is refused. */
|
|
27566
|
+
triggers: array(RecordTriggerSchema),
|
|
27567
|
+
/**
|
|
27568
|
+
* False when the camera stores ONE trigger per time range, so two
|
|
27569
|
+
* windows overlapping on the same day cannot carry different triggers.
|
|
27570
|
+
* True on Reolink, whose mask is per-trigger and independent.
|
|
27571
|
+
*/
|
|
27572
|
+
supportsOverlappingTriggers: boolean()
|
|
27573
|
+
});
|
|
27574
|
+
var RecordingOnboardOptionsSchema = object({
|
|
27575
|
+
enabled: OnboardFieldSupportSchema,
|
|
27576
|
+
overwriteWhenFull: OnboardFieldSupportSchema,
|
|
27577
|
+
preRecordSec: OnboardFieldSupportSchema,
|
|
27578
|
+
preRecordSecRange: RangeSchema.optional(),
|
|
27579
|
+
/** Preferred over the range when the camera takes a SET, not a span. */
|
|
27580
|
+
preRecordSecAllowed: AllowedValuesSchema.optional(),
|
|
27581
|
+
postRecordSec: OnboardFieldSupportSchema,
|
|
27582
|
+
postRecordSecRange: RangeSchema.optional(),
|
|
27583
|
+
/** Preferred over the range when the camera takes a SET, not a span. */
|
|
27584
|
+
postRecordSecAllowed: AllowedValuesSchema.optional(),
|
|
27585
|
+
segmentMinutes: OnboardFieldSupportSchema,
|
|
27586
|
+
segmentMinutesRange: RangeSchema.optional(),
|
|
27587
|
+
/** Preferred over the range when the camera takes a SET, not a span. */
|
|
27588
|
+
segmentMinutesAllowed: AllowedValuesSchema.optional(),
|
|
27589
|
+
schedule: OnboardScheduleSupportSchema
|
|
27590
|
+
});
|
|
27591
|
+
/**
|
|
27592
|
+
* A partial change. Every field optional.
|
|
27593
|
+
*
|
|
27594
|
+
* Unlike the other `deviceConfig` caps, a provider here does **NOT**
|
|
27595
|
+
* silently ignore a field it cannot support — it refuses, by name,
|
|
27596
|
+
* through {@link describeOnboardRefusal}. Silence on a recording setting
|
|
27597
|
+
* is the failure D62 exists to prevent: the operator believes the camera
|
|
27598
|
+
* is recording the way the form says, and it is not.
|
|
27599
|
+
*/
|
|
27600
|
+
var RecordingOnboardPatchSchema = object({
|
|
27601
|
+
enabled: boolean().optional(),
|
|
27602
|
+
overwriteWhenFull: boolean().optional(),
|
|
27603
|
+
preRecordSec: number().optional(),
|
|
27604
|
+
postRecordSec: number().optional(),
|
|
27605
|
+
segmentMinutes: number().optional(),
|
|
27606
|
+
/** The complete new window set for the primary track — not a delta. */
|
|
27607
|
+
windows: array(RecordWindowSchema).optional()
|
|
27608
|
+
});
|
|
27609
|
+
var recordingOnboardCapability = {
|
|
27610
|
+
name: "recording-onboard",
|
|
27611
|
+
scope: "device",
|
|
27612
|
+
deviceNative: true,
|
|
27613
|
+
mode: "singleton",
|
|
27614
|
+
deviceTypes: [DeviceType.Camera],
|
|
27615
|
+
deviceConfig: { ui: {
|
|
27616
|
+
kind: "derived-form",
|
|
27617
|
+
builderId: "recording-onboard",
|
|
27618
|
+
tab: "recording"
|
|
27619
|
+
} },
|
|
27620
|
+
methods: {
|
|
27621
|
+
getOptions: method(object({ deviceId: number() }), RecordingOnboardOptionsSchema),
|
|
27622
|
+
setSettings: method(object({
|
|
27623
|
+
deviceId: number(),
|
|
27624
|
+
settings: RecordingOnboardPatchSchema
|
|
27625
|
+
}), _void(), {
|
|
27626
|
+
kind: "mutation",
|
|
27627
|
+
auth: "admin"
|
|
27628
|
+
})
|
|
27629
|
+
},
|
|
27630
|
+
status: {
|
|
27631
|
+
schema: RecordingOnboardStatusSchema,
|
|
27632
|
+
kind: "poll"
|
|
27633
|
+
},
|
|
27634
|
+
runtimeState: RecordingOnboardStatusSchema,
|
|
27635
|
+
/**
|
|
27636
|
+
* Runtime-state durability: **restored** — operator-set camera-side
|
|
27637
|
+
* recording config; mutation-driven, and the storage half is the last
|
|
27638
|
+
* thing the camera said about its own card.
|
|
27639
|
+
*
|
|
27640
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
27641
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
27642
|
+
*/
|
|
27643
|
+
durability: "restored",
|
|
27644
|
+
/** Clock fields: written, but excluded from the compare that decides
|
|
27645
|
+
* whether persisting is worth a SQLite commit. */
|
|
27646
|
+
volatileStateFields: ["lastFetchedAt"]
|
|
27647
|
+
};
|
|
27648
|
+
/**
|
|
26414
27649
|
* Generic device-level status snapshot. Auto-registered by `BaseDevice`
|
|
26415
27650
|
* for every device, regardless of provider — the kernel needs a uniform
|
|
26416
27651
|
* cap-keyed slice for the basic device flags every consumer expects to
|
|
@@ -28920,11 +30155,15 @@ var motionTriggerCapability = {
|
|
|
28920
30155
|
*
|
|
28921
30156
|
* Adding a fifth name to that list would have been the wrong fix twice over:
|
|
28922
30157
|
* that page is per-camera DETECTION tuning, and a grid's geometry belongs
|
|
28923
|
-
* beside PTZ and motion zones on the camera itself
|
|
28924
|
-
*
|
|
28925
|
-
*
|
|
28926
|
-
*
|
|
28927
|
-
*
|
|
30158
|
+
* beside PTZ and motion zones on the camera itself. The device page is
|
|
30159
|
+
* BINDING-driven (D12), so the way in is a capability bound to the device —
|
|
30160
|
+
* and this cap carries its section the way `recording` does, by RETURNING it
|
|
30161
|
+
* from `getDeviceSettingsContribution`.
|
|
30162
|
+
*
|
|
30163
|
+
* Seven other widgets are still declared the other way, through a
|
|
30164
|
+
* `deviceConfig.ui` block the framework derives a section from. That route
|
|
30165
|
+
* gives the addon no say in where its own panel lands and no way to decline
|
|
30166
|
+
* for a device the panel does not suit, which is why this one does not use it.
|
|
28928
30167
|
*
|
|
28929
30168
|
* ## Why one addon may implement it
|
|
28930
30169
|
*
|
|
@@ -28980,16 +30219,37 @@ var GridProfileOfferSchema = object({
|
|
|
28980
30219
|
]),
|
|
28981
30220
|
offered: boolean(),
|
|
28982
30221
|
/** Sources that cannot serve it. Empty when it is offered, or when there are no cells. */
|
|
28983
|
-
missingSources: array(number().int().positive())
|
|
30222
|
+
missingSources: array(number().int().positive()),
|
|
30223
|
+
/**
|
|
30224
|
+
* The canvas this profile composes onto, `WxH`, or empty when it is not
|
|
30225
|
+
* offered. DERIVED from the cells and the sources' own size at this profile —
|
|
30226
|
+
* it is reported because nothing else in the system would ever say what the
|
|
30227
|
+
* grid came out as, and because it is the number an operator would otherwise
|
|
30228
|
+
* expect to type.
|
|
30229
|
+
*/
|
|
30230
|
+
canvas: string(),
|
|
30231
|
+
/**
|
|
30232
|
+
* Whether this profile is PUBLISHED, of the ones the grid could serve.
|
|
30233
|
+
*
|
|
30234
|
+
* A grid's `high` is composed of its sources' `high`, so on a 4K fleet it is
|
|
30235
|
+
* a 4K canvas built from 4K decodes — something to opt into, not something a
|
|
30236
|
+
* viewer's adaptive should be handed by climbing to the top rung it can see.
|
|
30237
|
+
* Default is `mid` + `low`.
|
|
30238
|
+
*/
|
|
30239
|
+
published: boolean()
|
|
28984
30240
|
});
|
|
28985
30241
|
var GridLayoutViewSchema = object({
|
|
28986
30242
|
/** The persisted grid row this camera was declared from. */
|
|
28987
30243
|
instanceId: string(),
|
|
28988
30244
|
deviceId: number().int().nonnegative(),
|
|
28989
30245
|
name: string(),
|
|
28990
|
-
/**
|
|
28991
|
-
|
|
28992
|
-
|
|
30246
|
+
/**
|
|
30247
|
+
* NO canvas size. A grid's resolution is not authored: each profile derives
|
|
30248
|
+
* its own from the cells and its sources' dimensions. The two numbers that
|
|
30249
|
+
* used to be here were a text field that silently decided both how much the
|
|
30250
|
+
* composite cost and how sharp it was — see `profiles[].canvas` for what it
|
|
30251
|
+
* came out as.
|
|
30252
|
+
*/
|
|
28993
30253
|
fps: number().int(),
|
|
28994
30254
|
cells: array(GridLayoutCellSchema),
|
|
28995
30255
|
/** What the catalog will publish, and what it refuses to. Read-only. */
|
|
@@ -28998,9 +30258,13 @@ var GridLayoutViewSchema = object({
|
|
|
28998
30258
|
var GridLayoutPatchSchema = object({
|
|
28999
30259
|
deviceId: number().int().nonnegative(),
|
|
29000
30260
|
name: string().min(1).max(160).optional(),
|
|
29001
|
-
width: number().int().min(160).max(7680).optional(),
|
|
29002
|
-
height: number().int().min(120).max(4320).optional(),
|
|
29003
30261
|
fps: number().int().min(1).max(60).optional(),
|
|
30262
|
+
/** Which profiles to publish. See `GridProfileOffer.published`. */
|
|
30263
|
+
publishedProfiles: array(_enum([
|
|
30264
|
+
"high",
|
|
30265
|
+
"mid",
|
|
30266
|
+
"low"
|
|
30267
|
+
])).max(3).optional(),
|
|
29004
30268
|
/**
|
|
29005
30269
|
* The whole cell list at once. A per-cell patch would need an ordering the
|
|
29006
30270
|
* editor does not have, and a half-applied layout is a picture nobody asked
|
|
@@ -31283,6 +32547,25 @@ var ptzAutotrackCapability = {
|
|
|
31283
32547
|
scope: "device",
|
|
31284
32548
|
deviceNative: true,
|
|
31285
32549
|
mode: "singleton",
|
|
32550
|
+
/**
|
|
32551
|
+
* Two kinds of provider behind one name, the same shape `snapshot` and
|
|
32552
|
+
* `doorbell` use:
|
|
32553
|
+
*
|
|
32554
|
+
* - **Native**: the camera's own firmware autotrack, registered per device by
|
|
32555
|
+
* the driver when the probe says it exists (Reolink: yes; Hikvision:
|
|
32556
|
+
* model-dependent).
|
|
32557
|
+
* - **Wrapper**: the emulated implementation (`@camstack/system`'s `autotrack` builtin),
|
|
32558
|
+
* which follows a subject with `ptz.move` pulses driven by the detection
|
|
32559
|
+
* pipeline. `defaultActive: false` — the operator binds it per camera in
|
|
32560
|
+
* the bindings UI, exactly as `virtual-doorbell` is bound.
|
|
32561
|
+
*
|
|
32562
|
+
* The binding chooses WHICH implementation answers; `setEnabled` stays the
|
|
32563
|
+
* one switch for the function either way. A camera never has both driving its
|
|
32564
|
+
* head, which is the whole reason this is not a second cap with a second
|
|
32565
|
+
* toggle (D62, D520).
|
|
32566
|
+
*/
|
|
32567
|
+
kind: "wrapper",
|
|
32568
|
+
defaultActive: false,
|
|
31286
32569
|
deviceTypes: [DeviceType.Camera],
|
|
31287
32570
|
deviceConfig: { ui: {
|
|
31288
32571
|
kind: "widget",
|
|
@@ -31765,7 +33048,7 @@ method(object({
|
|
|
31765
33048
|
auth: "admin"
|
|
31766
33049
|
}), method(object({ deviceId: number() }), RecordingConfigSchema, {
|
|
31767
33050
|
kind: "query",
|
|
31768
|
-
auth: "
|
|
33051
|
+
auth: "protected"
|
|
31769
33052
|
}), method(object({
|
|
31770
33053
|
deviceId: number(),
|
|
31771
33054
|
profile: string(),
|
|
@@ -31995,13 +33278,101 @@ var ExportStateSchema = _enum([
|
|
|
31995
33278
|
"expired",
|
|
31996
33279
|
"deleted"
|
|
31997
33280
|
]);
|
|
31998
|
-
/**
|
|
33281
|
+
/**
|
|
33282
|
+
* WHAT an export is — the authority, as opposed to the four top-level fields
|
|
33283
|
+
* the Library sorts and labels on (D558 § 2.2).
|
|
33284
|
+
*
|
|
33285
|
+
* Read it through {@link exportSubjectOf}, never off the record directly: the
|
|
33286
|
+
* field is optional for the history rows written before it existed, and that
|
|
33287
|
+
* absence has exactly one interpreter.
|
|
33288
|
+
*/
|
|
33289
|
+
var ExportSubjectSchema = discriminatedUnion("kind", [object({
|
|
33290
|
+
kind: literal("footage"),
|
|
33291
|
+
deviceId: number(),
|
|
33292
|
+
profiles: array(string()).min(1),
|
|
33293
|
+
fromMs: number(),
|
|
33294
|
+
toMs: number()
|
|
33295
|
+
}), object({
|
|
33296
|
+
kind: literal("clip"),
|
|
33297
|
+
deviceId: number(),
|
|
33298
|
+
provider: string().min(1),
|
|
33299
|
+
source: string().min(1),
|
|
33300
|
+
sourceLabel: string().min(1),
|
|
33301
|
+
clipId: string().min(1),
|
|
33302
|
+
/**
|
|
33303
|
+
* WHERE IN THE CATALOG to confirm this clip — the day window the surface was
|
|
33304
|
+
* already listing when the operator picked the segment.
|
|
33305
|
+
*
|
|
33306
|
+
* It is **not** a time range control and it never becomes one: the record's
|
|
33307
|
+
* `fromMs`/`toMs` come from the catalog ROW and from nothing a caller
|
|
33308
|
+
* supplied (D558 § 5.4.3), and a window that does not contain the clip is a
|
|
33309
|
+
* `catalog-miss`, not a silently wider search. It exists because
|
|
33310
|
+
* `videoclips.listClips` takes `since`/`until` and has no by-handle twin:
|
|
33311
|
+
* the catalog check D558 asks for is literally that call, and a call needs a
|
|
33312
|
+
* window. The surface has one — `ClipsBrowser`'s `dayWindow`, one local
|
|
33313
|
+
* wall-clock day, which is also the only width measured to be cheap (one
|
|
33314
|
+
* day on 592 lists 171 clips in 712 ms, a busy day 546 in 2.0 s; twenty days
|
|
33315
|
+
* of one child's events took 3.6–5.4 s).
|
|
33316
|
+
*/
|
|
33317
|
+
catalogWindow: object({
|
|
33318
|
+
sinceMs: number(),
|
|
33319
|
+
untilMs: number()
|
|
33320
|
+
}),
|
|
33321
|
+
profile: _enum([
|
|
33322
|
+
"high",
|
|
33323
|
+
"mid",
|
|
33324
|
+
"low"
|
|
33325
|
+
]),
|
|
33326
|
+
/**
|
|
33327
|
+
* The operator's authorisation to wake a sleeping camera for this export.
|
|
33328
|
+
* ONE definition, in the cap that owns the clip read ({@link ClipWakeSchema}),
|
|
33329
|
+
* because the gate that honours it is the clip provider's sleep gate — a
|
|
33330
|
+
* second enum here would be a second contract. Absent by default; never
|
|
33331
|
+
* settable by a scheduler or a retry.
|
|
33332
|
+
*
|
|
33333
|
+
* **One authorised yes is ONE wake.** A failed clip export is retried only by
|
|
33334
|
+
* an operator act that asks again; a queued job that outlived its wake fails
|
|
33335
|
+
* with a reason rather than waking on its turn; and this subject carries
|
|
33336
|
+
* exactly one profile precisely so one tap is never two fetches (D558 § 5.2).
|
|
33337
|
+
*/
|
|
33338
|
+
wake: ClipWakeSchema.optional()
|
|
33339
|
+
})]);
|
|
33340
|
+
/**
|
|
33341
|
+
* One export job / history row.
|
|
33342
|
+
*
|
|
33343
|
+
* **`subject` is the AUTHORITY on what was exported. `deviceId`, `profile`,
|
|
33344
|
+
* `fromMs` and `toMs` are its PROJECTION** — kept top-level because the whole
|
|
33345
|
+
* Library sorts and labels on them (`library-items.ts` orders an export by
|
|
33346
|
+
* `fromMs`; `export-format.ts` draws `rangeLabel` from the pair), and a row
|
|
33347
|
+
* that did not fill them would sort under the epoch and render a blank range.
|
|
33348
|
+
* Write to the subject and read from the projection and the two will disagree;
|
|
33349
|
+
* the projection is derived at creation and never edited afterwards.
|
|
33350
|
+
*
|
|
33351
|
+
* And they mean DIFFERENT FACTS for the two kinds, which is the part a reader
|
|
33352
|
+
* who knows only the recording export will get wrong:
|
|
33353
|
+
*
|
|
33354
|
+
* | field | `kind:'footage'` | `kind:'clip'` |
|
|
33355
|
+
* | --- | --- | --- |
|
|
33356
|
+
* | `fromMs`/`toMs` | the stretch the operator ASKED for | the camera's own boundaries, always `clip.timeRange`, never anything a caller supplied |
|
|
33357
|
+
* | `profile` | the stream rendered | the twin actually SERVED (`subject.profile` is the one asked for) |
|
|
33358
|
+
*
|
|
33359
|
+
* Same type, different fact — the shape this repo keeps getting wrong (D385's
|
|
33360
|
+
* two authorities, D224's second copy).
|
|
33361
|
+
*/
|
|
31999
33362
|
var ExportRecordSchema = object({
|
|
32000
33363
|
id: string(),
|
|
32001
33364
|
deviceId: number(),
|
|
32002
33365
|
profile: string(),
|
|
32003
33366
|
fromMs: number(),
|
|
32004
33367
|
toMs: number(),
|
|
33368
|
+
/**
|
|
33369
|
+
* What this export IS. Optional ONLY for the rows written before D558: the
|
|
33370
|
+
* store parses every row through this schema on every read, so a required
|
|
33371
|
+
* field would make the export AUDIT — which is the whole reason rows survive
|
|
33372
|
+
* file deletion — unreadable in one release. Absence means `footage`, and
|
|
33373
|
+
* {@link exportSubjectOf} is the one place that says so.
|
|
33374
|
+
*/
|
|
33375
|
+
subject: ExportSubjectSchema.optional(),
|
|
32005
33376
|
options: ExportOptionsSchema,
|
|
32006
33377
|
state: ExportStateSchema,
|
|
32007
33378
|
/** 0–100 while rendering; null otherwise. */
|
|
@@ -32018,6 +33389,18 @@ var ExportRecordSchema = object({
|
|
|
32018
33389
|
/** Failure reason when state is 'failed'; null otherwise. */
|
|
32019
33390
|
error: string().nullable()
|
|
32020
33391
|
});
|
|
33392
|
+
_enum([
|
|
33393
|
+
"catalog-miss",
|
|
33394
|
+
"catalog-unreachable",
|
|
33395
|
+
"no-file-for-window",
|
|
33396
|
+
"clip-in-progress",
|
|
33397
|
+
"unsupported-option",
|
|
33398
|
+
"sleeping",
|
|
33399
|
+
"camera-refused",
|
|
33400
|
+
"fetch-failed",
|
|
33401
|
+
"too-large-to-transfer",
|
|
33402
|
+
"wake-expired"
|
|
33403
|
+
]);
|
|
32021
33404
|
/** Candidate download URLs (LAN first, then operator extra hosts). */
|
|
32022
33405
|
var ExportDownloadSchema = object({
|
|
32023
33406
|
url: string(),
|
|
@@ -32036,16 +33419,50 @@ var ExportBytesSchema = object({
|
|
|
32036
33419
|
name: string(),
|
|
32037
33420
|
bytes: number().int().nonnegative()
|
|
32038
33421
|
});
|
|
33422
|
+
/** Canonical `profiles[]`, falling back to the legacy singular `profile`. */
|
|
33423
|
+
function resolveExportProfiles(input) {
|
|
33424
|
+
if (input.profiles !== void 0 && input.profiles.length > 0) return [...input.profiles];
|
|
33425
|
+
if (typeof input.profile === "string" && input.profile.length > 0) return [input.profile];
|
|
33426
|
+
return [];
|
|
33427
|
+
}
|
|
32039
33428
|
method(object({
|
|
32040
33429
|
deviceId: number(),
|
|
32041
33430
|
/** @deprecated Prefer `profiles`. Kept so timelapse/notifiers keep working. */
|
|
32042
33431
|
profile: string().optional(),
|
|
32043
33432
|
profiles: array(string()).min(1).optional(),
|
|
32044
|
-
|
|
32045
|
-
|
|
33433
|
+
/** Footage only — a clip's boundaries are the camera's. */
|
|
33434
|
+
fromMs: number().optional(),
|
|
33435
|
+
toMs: number().optional(),
|
|
33436
|
+
/** What to export. Absent means the legacy flat footage request. */
|
|
33437
|
+
subject: ExportSubjectSchema.optional(),
|
|
32046
33438
|
options: ExportOptionsSchema
|
|
32047
33439
|
}).superRefine((v, ctx) => {
|
|
32048
|
-
if (
|
|
33440
|
+
if (v.subject?.kind === "clip") {
|
|
33441
|
+
if (v.subject.deviceId !== v.deviceId) ctx.addIssue({
|
|
33442
|
+
code: ZodIssueCode.custom,
|
|
33443
|
+
message: `subject.deviceId (${v.subject.deviceId}) must equal deviceId (${v.deviceId}) — the top-level field is what per-device scope enforcement reads`,
|
|
33444
|
+
path: ["subject", "deviceId"]
|
|
33445
|
+
});
|
|
33446
|
+
if (v.fromMs !== void 0 || v.toMs !== void 0) ctx.addIssue({
|
|
33447
|
+
code: ZodIssueCode.custom,
|
|
33448
|
+
message: "a clip export asks for no time range: the camera chose the boundaries and they are read from the catalog row",
|
|
33449
|
+
path: ["fromMs"]
|
|
33450
|
+
});
|
|
33451
|
+
return;
|
|
33452
|
+
}
|
|
33453
|
+
if (v.subject?.kind === "footage" && v.subject.deviceId !== v.deviceId) ctx.addIssue({
|
|
33454
|
+
code: ZodIssueCode.custom,
|
|
33455
|
+
message: `subject.deviceId (${v.subject.deviceId}) must equal deviceId (${v.deviceId})`,
|
|
33456
|
+
path: ["subject", "deviceId"]
|
|
33457
|
+
});
|
|
33458
|
+
const fromMs = v.subject?.kind === "footage" ? v.subject.fromMs : v.fromMs;
|
|
33459
|
+
const toMs = v.subject?.kind === "footage" ? v.subject.toMs : v.toMs;
|
|
33460
|
+
if (typeof fromMs !== "number" || typeof toMs !== "number") ctx.addIssue({
|
|
33461
|
+
code: ZodIssueCode.custom,
|
|
33462
|
+
message: "a footage export needs fromMs and toMs",
|
|
33463
|
+
path: ["fromMs"]
|
|
33464
|
+
});
|
|
33465
|
+
if ((v.subject?.kind === "footage" ? [...v.subject.profiles] : resolveExportProfiles(v)).length < 1) ctx.addIssue({
|
|
32049
33466
|
code: ZodIssueCode.custom,
|
|
32050
33467
|
message: "pass profiles[] (min 1) or legacy profile",
|
|
32051
33468
|
path: ["profiles"]
|
|
@@ -34857,6 +36274,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
|
|
|
34857
36274
|
pressureSensor: pressureSensorCapability,
|
|
34858
36275
|
privacyMask: privacyMaskCapability,
|
|
34859
36276
|
ptzAutotrack: ptzAutotrackCapability,
|
|
36277
|
+
recordingOnboard: recordingOnboardCapability,
|
|
34860
36278
|
recordingSignal: recordingSignalCapability,
|
|
34861
36279
|
sceneMonitor: sceneMonitorCapability,
|
|
34862
36280
|
scriptRunner: scriptRunnerCapability,
|
|
@@ -36018,7 +37436,1336 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
36018
37436
|
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;
|
|
36019
37437
|
new Set(Object.values(DeviceType));
|
|
36020
37438
|
DeviceFeature.BatteryOperated;
|
|
37439
|
+
/**
|
|
37440
|
+
* The vocabulary, in the order a picker offers it.
|
|
37441
|
+
*
|
|
37442
|
+
* Adding a key here is not enough to make it pickable: both glyph tables must
|
|
37443
|
+
* gain it too, and their tests fail until they do. That is the intended
|
|
37444
|
+
* friction — it is the only thing that keeps the two surfaces honest.
|
|
37445
|
+
*/
|
|
37446
|
+
var DISPLAY_ICON_VOCABULARY = [
|
|
37447
|
+
{
|
|
37448
|
+
key: "camera",
|
|
37449
|
+
group: "device",
|
|
37450
|
+
synonyms: [
|
|
37451
|
+
"telecamera",
|
|
37452
|
+
"videocamera",
|
|
37453
|
+
"camera",
|
|
37454
|
+
"cam"
|
|
37455
|
+
]
|
|
37456
|
+
},
|
|
37457
|
+
{
|
|
37458
|
+
key: "network",
|
|
37459
|
+
group: "device",
|
|
37460
|
+
synonyms: [
|
|
37461
|
+
"rete",
|
|
37462
|
+
"network",
|
|
37463
|
+
"lan"
|
|
37464
|
+
]
|
|
37465
|
+
},
|
|
37466
|
+
{
|
|
37467
|
+
key: "toggle-left",
|
|
37468
|
+
group: "device",
|
|
37469
|
+
synonyms: [
|
|
37470
|
+
"interruttore",
|
|
37471
|
+
"switch",
|
|
37472
|
+
"toggle"
|
|
37473
|
+
]
|
|
37474
|
+
},
|
|
37475
|
+
{
|
|
37476
|
+
key: "toggle-right",
|
|
37477
|
+
group: "device",
|
|
37478
|
+
synonyms: [
|
|
37479
|
+
"interruttore",
|
|
37480
|
+
"switch",
|
|
37481
|
+
"toggle",
|
|
37482
|
+
"acceso"
|
|
37483
|
+
]
|
|
37484
|
+
},
|
|
37485
|
+
{
|
|
37486
|
+
key: "circle-dot",
|
|
37487
|
+
group: "device",
|
|
37488
|
+
synonyms: [
|
|
37489
|
+
"pulsante",
|
|
37490
|
+
"bottone",
|
|
37491
|
+
"button",
|
|
37492
|
+
"dot"
|
|
37493
|
+
]
|
|
37494
|
+
},
|
|
37495
|
+
{
|
|
37496
|
+
key: "refresh-cw",
|
|
37497
|
+
group: "device",
|
|
37498
|
+
synonyms: [
|
|
37499
|
+
"aggiorna",
|
|
37500
|
+
"ricarica",
|
|
37501
|
+
"refresh",
|
|
37502
|
+
"reload",
|
|
37503
|
+
"ciclo"
|
|
37504
|
+
]
|
|
37505
|
+
},
|
|
37506
|
+
{
|
|
37507
|
+
key: "box",
|
|
37508
|
+
group: "device",
|
|
37509
|
+
synonyms: [
|
|
37510
|
+
"scatola",
|
|
37511
|
+
"box",
|
|
37512
|
+
"contenitore"
|
|
37513
|
+
]
|
|
37514
|
+
},
|
|
37515
|
+
{
|
|
37516
|
+
key: "scroll-text",
|
|
37517
|
+
group: "device",
|
|
37518
|
+
synonyms: [
|
|
37519
|
+
"registro",
|
|
37520
|
+
"log",
|
|
37521
|
+
"testo"
|
|
37522
|
+
]
|
|
37523
|
+
},
|
|
37524
|
+
{
|
|
37525
|
+
key: "workflow",
|
|
37526
|
+
group: "device",
|
|
37527
|
+
synonyms: [
|
|
37528
|
+
"automazione",
|
|
37529
|
+
"flusso",
|
|
37530
|
+
"workflow",
|
|
37531
|
+
"scenario"
|
|
37532
|
+
]
|
|
37533
|
+
},
|
|
37534
|
+
{
|
|
37535
|
+
key: "lock",
|
|
37536
|
+
group: "device",
|
|
37537
|
+
synonyms: [
|
|
37538
|
+
"serratura",
|
|
37539
|
+
"chiave",
|
|
37540
|
+
"lucchetto",
|
|
37541
|
+
"chiuso",
|
|
37542
|
+
"locked"
|
|
37543
|
+
]
|
|
37544
|
+
},
|
|
37545
|
+
{
|
|
37546
|
+
key: "lock-open",
|
|
37547
|
+
group: "device",
|
|
37548
|
+
synonyms: [
|
|
37549
|
+
"serratura",
|
|
37550
|
+
"aperta",
|
|
37551
|
+
"sbloccato",
|
|
37552
|
+
"unlocked"
|
|
37553
|
+
]
|
|
37554
|
+
},
|
|
37555
|
+
{
|
|
37556
|
+
key: "blinds",
|
|
37557
|
+
group: "device",
|
|
37558
|
+
synonyms: [
|
|
37559
|
+
"tapparella",
|
|
37560
|
+
"persiana",
|
|
37561
|
+
"serranda",
|
|
37562
|
+
"avvolgibile",
|
|
37563
|
+
"shutter",
|
|
37564
|
+
"blind",
|
|
37565
|
+
"veneziana"
|
|
37566
|
+
]
|
|
37567
|
+
},
|
|
37568
|
+
{
|
|
37569
|
+
key: "air-vent",
|
|
37570
|
+
group: "device",
|
|
37571
|
+
synonyms: [
|
|
37572
|
+
"bocchetta",
|
|
37573
|
+
"aria",
|
|
37574
|
+
"ventilazione",
|
|
37575
|
+
"condizionatore",
|
|
37576
|
+
"vent"
|
|
37577
|
+
]
|
|
37578
|
+
},
|
|
37579
|
+
{
|
|
37580
|
+
key: "monitor-play",
|
|
37581
|
+
group: "device",
|
|
37582
|
+
synonyms: [
|
|
37583
|
+
"schermo",
|
|
37584
|
+
"riproduzione",
|
|
37585
|
+
"player",
|
|
37586
|
+
"display"
|
|
37587
|
+
]
|
|
37588
|
+
},
|
|
37589
|
+
{
|
|
37590
|
+
key: "sliders-horizontal",
|
|
37591
|
+
group: "device",
|
|
37592
|
+
synonyms: [
|
|
37593
|
+
"regolazione",
|
|
37594
|
+
"cursori",
|
|
37595
|
+
"impostazioni",
|
|
37596
|
+
"slider"
|
|
37597
|
+
]
|
|
37598
|
+
},
|
|
37599
|
+
{
|
|
37600
|
+
key: "map-pin",
|
|
37601
|
+
group: "device",
|
|
37602
|
+
synonyms: [
|
|
37603
|
+
"posizione",
|
|
37604
|
+
"luogo",
|
|
37605
|
+
"mappa",
|
|
37606
|
+
"pin"
|
|
37607
|
+
]
|
|
37608
|
+
},
|
|
37609
|
+
{
|
|
37610
|
+
key: "bot",
|
|
37611
|
+
group: "device",
|
|
37612
|
+
synonyms: [
|
|
37613
|
+
"robot",
|
|
37614
|
+
"aspirapolvere",
|
|
37615
|
+
"bot"
|
|
37616
|
+
]
|
|
37617
|
+
},
|
|
37618
|
+
{
|
|
37619
|
+
key: "tractor",
|
|
37620
|
+
group: "device",
|
|
37621
|
+
synonyms: [
|
|
37622
|
+
"trattore",
|
|
37623
|
+
"tosaerba",
|
|
37624
|
+
"giardino"
|
|
37625
|
+
]
|
|
37626
|
+
},
|
|
37627
|
+
{
|
|
37628
|
+
key: "image",
|
|
37629
|
+
group: "device",
|
|
37630
|
+
synonyms: [
|
|
37631
|
+
"immagine",
|
|
37632
|
+
"foto",
|
|
37633
|
+
"snapshot"
|
|
37634
|
+
]
|
|
37635
|
+
},
|
|
37636
|
+
{
|
|
37637
|
+
key: "bone",
|
|
37638
|
+
group: "device",
|
|
37639
|
+
synonyms: ["osso", "cane"]
|
|
37640
|
+
},
|
|
37641
|
+
{
|
|
37642
|
+
key: "door-closed",
|
|
37643
|
+
group: "device",
|
|
37644
|
+
synonyms: [
|
|
37645
|
+
"porta",
|
|
37646
|
+
"chiusa",
|
|
37647
|
+
"contatto"
|
|
37648
|
+
]
|
|
37649
|
+
},
|
|
37650
|
+
{
|
|
37651
|
+
key: "door-open",
|
|
37652
|
+
group: "device",
|
|
37653
|
+
synonyms: [
|
|
37654
|
+
"porta",
|
|
37655
|
+
"aperta",
|
|
37656
|
+
"contatto",
|
|
37657
|
+
"ingresso",
|
|
37658
|
+
"uscita"
|
|
37659
|
+
]
|
|
37660
|
+
},
|
|
37661
|
+
{
|
|
37662
|
+
key: "crosshair",
|
|
37663
|
+
group: "device",
|
|
37664
|
+
synonyms: [
|
|
37665
|
+
"mirino",
|
|
37666
|
+
"puntamento",
|
|
37667
|
+
"centro"
|
|
37668
|
+
]
|
|
37669
|
+
},
|
|
37670
|
+
{
|
|
37671
|
+
key: "eye",
|
|
37672
|
+
group: "device",
|
|
37673
|
+
synonyms: [
|
|
37674
|
+
"occhio",
|
|
37675
|
+
"visibile",
|
|
37676
|
+
"visto"
|
|
37677
|
+
]
|
|
37678
|
+
},
|
|
37679
|
+
{
|
|
37680
|
+
key: "eye-off",
|
|
37681
|
+
group: "device",
|
|
37682
|
+
synonyms: [
|
|
37683
|
+
"privacy",
|
|
37684
|
+
"nascosto",
|
|
37685
|
+
"oscurato",
|
|
37686
|
+
"mascherato",
|
|
37687
|
+
"invisibile"
|
|
37688
|
+
]
|
|
37689
|
+
},
|
|
37690
|
+
{
|
|
37691
|
+
key: "smartphone",
|
|
37692
|
+
group: "device",
|
|
37693
|
+
synonyms: [
|
|
37694
|
+
"telefono",
|
|
37695
|
+
"cellulare",
|
|
37696
|
+
"smartphone"
|
|
37697
|
+
]
|
|
37698
|
+
},
|
|
37699
|
+
{
|
|
37700
|
+
key: "phone",
|
|
37701
|
+
group: "device",
|
|
37702
|
+
synonyms: ["telefono", "chiamata"]
|
|
37703
|
+
},
|
|
37704
|
+
{
|
|
37705
|
+
key: "tablet",
|
|
37706
|
+
group: "device",
|
|
37707
|
+
synonyms: ["tablet", "ipad"]
|
|
37708
|
+
},
|
|
37709
|
+
{
|
|
37710
|
+
key: "laptop",
|
|
37711
|
+
group: "device",
|
|
37712
|
+
synonyms: [
|
|
37713
|
+
"portatile",
|
|
37714
|
+
"laptop",
|
|
37715
|
+
"notebook"
|
|
37716
|
+
]
|
|
37717
|
+
},
|
|
37718
|
+
{
|
|
37719
|
+
key: "monitor",
|
|
37720
|
+
group: "device",
|
|
37721
|
+
synonyms: ["monitor", "schermo"]
|
|
37722
|
+
},
|
|
37723
|
+
{
|
|
37724
|
+
key: "tv",
|
|
37725
|
+
group: "device",
|
|
37726
|
+
synonyms: ["televisore", "tv"]
|
|
37727
|
+
},
|
|
37728
|
+
{
|
|
37729
|
+
key: "speaker",
|
|
37730
|
+
group: "device",
|
|
37731
|
+
synonyms: [
|
|
37732
|
+
"altoparlante",
|
|
37733
|
+
"cassa",
|
|
37734
|
+
"speaker"
|
|
37735
|
+
]
|
|
37736
|
+
},
|
|
37737
|
+
{
|
|
37738
|
+
key: "printer",
|
|
37739
|
+
group: "device",
|
|
37740
|
+
synonyms: ["stampante"]
|
|
37741
|
+
},
|
|
37742
|
+
{
|
|
37743
|
+
key: "projector",
|
|
37744
|
+
group: "device",
|
|
37745
|
+
synonyms: ["proiettore"]
|
|
37746
|
+
},
|
|
37747
|
+
{
|
|
37748
|
+
key: "gamepad-2",
|
|
37749
|
+
group: "device",
|
|
37750
|
+
synonyms: [
|
|
37751
|
+
"console",
|
|
37752
|
+
"gioco",
|
|
37753
|
+
"joystick"
|
|
37754
|
+
]
|
|
37755
|
+
},
|
|
37756
|
+
{
|
|
37757
|
+
key: "radio",
|
|
37758
|
+
group: "device",
|
|
37759
|
+
synonyms: ["radio"]
|
|
37760
|
+
},
|
|
37761
|
+
{
|
|
37762
|
+
key: "router",
|
|
37763
|
+
group: "device",
|
|
37764
|
+
synonyms: ["router", "modem"]
|
|
37765
|
+
},
|
|
37766
|
+
{
|
|
37767
|
+
key: "bluetooth",
|
|
37768
|
+
group: "device",
|
|
37769
|
+
synonyms: ["bluetooth"]
|
|
37770
|
+
},
|
|
37771
|
+
{
|
|
37772
|
+
key: "cpu",
|
|
37773
|
+
group: "device",
|
|
37774
|
+
synonyms: [
|
|
37775
|
+
"processore",
|
|
37776
|
+
"cpu",
|
|
37777
|
+
"chip",
|
|
37778
|
+
"dispositivo"
|
|
37779
|
+
]
|
|
37780
|
+
},
|
|
37781
|
+
{
|
|
37782
|
+
key: "hard-drive",
|
|
37783
|
+
group: "device",
|
|
37784
|
+
synonyms: [
|
|
37785
|
+
"disco",
|
|
37786
|
+
"hdd",
|
|
37787
|
+
"archiviazione",
|
|
37788
|
+
"storage"
|
|
37789
|
+
]
|
|
37790
|
+
},
|
|
37791
|
+
{
|
|
37792
|
+
key: "server",
|
|
37793
|
+
group: "device",
|
|
37794
|
+
synonyms: ["server", "nas"]
|
|
37795
|
+
},
|
|
37796
|
+
{
|
|
37797
|
+
key: "mic",
|
|
37798
|
+
group: "device",
|
|
37799
|
+
synonyms: ["microfono", "audio"]
|
|
37800
|
+
},
|
|
37801
|
+
{
|
|
37802
|
+
key: "mic-off",
|
|
37803
|
+
group: "device",
|
|
37804
|
+
synonyms: [
|
|
37805
|
+
"microfono",
|
|
37806
|
+
"spento",
|
|
37807
|
+
"muto"
|
|
37808
|
+
]
|
|
37809
|
+
},
|
|
37810
|
+
{
|
|
37811
|
+
key: "video",
|
|
37812
|
+
group: "device",
|
|
37813
|
+
synonyms: ["video"]
|
|
37814
|
+
},
|
|
37815
|
+
{
|
|
37816
|
+
key: "video-off",
|
|
37817
|
+
group: "device",
|
|
37818
|
+
synonyms: ["video", "spento"]
|
|
37819
|
+
},
|
|
37820
|
+
{
|
|
37821
|
+
key: "music",
|
|
37822
|
+
group: "device",
|
|
37823
|
+
synonyms: ["musica"]
|
|
37824
|
+
},
|
|
37825
|
+
{
|
|
37826
|
+
key: "plug",
|
|
37827
|
+
group: "device",
|
|
37828
|
+
synonyms: [
|
|
37829
|
+
"presa",
|
|
37830
|
+
"spina",
|
|
37831
|
+
"plug"
|
|
37832
|
+
]
|
|
37833
|
+
},
|
|
37834
|
+
{
|
|
37835
|
+
key: "plug-zap",
|
|
37836
|
+
group: "device",
|
|
37837
|
+
synonyms: ["presa", "alimentata"]
|
|
37838
|
+
},
|
|
37839
|
+
{
|
|
37840
|
+
key: "power",
|
|
37841
|
+
group: "device",
|
|
37842
|
+
synonyms: [
|
|
37843
|
+
"accensione",
|
|
37844
|
+
"alimentazione",
|
|
37845
|
+
"power"
|
|
37846
|
+
]
|
|
37847
|
+
},
|
|
37848
|
+
{
|
|
37849
|
+
key: "power-off",
|
|
37850
|
+
group: "device",
|
|
37851
|
+
synonyms: ["spegnimento", "spento"]
|
|
37852
|
+
},
|
|
37853
|
+
{
|
|
37854
|
+
key: "cable",
|
|
37855
|
+
group: "device",
|
|
37856
|
+
synonyms: ["cavo"]
|
|
37857
|
+
},
|
|
37858
|
+
{
|
|
37859
|
+
key: "battery",
|
|
37860
|
+
group: "device",
|
|
37861
|
+
synonyms: ["batteria"]
|
|
37862
|
+
},
|
|
37863
|
+
{
|
|
37864
|
+
key: "battery-charging",
|
|
37865
|
+
group: "device",
|
|
37866
|
+
synonyms: [
|
|
37867
|
+
"batteria",
|
|
37868
|
+
"carica",
|
|
37869
|
+
"ricarica"
|
|
37870
|
+
]
|
|
37871
|
+
},
|
|
37872
|
+
{
|
|
37873
|
+
key: "battery-full",
|
|
37874
|
+
group: "device",
|
|
37875
|
+
synonyms: [
|
|
37876
|
+
"batteria",
|
|
37877
|
+
"carica",
|
|
37878
|
+
"piena"
|
|
37879
|
+
]
|
|
37880
|
+
},
|
|
37881
|
+
{
|
|
37882
|
+
key: "battery-low",
|
|
37883
|
+
group: "device",
|
|
37884
|
+
synonyms: ["batteria", "scarica"]
|
|
37885
|
+
},
|
|
37886
|
+
{
|
|
37887
|
+
key: "key",
|
|
37888
|
+
group: "device",
|
|
37889
|
+
synonyms: ["chiave"]
|
|
37890
|
+
},
|
|
37891
|
+
{
|
|
37892
|
+
key: "key-round",
|
|
37893
|
+
group: "device",
|
|
37894
|
+
synonyms: ["chiave", "tonda"]
|
|
37895
|
+
},
|
|
37896
|
+
{
|
|
37897
|
+
key: "mailbox",
|
|
37898
|
+
group: "device",
|
|
37899
|
+
synonyms: [
|
|
37900
|
+
"cassetta",
|
|
37901
|
+
"postale",
|
|
37902
|
+
"posta"
|
|
37903
|
+
]
|
|
37904
|
+
},
|
|
37905
|
+
{
|
|
37906
|
+
key: "package",
|
|
37907
|
+
group: "device",
|
|
37908
|
+
synonyms: ["pacco", "consegna"]
|
|
37909
|
+
},
|
|
37910
|
+
{
|
|
37911
|
+
key: "shield",
|
|
37912
|
+
group: "device",
|
|
37913
|
+
synonyms: [
|
|
37914
|
+
"scudo",
|
|
37915
|
+
"protezione",
|
|
37916
|
+
"sicurezza",
|
|
37917
|
+
"allarme"
|
|
37918
|
+
]
|
|
37919
|
+
},
|
|
37920
|
+
{
|
|
37921
|
+
key: "shield-check",
|
|
37922
|
+
group: "device",
|
|
37923
|
+
synonyms: [
|
|
37924
|
+
"protetto",
|
|
37925
|
+
"sicuro",
|
|
37926
|
+
"armato"
|
|
37927
|
+
]
|
|
37928
|
+
},
|
|
37929
|
+
{
|
|
37930
|
+
key: "shield-off",
|
|
37931
|
+
group: "device",
|
|
37932
|
+
synonyms: [
|
|
37933
|
+
"protezione",
|
|
37934
|
+
"disattivata",
|
|
37935
|
+
"disarmato"
|
|
37936
|
+
]
|
|
37937
|
+
},
|
|
37938
|
+
{
|
|
37939
|
+
key: "briefcase",
|
|
37940
|
+
group: "device",
|
|
37941
|
+
synonyms: [
|
|
37942
|
+
"ufficio",
|
|
37943
|
+
"lavoro",
|
|
37944
|
+
"valigetta",
|
|
37945
|
+
"studio"
|
|
37946
|
+
]
|
|
37947
|
+
},
|
|
37948
|
+
{
|
|
37949
|
+
key: "lightbulb",
|
|
37950
|
+
group: "lighting",
|
|
37951
|
+
synonyms: [
|
|
37952
|
+
"luce",
|
|
37953
|
+
"lampadina",
|
|
37954
|
+
"lampada",
|
|
37955
|
+
"light"
|
|
37956
|
+
]
|
|
37957
|
+
},
|
|
37958
|
+
{
|
|
37959
|
+
key: "lamp",
|
|
37960
|
+
group: "lighting",
|
|
37961
|
+
synonyms: ["lampada", "abatjour"]
|
|
37962
|
+
},
|
|
37963
|
+
{
|
|
37964
|
+
key: "lamp-ceiling",
|
|
37965
|
+
group: "lighting",
|
|
37966
|
+
synonyms: [
|
|
37967
|
+
"plafoniera",
|
|
37968
|
+
"lampadario",
|
|
37969
|
+
"luce",
|
|
37970
|
+
"soffitto"
|
|
37971
|
+
]
|
|
37972
|
+
},
|
|
37973
|
+
{
|
|
37974
|
+
key: "lamp-desk",
|
|
37975
|
+
group: "lighting",
|
|
37976
|
+
synonyms: [
|
|
37977
|
+
"lampada",
|
|
37978
|
+
"scrivania",
|
|
37979
|
+
"abatjour"
|
|
37980
|
+
]
|
|
37981
|
+
},
|
|
37982
|
+
{
|
|
37983
|
+
key: "lamp-floor",
|
|
37984
|
+
group: "lighting",
|
|
37985
|
+
synonyms: [
|
|
37986
|
+
"piantana",
|
|
37987
|
+
"lampada",
|
|
37988
|
+
"terra"
|
|
37989
|
+
]
|
|
37990
|
+
},
|
|
37991
|
+
{
|
|
37992
|
+
key: "flashlight",
|
|
37993
|
+
group: "lighting",
|
|
37994
|
+
synonyms: [
|
|
37995
|
+
"faretto",
|
|
37996
|
+
"torcia",
|
|
37997
|
+
"spot",
|
|
37998
|
+
"faro"
|
|
37999
|
+
]
|
|
38000
|
+
},
|
|
38001
|
+
{
|
|
38002
|
+
key: "heater",
|
|
38003
|
+
group: "lighting",
|
|
38004
|
+
synonyms: [
|
|
38005
|
+
"riscaldamento",
|
|
38006
|
+
"termosifone",
|
|
38007
|
+
"stufa",
|
|
38008
|
+
"calore"
|
|
38009
|
+
]
|
|
38010
|
+
},
|
|
38011
|
+
{
|
|
38012
|
+
key: "snowflake",
|
|
38013
|
+
group: "lighting",
|
|
38014
|
+
synonyms: [
|
|
38015
|
+
"freddo",
|
|
38016
|
+
"raffrescamento",
|
|
38017
|
+
"aria",
|
|
38018
|
+
"condizionata",
|
|
38019
|
+
"neve"
|
|
38020
|
+
]
|
|
38021
|
+
},
|
|
38022
|
+
{
|
|
38023
|
+
key: "thermometer-sun",
|
|
38024
|
+
group: "lighting",
|
|
38025
|
+
synonyms: [
|
|
38026
|
+
"caldo",
|
|
38027
|
+
"temperatura",
|
|
38028
|
+
"alta"
|
|
38029
|
+
]
|
|
38030
|
+
},
|
|
38031
|
+
{
|
|
38032
|
+
key: "thermometer-snowflake",
|
|
38033
|
+
group: "lighting",
|
|
38034
|
+
synonyms: [
|
|
38035
|
+
"freddo",
|
|
38036
|
+
"temperatura",
|
|
38037
|
+
"bassa"
|
|
38038
|
+
]
|
|
38039
|
+
},
|
|
38040
|
+
{
|
|
38041
|
+
key: "refrigerator",
|
|
38042
|
+
group: "lighting",
|
|
38043
|
+
synonyms: ["frigorifero", "frigo"]
|
|
38044
|
+
},
|
|
38045
|
+
{
|
|
38046
|
+
key: "microwave",
|
|
38047
|
+
group: "lighting",
|
|
38048
|
+
synonyms: ["microonde", "forno"]
|
|
38049
|
+
},
|
|
38050
|
+
{
|
|
38051
|
+
key: "washing-machine",
|
|
38052
|
+
group: "lighting",
|
|
38053
|
+
synonyms: [
|
|
38054
|
+
"lavatrice",
|
|
38055
|
+
"lavastoviglie",
|
|
38056
|
+
"asciugatrice"
|
|
38057
|
+
]
|
|
38058
|
+
},
|
|
38059
|
+
{
|
|
38060
|
+
key: "cooking-pot",
|
|
38061
|
+
group: "lighting",
|
|
38062
|
+
synonyms: [
|
|
38063
|
+
"pentola",
|
|
38064
|
+
"cucina",
|
|
38065
|
+
"fornello"
|
|
38066
|
+
]
|
|
38067
|
+
},
|
|
38068
|
+
{
|
|
38069
|
+
key: "utensils",
|
|
38070
|
+
group: "lighting",
|
|
38071
|
+
synonyms: [
|
|
38072
|
+
"cucina",
|
|
38073
|
+
"posate",
|
|
38074
|
+
"sala",
|
|
38075
|
+
"pranzo"
|
|
38076
|
+
]
|
|
38077
|
+
},
|
|
38078
|
+
{
|
|
38079
|
+
key: "thermometer",
|
|
38080
|
+
group: "sensing",
|
|
38081
|
+
synonyms: [
|
|
38082
|
+
"temperatura",
|
|
38083
|
+
"termometro",
|
|
38084
|
+
"termostato"
|
|
38085
|
+
]
|
|
38086
|
+
},
|
|
38087
|
+
{
|
|
38088
|
+
key: "droplets",
|
|
38089
|
+
group: "sensing",
|
|
38090
|
+
synonyms: [
|
|
38091
|
+
"umidita",
|
|
38092
|
+
"acqua",
|
|
38093
|
+
"allagamento",
|
|
38094
|
+
"perdita"
|
|
38095
|
+
]
|
|
38096
|
+
},
|
|
38097
|
+
{
|
|
38098
|
+
key: "droplet",
|
|
38099
|
+
group: "sensing",
|
|
38100
|
+
synonyms: [
|
|
38101
|
+
"goccia",
|
|
38102
|
+
"acqua",
|
|
38103
|
+
"perdita",
|
|
38104
|
+
"allagamento"
|
|
38105
|
+
]
|
|
38106
|
+
},
|
|
38107
|
+
{
|
|
38108
|
+
key: "gauge",
|
|
38109
|
+
group: "sensing",
|
|
38110
|
+
synonyms: [
|
|
38111
|
+
"misura",
|
|
38112
|
+
"livello",
|
|
38113
|
+
"pressione",
|
|
38114
|
+
"indicatore"
|
|
38115
|
+
]
|
|
38116
|
+
},
|
|
38117
|
+
{
|
|
38118
|
+
key: "gauge2",
|
|
38119
|
+
group: "sensing",
|
|
38120
|
+
synonyms: [
|
|
38121
|
+
"misura",
|
|
38122
|
+
"livello",
|
|
38123
|
+
"pressione",
|
|
38124
|
+
"indicatore"
|
|
38125
|
+
],
|
|
38126
|
+
aliasOf: "gauge"
|
|
38127
|
+
},
|
|
38128
|
+
{
|
|
38129
|
+
key: "sun",
|
|
38130
|
+
group: "sensing",
|
|
38131
|
+
synonyms: [
|
|
38132
|
+
"sole",
|
|
38133
|
+
"luminosita",
|
|
38134
|
+
"giorno",
|
|
38135
|
+
"esterno"
|
|
38136
|
+
]
|
|
38137
|
+
},
|
|
38138
|
+
{
|
|
38139
|
+
key: "sun-medium",
|
|
38140
|
+
group: "sensing",
|
|
38141
|
+
synonyms: ["sole", "luminosita"]
|
|
38142
|
+
},
|
|
38143
|
+
{
|
|
38144
|
+
key: "sunrise",
|
|
38145
|
+
group: "sensing",
|
|
38146
|
+
synonyms: ["alba"]
|
|
38147
|
+
},
|
|
38148
|
+
{
|
|
38149
|
+
key: "moon",
|
|
38150
|
+
group: "sensing",
|
|
38151
|
+
synonyms: [
|
|
38152
|
+
"notte",
|
|
38153
|
+
"luna",
|
|
38154
|
+
"notturna",
|
|
38155
|
+
"nightvision"
|
|
38156
|
+
]
|
|
38157
|
+
},
|
|
38158
|
+
{
|
|
38159
|
+
key: "wind",
|
|
38160
|
+
group: "sensing",
|
|
38161
|
+
synonyms: [
|
|
38162
|
+
"vento",
|
|
38163
|
+
"aria",
|
|
38164
|
+
"gas",
|
|
38165
|
+
"monossido",
|
|
38166
|
+
"co"
|
|
38167
|
+
]
|
|
38168
|
+
},
|
|
38169
|
+
{
|
|
38170
|
+
key: "cloud",
|
|
38171
|
+
group: "sensing",
|
|
38172
|
+
synonyms: ["nuvola", "nuvoloso"]
|
|
38173
|
+
},
|
|
38174
|
+
{
|
|
38175
|
+
key: "cloud-rain",
|
|
38176
|
+
group: "sensing",
|
|
38177
|
+
synonyms: ["pioggia"]
|
|
38178
|
+
},
|
|
38179
|
+
{
|
|
38180
|
+
key: "cloud-sun",
|
|
38181
|
+
group: "sensing",
|
|
38182
|
+
synonyms: [
|
|
38183
|
+
"variabile",
|
|
38184
|
+
"sole",
|
|
38185
|
+
"nuvole"
|
|
38186
|
+
]
|
|
38187
|
+
},
|
|
38188
|
+
{
|
|
38189
|
+
key: "waves",
|
|
38190
|
+
group: "sensing",
|
|
38191
|
+
synonyms: [
|
|
38192
|
+
"onde",
|
|
38193
|
+
"acqua",
|
|
38194
|
+
"vibrazione"
|
|
38195
|
+
]
|
|
38196
|
+
},
|
|
38197
|
+
{
|
|
38198
|
+
key: "sprout",
|
|
38199
|
+
group: "sensing",
|
|
38200
|
+
synonyms: [
|
|
38201
|
+
"irrigazione",
|
|
38202
|
+
"pianta",
|
|
38203
|
+
"germoglio",
|
|
38204
|
+
"orto"
|
|
38205
|
+
]
|
|
38206
|
+
},
|
|
38207
|
+
{
|
|
38208
|
+
key: "leaf",
|
|
38209
|
+
group: "sensing",
|
|
38210
|
+
synonyms: [
|
|
38211
|
+
"foglia",
|
|
38212
|
+
"pianta",
|
|
38213
|
+
"verde"
|
|
38214
|
+
]
|
|
38215
|
+
},
|
|
38216
|
+
{
|
|
38217
|
+
key: "flower",
|
|
38218
|
+
group: "sensing",
|
|
38219
|
+
synonyms: ["fiore", "pianta"]
|
|
38220
|
+
},
|
|
38221
|
+
{
|
|
38222
|
+
key: "zap",
|
|
38223
|
+
group: "sensing",
|
|
38224
|
+
synonyms: [
|
|
38225
|
+
"energia",
|
|
38226
|
+
"corrente",
|
|
38227
|
+
"consumo",
|
|
38228
|
+
"watt",
|
|
38229
|
+
"elettricita"
|
|
38230
|
+
]
|
|
38231
|
+
},
|
|
38232
|
+
{
|
|
38233
|
+
key: "flame",
|
|
38234
|
+
group: "sensing",
|
|
38235
|
+
synonyms: [
|
|
38236
|
+
"fiamma",
|
|
38237
|
+
"gas",
|
|
38238
|
+
"fuoco",
|
|
38239
|
+
"incendio"
|
|
38240
|
+
]
|
|
38241
|
+
},
|
|
38242
|
+
{
|
|
38243
|
+
key: "bell",
|
|
38244
|
+
group: "sensing",
|
|
38245
|
+
synonyms: [
|
|
38246
|
+
"campanello",
|
|
38247
|
+
"suoneria",
|
|
38248
|
+
"notifica",
|
|
38249
|
+
"bell",
|
|
38250
|
+
"chime"
|
|
38251
|
+
]
|
|
38252
|
+
},
|
|
38253
|
+
{
|
|
38254
|
+
key: "bell-ring",
|
|
38255
|
+
group: "sensing",
|
|
38256
|
+
synonyms: [
|
|
38257
|
+
"campanello",
|
|
38258
|
+
"suona",
|
|
38259
|
+
"doorbell",
|
|
38260
|
+
"notifica"
|
|
38261
|
+
]
|
|
38262
|
+
},
|
|
38263
|
+
{
|
|
38264
|
+
key: "bell-off",
|
|
38265
|
+
group: "sensing",
|
|
38266
|
+
synonyms: [
|
|
38267
|
+
"silenzioso",
|
|
38268
|
+
"notifiche",
|
|
38269
|
+
"disattivate",
|
|
38270
|
+
"muto"
|
|
38271
|
+
]
|
|
38272
|
+
},
|
|
38273
|
+
{
|
|
38274
|
+
key: "siren",
|
|
38275
|
+
group: "sensing",
|
|
38276
|
+
synonyms: ["sirena", "allarme"]
|
|
38277
|
+
},
|
|
38278
|
+
{
|
|
38279
|
+
key: "radar",
|
|
38280
|
+
group: "sensing",
|
|
38281
|
+
synonyms: [
|
|
38282
|
+
"movimento",
|
|
38283
|
+
"presenza",
|
|
38284
|
+
"pir",
|
|
38285
|
+
"rilevamento",
|
|
38286
|
+
"radar"
|
|
38287
|
+
]
|
|
38288
|
+
},
|
|
38289
|
+
{
|
|
38290
|
+
key: "activity",
|
|
38291
|
+
group: "sensing",
|
|
38292
|
+
synonyms: [
|
|
38293
|
+
"attivita",
|
|
38294
|
+
"sensore",
|
|
38295
|
+
"generico"
|
|
38296
|
+
]
|
|
38297
|
+
},
|
|
38298
|
+
{
|
|
38299
|
+
key: "hash",
|
|
38300
|
+
group: "sensing",
|
|
38301
|
+
synonyms: [
|
|
38302
|
+
"numero",
|
|
38303
|
+
"contatore",
|
|
38304
|
+
"valore"
|
|
38305
|
+
]
|
|
38306
|
+
},
|
|
38307
|
+
{
|
|
38308
|
+
key: "clock",
|
|
38309
|
+
group: "sensing",
|
|
38310
|
+
synonyms: [
|
|
38311
|
+
"orologio",
|
|
38312
|
+
"ora",
|
|
38313
|
+
"tempo"
|
|
38314
|
+
]
|
|
38315
|
+
},
|
|
38316
|
+
{
|
|
38317
|
+
key: "timer",
|
|
38318
|
+
group: "sensing",
|
|
38319
|
+
synonyms: [
|
|
38320
|
+
"timer",
|
|
38321
|
+
"conto",
|
|
38322
|
+
"rovescia"
|
|
38323
|
+
]
|
|
38324
|
+
},
|
|
38325
|
+
{
|
|
38326
|
+
key: "calendar",
|
|
38327
|
+
group: "sensing",
|
|
38328
|
+
synonyms: [
|
|
38329
|
+
"calendario",
|
|
38330
|
+
"data",
|
|
38331
|
+
"pianificazione"
|
|
38332
|
+
]
|
|
38333
|
+
},
|
|
38334
|
+
{
|
|
38335
|
+
key: "pipette",
|
|
38336
|
+
group: "sensing",
|
|
38337
|
+
synonyms: [
|
|
38338
|
+
"qualita",
|
|
38339
|
+
"aria",
|
|
38340
|
+
"misura",
|
|
38341
|
+
"campione"
|
|
38342
|
+
]
|
|
38343
|
+
},
|
|
38344
|
+
{
|
|
38345
|
+
key: "fan",
|
|
38346
|
+
group: "sensing",
|
|
38347
|
+
synonyms: [
|
|
38348
|
+
"ventilatore",
|
|
38349
|
+
"ventola",
|
|
38350
|
+
"aria"
|
|
38351
|
+
]
|
|
38352
|
+
},
|
|
38353
|
+
{
|
|
38354
|
+
key: "bath",
|
|
38355
|
+
group: "sensing",
|
|
38356
|
+
synonyms: [
|
|
38357
|
+
"bagno",
|
|
38358
|
+
"vasca",
|
|
38359
|
+
"doccia"
|
|
38360
|
+
]
|
|
38361
|
+
},
|
|
38362
|
+
{
|
|
38363
|
+
key: "footprints",
|
|
38364
|
+
group: "sensing",
|
|
38365
|
+
synonyms: [
|
|
38366
|
+
"movimento",
|
|
38367
|
+
"passi",
|
|
38368
|
+
"presenza",
|
|
38369
|
+
"motion"
|
|
38370
|
+
]
|
|
38371
|
+
},
|
|
38372
|
+
{
|
|
38373
|
+
key: "alarm-smoke",
|
|
38374
|
+
group: "sensing",
|
|
38375
|
+
synonyms: [
|
|
38376
|
+
"fumo",
|
|
38377
|
+
"antincendio",
|
|
38378
|
+
"rilevatore"
|
|
38379
|
+
]
|
|
38380
|
+
},
|
|
38381
|
+
{
|
|
38382
|
+
key: "shield-alert",
|
|
38383
|
+
group: "sensing",
|
|
38384
|
+
synonyms: [
|
|
38385
|
+
"manomissione",
|
|
38386
|
+
"allarme",
|
|
38387
|
+
"tamper",
|
|
38388
|
+
"avviso"
|
|
38389
|
+
]
|
|
38390
|
+
},
|
|
38391
|
+
{
|
|
38392
|
+
key: "vibrate",
|
|
38393
|
+
group: "sensing",
|
|
38394
|
+
synonyms: [
|
|
38395
|
+
"vibrazione",
|
|
38396
|
+
"urto",
|
|
38397
|
+
"tamper"
|
|
38398
|
+
]
|
|
38399
|
+
},
|
|
38400
|
+
{
|
|
38401
|
+
key: "wifi",
|
|
38402
|
+
group: "sensing",
|
|
38403
|
+
synonyms: [
|
|
38404
|
+
"wifi",
|
|
38405
|
+
"rete",
|
|
38406
|
+
"segnale"
|
|
38407
|
+
]
|
|
38408
|
+
},
|
|
38409
|
+
{
|
|
38410
|
+
key: "wifi-off",
|
|
38411
|
+
group: "sensing",
|
|
38412
|
+
synonyms: [
|
|
38413
|
+
"wifi",
|
|
38414
|
+
"assente",
|
|
38415
|
+
"offline",
|
|
38416
|
+
"disconnesso"
|
|
38417
|
+
]
|
|
38418
|
+
},
|
|
38419
|
+
{
|
|
38420
|
+
key: "volume-2",
|
|
38421
|
+
group: "sensing",
|
|
38422
|
+
synonyms: [
|
|
38423
|
+
"volume",
|
|
38424
|
+
"audio",
|
|
38425
|
+
"altoparlante",
|
|
38426
|
+
"suono"
|
|
38427
|
+
]
|
|
38428
|
+
},
|
|
38429
|
+
{
|
|
38430
|
+
key: "volume-x",
|
|
38431
|
+
group: "sensing",
|
|
38432
|
+
synonyms: [
|
|
38433
|
+
"muto",
|
|
38434
|
+
"silenziato",
|
|
38435
|
+
"volume",
|
|
38436
|
+
"spento"
|
|
38437
|
+
]
|
|
38438
|
+
},
|
|
38439
|
+
{
|
|
38440
|
+
key: "heart",
|
|
38441
|
+
group: "sensing",
|
|
38442
|
+
synonyms: [
|
|
38443
|
+
"cuore",
|
|
38444
|
+
"preferito",
|
|
38445
|
+
"salute"
|
|
38446
|
+
]
|
|
38447
|
+
},
|
|
38448
|
+
{
|
|
38449
|
+
key: "heart-pulse",
|
|
38450
|
+
group: "sensing",
|
|
38451
|
+
synonyms: [
|
|
38452
|
+
"battito",
|
|
38453
|
+
"salute",
|
|
38454
|
+
"monitoraggio"
|
|
38455
|
+
]
|
|
38456
|
+
},
|
|
38457
|
+
{
|
|
38458
|
+
key: "star",
|
|
38459
|
+
group: "sensing",
|
|
38460
|
+
synonyms: [
|
|
38461
|
+
"preferito",
|
|
38462
|
+
"stella",
|
|
38463
|
+
"importante"
|
|
38464
|
+
]
|
|
38465
|
+
},
|
|
38466
|
+
{
|
|
38467
|
+
key: "sparkles",
|
|
38468
|
+
group: "sensing",
|
|
38469
|
+
synonyms: [
|
|
38470
|
+
"automatico",
|
|
38471
|
+
"intelligente",
|
|
38472
|
+
"ai",
|
|
38473
|
+
"scena"
|
|
38474
|
+
]
|
|
38475
|
+
},
|
|
38476
|
+
{
|
|
38477
|
+
key: "house",
|
|
38478
|
+
group: "place",
|
|
38479
|
+
synonyms: [
|
|
38480
|
+
"casa",
|
|
38481
|
+
"abitazione",
|
|
38482
|
+
"home"
|
|
38483
|
+
]
|
|
38484
|
+
},
|
|
38485
|
+
{
|
|
38486
|
+
key: "sofa",
|
|
38487
|
+
group: "place",
|
|
38488
|
+
synonyms: [
|
|
38489
|
+
"salone",
|
|
38490
|
+
"soggiorno",
|
|
38491
|
+
"divano",
|
|
38492
|
+
"living"
|
|
38493
|
+
]
|
|
38494
|
+
},
|
|
38495
|
+
{
|
|
38496
|
+
key: "bed-double",
|
|
38497
|
+
group: "place",
|
|
38498
|
+
synonyms: [
|
|
38499
|
+
"camera",
|
|
38500
|
+
"letto",
|
|
38501
|
+
"matrimoniale"
|
|
38502
|
+
]
|
|
38503
|
+
},
|
|
38504
|
+
{
|
|
38505
|
+
key: "bed",
|
|
38506
|
+
group: "place",
|
|
38507
|
+
synonyms: [
|
|
38508
|
+
"cameretta",
|
|
38509
|
+
"letto",
|
|
38510
|
+
"singolo"
|
|
38511
|
+
]
|
|
38512
|
+
},
|
|
38513
|
+
{
|
|
38514
|
+
key: "warehouse",
|
|
38515
|
+
group: "place",
|
|
38516
|
+
synonyms: [
|
|
38517
|
+
"magazzino",
|
|
38518
|
+
"capanno",
|
|
38519
|
+
"deposito",
|
|
38520
|
+
"cantina"
|
|
38521
|
+
]
|
|
38522
|
+
},
|
|
38523
|
+
{
|
|
38524
|
+
key: "trees",
|
|
38525
|
+
group: "place",
|
|
38526
|
+
synonyms: [
|
|
38527
|
+
"giardino",
|
|
38528
|
+
"alberi",
|
|
38529
|
+
"esterno",
|
|
38530
|
+
"parco"
|
|
38531
|
+
]
|
|
38532
|
+
},
|
|
38533
|
+
{
|
|
38534
|
+
key: "tree-pine",
|
|
38535
|
+
group: "place",
|
|
38536
|
+
synonyms: [
|
|
38537
|
+
"albero",
|
|
38538
|
+
"pino",
|
|
38539
|
+
"giardino"
|
|
38540
|
+
]
|
|
38541
|
+
},
|
|
38542
|
+
{
|
|
38543
|
+
key: "fence",
|
|
38544
|
+
group: "place",
|
|
38545
|
+
synonyms: [
|
|
38546
|
+
"recinto",
|
|
38547
|
+
"cancello",
|
|
38548
|
+
"staccionata",
|
|
38549
|
+
"perimetro"
|
|
38550
|
+
]
|
|
38551
|
+
},
|
|
38552
|
+
{
|
|
38553
|
+
key: "store",
|
|
38554
|
+
group: "place",
|
|
38555
|
+
synonyms: [
|
|
38556
|
+
"negozio",
|
|
38557
|
+
"ingresso",
|
|
38558
|
+
"vetrina"
|
|
38559
|
+
]
|
|
38560
|
+
},
|
|
38561
|
+
{
|
|
38562
|
+
key: "building",
|
|
38563
|
+
group: "place",
|
|
38564
|
+
synonyms: ["edificio", "palazzo"]
|
|
38565
|
+
},
|
|
38566
|
+
{
|
|
38567
|
+
key: "building-2",
|
|
38568
|
+
group: "place",
|
|
38569
|
+
synonyms: [
|
|
38570
|
+
"edificio",
|
|
38571
|
+
"uffici",
|
|
38572
|
+
"condominio"
|
|
38573
|
+
]
|
|
38574
|
+
},
|
|
38575
|
+
{
|
|
38576
|
+
key: "mountain",
|
|
38577
|
+
group: "place",
|
|
38578
|
+
synonyms: [
|
|
38579
|
+
"montagna",
|
|
38580
|
+
"esterno",
|
|
38581
|
+
"panorama"
|
|
38582
|
+
]
|
|
38583
|
+
},
|
|
38584
|
+
{
|
|
38585
|
+
key: "umbrella",
|
|
38586
|
+
group: "place",
|
|
38587
|
+
synonyms: [
|
|
38588
|
+
"tenda",
|
|
38589
|
+
"markise",
|
|
38590
|
+
"ombrellone",
|
|
38591
|
+
"pergola",
|
|
38592
|
+
"veranda"
|
|
38593
|
+
]
|
|
38594
|
+
},
|
|
38595
|
+
{
|
|
38596
|
+
key: "dumbbell",
|
|
38597
|
+
group: "place",
|
|
38598
|
+
synonyms: ["palestra", "attrezzi"]
|
|
38599
|
+
},
|
|
38600
|
+
{
|
|
38601
|
+
key: "wrench",
|
|
38602
|
+
group: "place",
|
|
38603
|
+
synonyms: [
|
|
38604
|
+
"officina",
|
|
38605
|
+
"manutenzione",
|
|
38606
|
+
"attrezzi",
|
|
38607
|
+
"utenze"
|
|
38608
|
+
]
|
|
38609
|
+
},
|
|
38610
|
+
{
|
|
38611
|
+
key: "hammer",
|
|
38612
|
+
group: "place",
|
|
38613
|
+
synonyms: [
|
|
38614
|
+
"officina",
|
|
38615
|
+
"martello",
|
|
38616
|
+
"lavori"
|
|
38617
|
+
]
|
|
38618
|
+
},
|
|
38619
|
+
{
|
|
38620
|
+
key: "drill",
|
|
38621
|
+
group: "place",
|
|
38622
|
+
synonyms: [
|
|
38623
|
+
"officina",
|
|
38624
|
+
"trapano",
|
|
38625
|
+
"lavori"
|
|
38626
|
+
]
|
|
38627
|
+
},
|
|
38628
|
+
{
|
|
38629
|
+
key: "waves-ladder",
|
|
38630
|
+
group: "place",
|
|
38631
|
+
synonyms: ["piscina", "vasca"]
|
|
38632
|
+
},
|
|
38633
|
+
{
|
|
38634
|
+
key: "circle-parking",
|
|
38635
|
+
group: "place",
|
|
38636
|
+
synonyms: [
|
|
38637
|
+
"parcheggio",
|
|
38638
|
+
"posto",
|
|
38639
|
+
"auto",
|
|
38640
|
+
"garage"
|
|
38641
|
+
]
|
|
38642
|
+
},
|
|
38643
|
+
{
|
|
38644
|
+
key: "route",
|
|
38645
|
+
group: "place",
|
|
38646
|
+
synonyms: [
|
|
38647
|
+
"percorso",
|
|
38648
|
+
"strada",
|
|
38649
|
+
"vialetto",
|
|
38650
|
+
"tragitto"
|
|
38651
|
+
]
|
|
38652
|
+
},
|
|
38653
|
+
{
|
|
38654
|
+
key: "car",
|
|
38655
|
+
group: "life",
|
|
38656
|
+
synonyms: [
|
|
38657
|
+
"auto",
|
|
38658
|
+
"macchina",
|
|
38659
|
+
"veicolo",
|
|
38660
|
+
"parcheggio"
|
|
38661
|
+
]
|
|
38662
|
+
},
|
|
38663
|
+
{
|
|
38664
|
+
key: "bike",
|
|
38665
|
+
group: "life",
|
|
38666
|
+
synonyms: [
|
|
38667
|
+
"bicicletta",
|
|
38668
|
+
"bici",
|
|
38669
|
+
"moto"
|
|
38670
|
+
]
|
|
38671
|
+
},
|
|
38672
|
+
{
|
|
38673
|
+
key: "bus",
|
|
38674
|
+
group: "life",
|
|
38675
|
+
synonyms: ["autobus", "pullman"]
|
|
38676
|
+
},
|
|
38677
|
+
{
|
|
38678
|
+
key: "truck",
|
|
38679
|
+
group: "life",
|
|
38680
|
+
synonyms: [
|
|
38681
|
+
"camion",
|
|
38682
|
+
"furgone",
|
|
38683
|
+
"consegna"
|
|
38684
|
+
]
|
|
38685
|
+
},
|
|
38686
|
+
{
|
|
38687
|
+
key: "plane",
|
|
38688
|
+
group: "life",
|
|
38689
|
+
synonyms: ["aereo"]
|
|
38690
|
+
},
|
|
38691
|
+
{
|
|
38692
|
+
key: "train-front",
|
|
38693
|
+
group: "life",
|
|
38694
|
+
synonyms: ["treno"]
|
|
38695
|
+
},
|
|
38696
|
+
{
|
|
38697
|
+
key: "sailboat",
|
|
38698
|
+
group: "life",
|
|
38699
|
+
synonyms: ["barca", "imbarcazione"]
|
|
38700
|
+
},
|
|
38701
|
+
{
|
|
38702
|
+
key: "dog",
|
|
38703
|
+
group: "life",
|
|
38704
|
+
synonyms: ["cane", "animale"]
|
|
38705
|
+
},
|
|
38706
|
+
{
|
|
38707
|
+
key: "cat",
|
|
38708
|
+
group: "life",
|
|
38709
|
+
synonyms: ["gatto", "animale"]
|
|
38710
|
+
},
|
|
38711
|
+
{
|
|
38712
|
+
key: "bird",
|
|
38713
|
+
group: "life",
|
|
38714
|
+
synonyms: ["uccello", "animale"]
|
|
38715
|
+
},
|
|
38716
|
+
{
|
|
38717
|
+
key: "paw-print",
|
|
38718
|
+
group: "life",
|
|
38719
|
+
synonyms: [
|
|
38720
|
+
"animale",
|
|
38721
|
+
"zampa",
|
|
38722
|
+
"pet"
|
|
38723
|
+
]
|
|
38724
|
+
},
|
|
38725
|
+
{
|
|
38726
|
+
key: "baby",
|
|
38727
|
+
group: "life",
|
|
38728
|
+
synonyms: [
|
|
38729
|
+
"neonato",
|
|
38730
|
+
"bimbo",
|
|
38731
|
+
"cameretta",
|
|
38732
|
+
"babymonitor"
|
|
38733
|
+
]
|
|
38734
|
+
},
|
|
38735
|
+
{
|
|
38736
|
+
key: "user",
|
|
38737
|
+
group: "life",
|
|
38738
|
+
synonyms: ["persona", "utente"]
|
|
38739
|
+
},
|
|
38740
|
+
{
|
|
38741
|
+
key: "users",
|
|
38742
|
+
group: "life",
|
|
38743
|
+
synonyms: [
|
|
38744
|
+
"persone",
|
|
38745
|
+
"gruppo",
|
|
38746
|
+
"famiglia"
|
|
38747
|
+
]
|
|
38748
|
+
},
|
|
38749
|
+
{
|
|
38750
|
+
key: "person-standing",
|
|
38751
|
+
group: "life",
|
|
38752
|
+
synonyms: [
|
|
38753
|
+
"persona",
|
|
38754
|
+
"piedi",
|
|
38755
|
+
"presenza"
|
|
38756
|
+
]
|
|
38757
|
+
}
|
|
38758
|
+
];
|
|
38759
|
+
DISPLAY_ICON_VOCABULARY.map((e) => e.key);
|
|
38760
|
+
DISPLAY_ICON_VOCABULARY.filter((e) => e.aliasOf === void 0).map((e) => e.key);
|
|
38761
|
+
new Map(DISPLAY_ICON_VOCABULARY.map((e) => [e.key, e]));
|
|
36021
38762
|
Object.freeze({
|
|
38763
|
+
"accessories.getStatus": {
|
|
38764
|
+
capName: "accessories",
|
|
38765
|
+
capScope: "device",
|
|
38766
|
+
addonId: null,
|
|
38767
|
+
access: "view"
|
|
38768
|
+
},
|
|
36022
38769
|
"accessories.setChildHidden": {
|
|
36023
38770
|
capName: "accessories",
|
|
36024
38771
|
capScope: "device",
|
|
@@ -36301,6 +39048,12 @@ Object.freeze({
|
|
|
36301
39048
|
addonId: null,
|
|
36302
39049
|
access: "view"
|
|
36303
39050
|
},
|
|
39051
|
+
"airQualitySensor.getStatus": {
|
|
39052
|
+
capName: "air-quality-sensor",
|
|
39053
|
+
capScope: "device",
|
|
39054
|
+
addonId: null,
|
|
39055
|
+
access: "view"
|
|
39056
|
+
},
|
|
36304
39057
|
"alarmPanel.arm": {
|
|
36305
39058
|
capName: "alarm-panel",
|
|
36306
39059
|
capScope: "device",
|
|
@@ -36313,6 +39066,12 @@ Object.freeze({
|
|
|
36313
39066
|
addonId: null,
|
|
36314
39067
|
access: "create"
|
|
36315
39068
|
},
|
|
39069
|
+
"alarmPanel.getStatus": {
|
|
39070
|
+
capName: "alarm-panel",
|
|
39071
|
+
capScope: "device",
|
|
39072
|
+
addonId: null,
|
|
39073
|
+
access: "view"
|
|
39074
|
+
},
|
|
36316
39075
|
"alarmPanel.trigger": {
|
|
36317
39076
|
capName: "alarm-panel",
|
|
36318
39077
|
capScope: "device",
|
|
@@ -36361,6 +39120,30 @@ Object.freeze({
|
|
|
36361
39120
|
addonId: null,
|
|
36362
39121
|
access: "create"
|
|
36363
39122
|
},
|
|
39123
|
+
"ambientLightSensor.getStatus": {
|
|
39124
|
+
capName: "ambient-light-sensor",
|
|
39125
|
+
capScope: "device",
|
|
39126
|
+
addonId: null,
|
|
39127
|
+
access: "view"
|
|
39128
|
+
},
|
|
39129
|
+
"audioAnalysis.applyDeviceSettingsPatch": {
|
|
39130
|
+
capName: "audio-analysis",
|
|
39131
|
+
capScope: "device",
|
|
39132
|
+
addonId: null,
|
|
39133
|
+
access: "create"
|
|
39134
|
+
},
|
|
39135
|
+
"audioAnalysis.getDeviceLiveContribution": {
|
|
39136
|
+
capName: "audio-analysis",
|
|
39137
|
+
capScope: "device",
|
|
39138
|
+
addonId: null,
|
|
39139
|
+
access: "view"
|
|
39140
|
+
},
|
|
39141
|
+
"audioAnalysis.getDeviceSettingsContribution": {
|
|
39142
|
+
capName: "audio-analysis",
|
|
39143
|
+
capScope: "device",
|
|
39144
|
+
addonId: null,
|
|
39145
|
+
access: "view"
|
|
39146
|
+
},
|
|
36364
39147
|
"audioAnalysis.resolveDeviceSettings": {
|
|
36365
39148
|
capName: "audio-analysis",
|
|
36366
39149
|
capScope: "device",
|
|
@@ -36523,6 +39306,12 @@ Object.freeze({
|
|
|
36523
39306
|
addonId: null,
|
|
36524
39307
|
access: "create"
|
|
36525
39308
|
},
|
|
39309
|
+
"automationControl.getStatus": {
|
|
39310
|
+
capName: "automation-control",
|
|
39311
|
+
capScope: "device",
|
|
39312
|
+
addonId: null,
|
|
39313
|
+
access: "view"
|
|
39314
|
+
},
|
|
36526
39315
|
"automationControl.trigger": {
|
|
36527
39316
|
capName: "automation-control",
|
|
36528
39317
|
capScope: "device",
|
|
@@ -36619,12 +39408,30 @@ Object.freeze({
|
|
|
36619
39408
|
addonId: null,
|
|
36620
39409
|
access: "create"
|
|
36621
39410
|
},
|
|
39411
|
+
"battery.getStatus": {
|
|
39412
|
+
capName: "battery",
|
|
39413
|
+
capScope: "device",
|
|
39414
|
+
addonId: null,
|
|
39415
|
+
access: "view"
|
|
39416
|
+
},
|
|
36622
39417
|
"battery.wakeForStream": {
|
|
36623
39418
|
capName: "battery",
|
|
36624
39419
|
capScope: "device",
|
|
36625
39420
|
addonId: null,
|
|
36626
39421
|
access: "create"
|
|
36627
39422
|
},
|
|
39423
|
+
"binary.getStatus": {
|
|
39424
|
+
capName: "binary",
|
|
39425
|
+
capScope: "device",
|
|
39426
|
+
addonId: null,
|
|
39427
|
+
access: "view"
|
|
39428
|
+
},
|
|
39429
|
+
"brightness.getStatus": {
|
|
39430
|
+
capName: "brightness",
|
|
39431
|
+
capScope: "device",
|
|
39432
|
+
addonId: null,
|
|
39433
|
+
access: "view"
|
|
39434
|
+
},
|
|
36628
39435
|
"brightness.setBrightness": {
|
|
36629
39436
|
capName: "brightness",
|
|
36630
39437
|
capScope: "device",
|
|
@@ -36739,6 +39546,30 @@ Object.freeze({
|
|
|
36739
39546
|
addonId: null,
|
|
36740
39547
|
access: "view"
|
|
36741
39548
|
},
|
|
39549
|
+
"cameraCredentials.getStatus": {
|
|
39550
|
+
capName: "camera-credentials",
|
|
39551
|
+
capScope: "device",
|
|
39552
|
+
addonId: null,
|
|
39553
|
+
access: "view"
|
|
39554
|
+
},
|
|
39555
|
+
"cameraGridLayout.applyDeviceSettingsPatch": {
|
|
39556
|
+
capName: "camera-grid-layout",
|
|
39557
|
+
capScope: "device",
|
|
39558
|
+
addonId: null,
|
|
39559
|
+
access: "create"
|
|
39560
|
+
},
|
|
39561
|
+
"cameraGridLayout.getDeviceLiveContribution": {
|
|
39562
|
+
capName: "camera-grid-layout",
|
|
39563
|
+
capScope: "device",
|
|
39564
|
+
addonId: null,
|
|
39565
|
+
access: "view"
|
|
39566
|
+
},
|
|
39567
|
+
"cameraGridLayout.getDeviceSettingsContribution": {
|
|
39568
|
+
capName: "camera-grid-layout",
|
|
39569
|
+
capScope: "device",
|
|
39570
|
+
addonId: null,
|
|
39571
|
+
access: "view"
|
|
39572
|
+
},
|
|
36742
39573
|
"cameraGridLayout.getLayout": {
|
|
36743
39574
|
capName: "camera-grid-layout",
|
|
36744
39575
|
capScope: "device",
|
|
@@ -36751,6 +39582,24 @@ Object.freeze({
|
|
|
36751
39582
|
addonId: null,
|
|
36752
39583
|
access: "create"
|
|
36753
39584
|
},
|
|
39585
|
+
"cameraPipelineConfig.applyDeviceSettingsPatch": {
|
|
39586
|
+
capName: "camera-pipeline-config",
|
|
39587
|
+
capScope: "device",
|
|
39588
|
+
addonId: null,
|
|
39589
|
+
access: "create"
|
|
39590
|
+
},
|
|
39591
|
+
"cameraPipelineConfig.getDeviceLiveContribution": {
|
|
39592
|
+
capName: "camera-pipeline-config",
|
|
39593
|
+
capScope: "device",
|
|
39594
|
+
addonId: null,
|
|
39595
|
+
access: "view"
|
|
39596
|
+
},
|
|
39597
|
+
"cameraPipelineConfig.getDeviceSettingsContribution": {
|
|
39598
|
+
capName: "camera-pipeline-config",
|
|
39599
|
+
capScope: "device",
|
|
39600
|
+
addonId: null,
|
|
39601
|
+
access: "view"
|
|
39602
|
+
},
|
|
36754
39603
|
"cameraStreams.getBrokerStreams": {
|
|
36755
39604
|
capName: "camera-streams",
|
|
36756
39605
|
capScope: "device",
|
|
@@ -36781,6 +39630,18 @@ Object.freeze({
|
|
|
36781
39630
|
addonId: null,
|
|
36782
39631
|
access: "view"
|
|
36783
39632
|
},
|
|
39633
|
+
"carbonMonoxide.getStatus": {
|
|
39634
|
+
capName: "carbon-monoxide",
|
|
39635
|
+
capScope: "device",
|
|
39636
|
+
addonId: null,
|
|
39637
|
+
access: "view"
|
|
39638
|
+
},
|
|
39639
|
+
"climateControl.getStatus": {
|
|
39640
|
+
capName: "climate-control",
|
|
39641
|
+
capScope: "device",
|
|
39642
|
+
addonId: null,
|
|
39643
|
+
access: "view"
|
|
39644
|
+
},
|
|
36784
39645
|
"climateControl.setFanMode": {
|
|
36785
39646
|
capName: "climate-control",
|
|
36786
39647
|
capScope: "device",
|
|
@@ -36829,6 +39690,12 @@ Object.freeze({
|
|
|
36829
39690
|
addonId: null,
|
|
36830
39691
|
access: "create"
|
|
36831
39692
|
},
|
|
39693
|
+
"color.getStatus": {
|
|
39694
|
+
capName: "color",
|
|
39695
|
+
capScope: "device",
|
|
39696
|
+
addonId: null,
|
|
39697
|
+
access: "view"
|
|
39698
|
+
},
|
|
36832
39699
|
"color.setColor": {
|
|
36833
39700
|
capName: "color",
|
|
36834
39701
|
capScope: "device",
|
|
@@ -36847,12 +39714,36 @@ Object.freeze({
|
|
|
36847
39714
|
addonId: null,
|
|
36848
39715
|
access: "create"
|
|
36849
39716
|
},
|
|
39717
|
+
"connectivity.getStatus": {
|
|
39718
|
+
capName: "connectivity",
|
|
39719
|
+
capScope: "device",
|
|
39720
|
+
addonId: null,
|
|
39721
|
+
access: "view"
|
|
39722
|
+
},
|
|
39723
|
+
"consumables.getStatus": {
|
|
39724
|
+
capName: "consumables",
|
|
39725
|
+
capScope: "device",
|
|
39726
|
+
addonId: null,
|
|
39727
|
+
access: "view"
|
|
39728
|
+
},
|
|
36850
39729
|
"consumables.reset": {
|
|
36851
39730
|
capName: "consumables",
|
|
36852
39731
|
capScope: "device",
|
|
36853
39732
|
addonId: null,
|
|
36854
39733
|
access: "delete"
|
|
36855
39734
|
},
|
|
39735
|
+
"contact.getStatus": {
|
|
39736
|
+
capName: "contact",
|
|
39737
|
+
capScope: "device",
|
|
39738
|
+
addonId: null,
|
|
39739
|
+
access: "view"
|
|
39740
|
+
},
|
|
39741
|
+
"control.getStatus": {
|
|
39742
|
+
capName: "control",
|
|
39743
|
+
capScope: "device",
|
|
39744
|
+
addonId: null,
|
|
39745
|
+
access: "view"
|
|
39746
|
+
},
|
|
36856
39747
|
"control.setValue": {
|
|
36857
39748
|
capName: "control",
|
|
36858
39749
|
capScope: "device",
|
|
@@ -36919,6 +39810,12 @@ Object.freeze({
|
|
|
36919
39810
|
addonId: null,
|
|
36920
39811
|
access: "create"
|
|
36921
39812
|
},
|
|
39813
|
+
"cover.getStatus": {
|
|
39814
|
+
capName: "cover",
|
|
39815
|
+
capScope: "device",
|
|
39816
|
+
addonId: null,
|
|
39817
|
+
access: "view"
|
|
39818
|
+
},
|
|
36922
39819
|
"cover.open": {
|
|
36923
39820
|
capName: "cover",
|
|
36924
39821
|
capScope: "device",
|
|
@@ -37051,6 +39948,12 @@ Object.freeze({
|
|
|
37051
39948
|
addonId: null,
|
|
37052
39949
|
access: "view"
|
|
37053
39950
|
},
|
|
39951
|
+
"dayNight.getStatus": {
|
|
39952
|
+
capName: "day-night",
|
|
39953
|
+
capScope: "device",
|
|
39954
|
+
addonId: null,
|
|
39955
|
+
access: "view"
|
|
39956
|
+
},
|
|
37054
39957
|
"dayNight.setSettings": {
|
|
37055
39958
|
capName: "day-night",
|
|
37056
39959
|
capScope: "device",
|
|
@@ -37141,6 +40044,30 @@ Object.freeze({
|
|
|
37141
40044
|
addonId: null,
|
|
37142
40045
|
access: "view"
|
|
37143
40046
|
},
|
|
40047
|
+
"detectionPipeline.applyDeviceSettingsPatch": {
|
|
40048
|
+
capName: "detection-pipeline",
|
|
40049
|
+
capScope: "device",
|
|
40050
|
+
addonId: null,
|
|
40051
|
+
access: "create"
|
|
40052
|
+
},
|
|
40053
|
+
"detectionPipeline.getDeviceLiveContribution": {
|
|
40054
|
+
capName: "detection-pipeline",
|
|
40055
|
+
capScope: "device",
|
|
40056
|
+
addonId: null,
|
|
40057
|
+
access: "view"
|
|
40058
|
+
},
|
|
40059
|
+
"detectionPipeline.getDeviceSettingsContribution": {
|
|
40060
|
+
capName: "detection-pipeline",
|
|
40061
|
+
capScope: "device",
|
|
40062
|
+
addonId: null,
|
|
40063
|
+
access: "view"
|
|
40064
|
+
},
|
|
40065
|
+
"deviceAdminLink.getAdminLink": {
|
|
40066
|
+
capName: "device-admin-link",
|
|
40067
|
+
capScope: "device",
|
|
40068
|
+
addonId: null,
|
|
40069
|
+
access: "view"
|
|
40070
|
+
},
|
|
37144
40071
|
"deviceAdoption.adopt": {
|
|
37145
40072
|
capName: "device-adoption",
|
|
37146
40073
|
capScope: "system",
|
|
@@ -37153,6 +40080,12 @@ Object.freeze({
|
|
|
37153
40080
|
addonId: null,
|
|
37154
40081
|
access: "view"
|
|
37155
40082
|
},
|
|
40083
|
+
"deviceAdoption.getStatus": {
|
|
40084
|
+
capName: "device-adoption",
|
|
40085
|
+
capScope: "system",
|
|
40086
|
+
addonId: null,
|
|
40087
|
+
access: "view"
|
|
40088
|
+
},
|
|
37156
40089
|
"deviceAdoption.listCandidateFilters": {
|
|
37157
40090
|
capName: "device-adoption",
|
|
37158
40091
|
capScope: "system",
|
|
@@ -37189,6 +40122,12 @@ Object.freeze({
|
|
|
37189
40122
|
addonId: null,
|
|
37190
40123
|
access: "create"
|
|
37191
40124
|
},
|
|
40125
|
+
"deviceDiscovery.getStatus": {
|
|
40126
|
+
capName: "device-discovery",
|
|
40127
|
+
capScope: "device",
|
|
40128
|
+
addonId: null,
|
|
40129
|
+
access: "view"
|
|
40130
|
+
},
|
|
37192
40131
|
"deviceDiscovery.listDiscovered": {
|
|
37193
40132
|
capName: "device-discovery",
|
|
37194
40133
|
capScope: "device",
|
|
@@ -37207,12 +40146,30 @@ Object.freeze({
|
|
|
37207
40146
|
addonId: null,
|
|
37208
40147
|
access: "create"
|
|
37209
40148
|
},
|
|
40149
|
+
"deviceExport.applyDeviceSettingsPatch": {
|
|
40150
|
+
capName: "device-export",
|
|
40151
|
+
capScope: "system",
|
|
40152
|
+
addonId: null,
|
|
40153
|
+
access: "create"
|
|
40154
|
+
},
|
|
37210
40155
|
"deviceExport.exposeDevice": {
|
|
37211
40156
|
capName: "device-export",
|
|
37212
40157
|
capScope: "system",
|
|
37213
40158
|
addonId: null,
|
|
37214
40159
|
access: "create"
|
|
37215
40160
|
},
|
|
40161
|
+
"deviceExport.getDeviceLiveContribution": {
|
|
40162
|
+
capName: "device-export",
|
|
40163
|
+
capScope: "system",
|
|
40164
|
+
addonId: null,
|
|
40165
|
+
access: "view"
|
|
40166
|
+
},
|
|
40167
|
+
"deviceExport.getDeviceSettingsContribution": {
|
|
40168
|
+
capName: "device-export",
|
|
40169
|
+
capScope: "system",
|
|
40170
|
+
addonId: null,
|
|
40171
|
+
access: "view"
|
|
40172
|
+
},
|
|
37216
40173
|
"deviceExport.getStatus": {
|
|
37217
40174
|
capName: "device-export",
|
|
37218
40175
|
capScope: "system",
|
|
@@ -37309,6 +40266,12 @@ Object.freeze({
|
|
|
37309
40266
|
addonId: null,
|
|
37310
40267
|
access: "create"
|
|
37311
40268
|
},
|
|
40269
|
+
"deviceManager.applyDeviceSettingsPatch": {
|
|
40270
|
+
capName: "device-manager",
|
|
40271
|
+
capScope: "system",
|
|
40272
|
+
addonId: null,
|
|
40273
|
+
access: "create"
|
|
40274
|
+
},
|
|
37312
40275
|
"deviceManager.applyInitialMeta": {
|
|
37313
40276
|
capName: "device-manager",
|
|
37314
40277
|
capScope: "system",
|
|
@@ -37411,6 +40374,12 @@ Object.freeze({
|
|
|
37411
40374
|
addonId: null,
|
|
37412
40375
|
access: "view"
|
|
37413
40376
|
},
|
|
40377
|
+
"deviceManager.getDeviceLiveContribution": {
|
|
40378
|
+
capName: "device-manager",
|
|
40379
|
+
capScope: "system",
|
|
40380
|
+
addonId: null,
|
|
40381
|
+
access: "view"
|
|
40382
|
+
},
|
|
37414
40383
|
"deviceManager.getDeviceLiveInfoAggregate": {
|
|
37415
40384
|
capName: "device-manager",
|
|
37416
40385
|
capScope: "system",
|
|
@@ -37423,6 +40392,12 @@ Object.freeze({
|
|
|
37423
40392
|
addonId: null,
|
|
37424
40393
|
access: "view"
|
|
37425
40394
|
},
|
|
40395
|
+
"deviceManager.getDeviceSettingsContribution": {
|
|
40396
|
+
capName: "device-manager",
|
|
40397
|
+
capScope: "system",
|
|
40398
|
+
addonId: null,
|
|
40399
|
+
access: "view"
|
|
40400
|
+
},
|
|
37426
40401
|
"deviceManager.getDeviceStatusAggregate": {
|
|
37427
40402
|
capName: "device-manager",
|
|
37428
40403
|
capScope: "system",
|
|
@@ -37915,6 +40890,36 @@ Object.freeze({
|
|
|
37915
40890
|
addonId: null,
|
|
37916
40891
|
access: "create"
|
|
37917
40892
|
},
|
|
40893
|
+
"deviceStatus.getStatus": {
|
|
40894
|
+
capName: "device-status",
|
|
40895
|
+
capScope: "device",
|
|
40896
|
+
addonId: null,
|
|
40897
|
+
access: "view"
|
|
40898
|
+
},
|
|
40899
|
+
"doorbell.applyDeviceSettingsPatch": {
|
|
40900
|
+
capName: "doorbell",
|
|
40901
|
+
capScope: "device",
|
|
40902
|
+
addonId: null,
|
|
40903
|
+
access: "create"
|
|
40904
|
+
},
|
|
40905
|
+
"doorbell.getDeviceLiveContribution": {
|
|
40906
|
+
capName: "doorbell",
|
|
40907
|
+
capScope: "device",
|
|
40908
|
+
addonId: null,
|
|
40909
|
+
access: "view"
|
|
40910
|
+
},
|
|
40911
|
+
"doorbell.getDeviceSettingsContribution": {
|
|
40912
|
+
capName: "doorbell",
|
|
40913
|
+
capScope: "device",
|
|
40914
|
+
addonId: null,
|
|
40915
|
+
access: "view"
|
|
40916
|
+
},
|
|
40917
|
+
"doorbell.getStatus": {
|
|
40918
|
+
capName: "doorbell",
|
|
40919
|
+
capScope: "device",
|
|
40920
|
+
addonId: null,
|
|
40921
|
+
access: "view"
|
|
40922
|
+
},
|
|
37918
40923
|
"embeddingEncoder.encode": {
|
|
37919
40924
|
capName: "embedding-encoder",
|
|
37920
40925
|
capScope: "system",
|
|
@@ -37933,6 +40938,18 @@ Object.freeze({
|
|
|
37933
40938
|
addonId: null,
|
|
37934
40939
|
access: "view"
|
|
37935
40940
|
},
|
|
40941
|
+
"enumSensor.getStatus": {
|
|
40942
|
+
capName: "enum-sensor",
|
|
40943
|
+
capScope: "device",
|
|
40944
|
+
addonId: null,
|
|
40945
|
+
access: "view"
|
|
40946
|
+
},
|
|
40947
|
+
"eventEmitter.getStatus": {
|
|
40948
|
+
capName: "event-emitter",
|
|
40949
|
+
capScope: "device",
|
|
40950
|
+
addonId: null,
|
|
40951
|
+
access: "view"
|
|
40952
|
+
},
|
|
37936
40953
|
"events.getEventClipUrl": {
|
|
37937
40954
|
capName: "events",
|
|
37938
40955
|
capScope: "device",
|
|
@@ -38053,6 +41070,12 @@ Object.freeze({
|
|
|
38053
41070
|
addonId: null,
|
|
38054
41071
|
access: "view"
|
|
38055
41072
|
},
|
|
41073
|
+
"fanControl.getStatus": {
|
|
41074
|
+
capName: "fan-control",
|
|
41075
|
+
capScope: "device",
|
|
41076
|
+
addonId: null,
|
|
41077
|
+
access: "view"
|
|
41078
|
+
},
|
|
38056
41079
|
"fanControl.setDirection": {
|
|
38057
41080
|
capName: "fan-control",
|
|
38058
41081
|
capScope: "device",
|
|
@@ -38077,6 +41100,12 @@ Object.freeze({
|
|
|
38077
41100
|
addonId: null,
|
|
38078
41101
|
access: "create"
|
|
38079
41102
|
},
|
|
41103
|
+
"featureProbe.getStatus": {
|
|
41104
|
+
capName: "feature-probe",
|
|
41105
|
+
capScope: "device",
|
|
41106
|
+
addonId: null,
|
|
41107
|
+
access: "view"
|
|
41108
|
+
},
|
|
38080
41109
|
"filesystemBrowse.browse": {
|
|
38081
41110
|
capName: "filesystem-browse",
|
|
38082
41111
|
capScope: "system",
|
|
@@ -38095,6 +41124,24 @@ Object.freeze({
|
|
|
38095
41124
|
addonId: null,
|
|
38096
41125
|
access: "view"
|
|
38097
41126
|
},
|
|
41127
|
+
"flood.getStatus": {
|
|
41128
|
+
capName: "flood",
|
|
41129
|
+
capScope: "device",
|
|
41130
|
+
addonId: null,
|
|
41131
|
+
access: "view"
|
|
41132
|
+
},
|
|
41133
|
+
"gas.getStatus": {
|
|
41134
|
+
capName: "gas",
|
|
41135
|
+
capScope: "device",
|
|
41136
|
+
addonId: null,
|
|
41137
|
+
access: "view"
|
|
41138
|
+
},
|
|
41139
|
+
"humidifier.getStatus": {
|
|
41140
|
+
capName: "humidifier",
|
|
41141
|
+
capScope: "device",
|
|
41142
|
+
addonId: null,
|
|
41143
|
+
access: "view"
|
|
41144
|
+
},
|
|
38098
41145
|
"humidifier.setMode": {
|
|
38099
41146
|
capName: "humidifier",
|
|
38100
41147
|
capScope: "device",
|
|
@@ -38113,12 +41160,30 @@ Object.freeze({
|
|
|
38113
41160
|
addonId: null,
|
|
38114
41161
|
access: "create"
|
|
38115
41162
|
},
|
|
41163
|
+
"humiditySensor.getStatus": {
|
|
41164
|
+
capName: "humidity-sensor",
|
|
41165
|
+
capScope: "device",
|
|
41166
|
+
addonId: null,
|
|
41167
|
+
access: "view"
|
|
41168
|
+
},
|
|
41169
|
+
"image.getStatus": {
|
|
41170
|
+
capName: "image",
|
|
41171
|
+
capScope: "device",
|
|
41172
|
+
addonId: null,
|
|
41173
|
+
access: "view"
|
|
41174
|
+
},
|
|
38116
41175
|
"imageSettings.getOptions": {
|
|
38117
41176
|
capName: "image-settings",
|
|
38118
41177
|
capScope: "device",
|
|
38119
41178
|
addonId: null,
|
|
38120
41179
|
access: "view"
|
|
38121
41180
|
},
|
|
41181
|
+
"imageSettings.getStatus": {
|
|
41182
|
+
capName: "image-settings",
|
|
41183
|
+
capScope: "device",
|
|
41184
|
+
addonId: null,
|
|
41185
|
+
access: "view"
|
|
41186
|
+
},
|
|
38122
41187
|
"imageSettings.setSettings": {
|
|
38123
41188
|
capName: "image-settings",
|
|
38124
41189
|
capScope: "device",
|
|
@@ -38191,6 +41256,12 @@ Object.freeze({
|
|
|
38191
41256
|
addonId: null,
|
|
38192
41257
|
access: "create"
|
|
38193
41258
|
},
|
|
41259
|
+
"intercom.getStatus": {
|
|
41260
|
+
capName: "intercom",
|
|
41261
|
+
capScope: "device",
|
|
41262
|
+
addonId: null,
|
|
41263
|
+
access: "view"
|
|
41264
|
+
},
|
|
38194
41265
|
"intercom.handleAnswer": {
|
|
38195
41266
|
capName: "intercom",
|
|
38196
41267
|
capScope: "device",
|
|
@@ -38227,6 +41298,12 @@ Object.freeze({
|
|
|
38227
41298
|
addonId: null,
|
|
38228
41299
|
access: "create"
|
|
38229
41300
|
},
|
|
41301
|
+
"lawnMowerControl.getStatus": {
|
|
41302
|
+
capName: "lawn-mower-control",
|
|
41303
|
+
capScope: "device",
|
|
41304
|
+
addonId: null,
|
|
41305
|
+
access: "view"
|
|
41306
|
+
},
|
|
38230
41307
|
"lawnMowerControl.pause": {
|
|
38231
41308
|
capName: "lawn-mower-control",
|
|
38232
41309
|
capScope: "device",
|
|
@@ -38509,6 +41586,12 @@ Object.freeze({
|
|
|
38509
41586
|
addonId: null,
|
|
38510
41587
|
access: "create"
|
|
38511
41588
|
},
|
|
41589
|
+
"lockControl.getStatus": {
|
|
41590
|
+
capName: "lock-control",
|
|
41591
|
+
capScope: "device",
|
|
41592
|
+
addonId: null,
|
|
41593
|
+
access: "view"
|
|
41594
|
+
},
|
|
38512
41595
|
"lockControl.lock": {
|
|
38513
41596
|
capName: "lock-control",
|
|
38514
41597
|
capScope: "device",
|
|
@@ -38557,6 +41640,12 @@ Object.freeze({
|
|
|
38557
41640
|
addonId: null,
|
|
38558
41641
|
access: "view"
|
|
38559
41642
|
},
|
|
41643
|
+
"mediaPlayer.getStatus": {
|
|
41644
|
+
capName: "media-player",
|
|
41645
|
+
capScope: "device",
|
|
41646
|
+
addonId: null,
|
|
41647
|
+
access: "view"
|
|
41648
|
+
},
|
|
38560
41649
|
"mediaPlayer.next": {
|
|
38561
41650
|
capName: "media-player",
|
|
38562
41651
|
capScope: "device",
|
|
@@ -38749,6 +41838,12 @@ Object.freeze({
|
|
|
38749
41838
|
addonId: null,
|
|
38750
41839
|
access: "create"
|
|
38751
41840
|
},
|
|
41841
|
+
"motion.getStatus": {
|
|
41842
|
+
capName: "motion",
|
|
41843
|
+
capScope: "device",
|
|
41844
|
+
addonId: null,
|
|
41845
|
+
access: "view"
|
|
41846
|
+
},
|
|
38752
41847
|
"motion.isDetected": {
|
|
38753
41848
|
capName: "motion",
|
|
38754
41849
|
capScope: "device",
|
|
@@ -38761,6 +41856,24 @@ Object.freeze({
|
|
|
38761
41856
|
addonId: null,
|
|
38762
41857
|
access: "create"
|
|
38763
41858
|
},
|
|
41859
|
+
"motionDetection.applyDeviceSettingsPatch": {
|
|
41860
|
+
capName: "motion-detection",
|
|
41861
|
+
capScope: "device",
|
|
41862
|
+
addonId: null,
|
|
41863
|
+
access: "create"
|
|
41864
|
+
},
|
|
41865
|
+
"motionDetection.getDeviceLiveContribution": {
|
|
41866
|
+
capName: "motion-detection",
|
|
41867
|
+
capScope: "device",
|
|
41868
|
+
addonId: null,
|
|
41869
|
+
access: "view"
|
|
41870
|
+
},
|
|
41871
|
+
"motionDetection.getDeviceSettingsContribution": {
|
|
41872
|
+
capName: "motion-detection",
|
|
41873
|
+
capScope: "device",
|
|
41874
|
+
addonId: null,
|
|
41875
|
+
access: "view"
|
|
41876
|
+
},
|
|
38764
41877
|
"motionDetection.removeCamera": {
|
|
38765
41878
|
capName: "motion-detection",
|
|
38766
41879
|
capScope: "device",
|
|
@@ -38773,6 +41886,12 @@ Object.freeze({
|
|
|
38773
41886
|
addonId: null,
|
|
38774
41887
|
access: "delete"
|
|
38775
41888
|
},
|
|
41889
|
+
"motionTrigger.getStatus": {
|
|
41890
|
+
capName: "motion-trigger",
|
|
41891
|
+
capScope: "device",
|
|
41892
|
+
addonId: null,
|
|
41893
|
+
access: "view"
|
|
41894
|
+
},
|
|
38776
41895
|
"motionTrigger.setMotionTrigger": {
|
|
38777
41896
|
capName: "motion-trigger",
|
|
38778
41897
|
capScope: "device",
|
|
@@ -38785,6 +41904,12 @@ Object.freeze({
|
|
|
38785
41904
|
addonId: null,
|
|
38786
41905
|
access: "view"
|
|
38787
41906
|
},
|
|
41907
|
+
"motionZones.getStatus": {
|
|
41908
|
+
capName: "motion-zones",
|
|
41909
|
+
capScope: "device",
|
|
41910
|
+
addonId: null,
|
|
41911
|
+
access: "view"
|
|
41912
|
+
},
|
|
38788
41913
|
"motionZones.setZone": {
|
|
38789
41914
|
capName: "motion-zones",
|
|
38790
41915
|
capScope: "device",
|
|
@@ -38845,6 +41970,12 @@ Object.freeze({
|
|
|
38845
41970
|
addonId: null,
|
|
38846
41971
|
access: "view"
|
|
38847
41972
|
},
|
|
41973
|
+
"nativeObjectDetection.getStatus": {
|
|
41974
|
+
capName: "native-object-detection",
|
|
41975
|
+
capScope: "device",
|
|
41976
|
+
addonId: null,
|
|
41977
|
+
access: "view"
|
|
41978
|
+
},
|
|
38848
41979
|
"nativeObjectDetection.setEnabled": {
|
|
38849
41980
|
capName: "native-object-detection",
|
|
38850
41981
|
capScope: "device",
|
|
@@ -38863,6 +41994,12 @@ Object.freeze({
|
|
|
38863
41994
|
addonId: null,
|
|
38864
41995
|
access: "view"
|
|
38865
41996
|
},
|
|
41997
|
+
"navigation.getStatus": {
|
|
41998
|
+
capName: "navigation",
|
|
41999
|
+
capScope: "device",
|
|
42000
|
+
addonId: null,
|
|
42001
|
+
access: "view"
|
|
42002
|
+
},
|
|
38866
42003
|
"navigation.goToPoint": {
|
|
38867
42004
|
capName: "navigation",
|
|
38868
42005
|
capScope: "device",
|
|
@@ -38947,6 +42084,12 @@ Object.freeze({
|
|
|
38947
42084
|
addonId: null,
|
|
38948
42085
|
access: "create"
|
|
38949
42086
|
},
|
|
42087
|
+
"networkLink.getStatus": {
|
|
42088
|
+
capName: "network-link",
|
|
42089
|
+
capScope: "device",
|
|
42090
|
+
addonId: null,
|
|
42091
|
+
access: "view"
|
|
42092
|
+
},
|
|
38950
42093
|
"networkQuality.getAllStats": {
|
|
38951
42094
|
capName: "network-quality",
|
|
38952
42095
|
capScope: "system",
|
|
@@ -39205,18 +42348,36 @@ Object.freeze({
|
|
|
39205
42348
|
addonId: null,
|
|
39206
42349
|
access: "create"
|
|
39207
42350
|
},
|
|
42351
|
+
"notifier.getStatus": {
|
|
42352
|
+
capName: "notifier",
|
|
42353
|
+
capScope: "device",
|
|
42354
|
+
addonId: null,
|
|
42355
|
+
access: "view"
|
|
42356
|
+
},
|
|
39208
42357
|
"notifier.send": {
|
|
39209
42358
|
capName: "notifier",
|
|
39210
42359
|
capScope: "device",
|
|
39211
42360
|
addonId: null,
|
|
39212
42361
|
access: "create"
|
|
39213
42362
|
},
|
|
42363
|
+
"numericSensor.getStatus": {
|
|
42364
|
+
capName: "numeric-sensor",
|
|
42365
|
+
capScope: "device",
|
|
42366
|
+
addonId: null,
|
|
42367
|
+
access: "view"
|
|
42368
|
+
},
|
|
39214
42369
|
"oauthIntegration.getDescriptor": {
|
|
39215
42370
|
capName: "oauth-integration",
|
|
39216
42371
|
capScope: "system",
|
|
39217
42372
|
addonId: null,
|
|
39218
42373
|
access: "view"
|
|
39219
42374
|
},
|
|
42375
|
+
"osd.getStatus": {
|
|
42376
|
+
capName: "osd",
|
|
42377
|
+
capScope: "device",
|
|
42378
|
+
addonId: null,
|
|
42379
|
+
access: "view"
|
|
42380
|
+
},
|
|
39220
42381
|
"osd.setOverlay": {
|
|
39221
42382
|
capName: "osd",
|
|
39222
42383
|
capScope: "device",
|
|
@@ -39289,6 +42450,12 @@ Object.freeze({
|
|
|
39289
42450
|
addonId: null,
|
|
39290
42451
|
access: "create"
|
|
39291
42452
|
},
|
|
42453
|
+
"petFeeder.getStatus": {
|
|
42454
|
+
capName: "pet-feeder",
|
|
42455
|
+
capScope: "device",
|
|
42456
|
+
addonId: null,
|
|
42457
|
+
access: "view"
|
|
42458
|
+
},
|
|
39292
42459
|
"petFeeder.markFoodReplenished": {
|
|
39293
42460
|
capName: "pet-feeder",
|
|
39294
42461
|
capScope: "device",
|
|
@@ -39331,6 +42498,12 @@ Object.freeze({
|
|
|
39331
42498
|
addonId: null,
|
|
39332
42499
|
access: "create"
|
|
39333
42500
|
},
|
|
42501
|
+
"pipelineAnalytics.applyDeviceSettingsPatch": {
|
|
42502
|
+
capName: "pipeline-analytics",
|
|
42503
|
+
capScope: "device",
|
|
42504
|
+
addonId: null,
|
|
42505
|
+
access: "create"
|
|
42506
|
+
},
|
|
39334
42507
|
"pipelineAnalytics.cancelRelocateMedia": {
|
|
39335
42508
|
capName: "pipeline-analytics",
|
|
39336
42509
|
capScope: "device",
|
|
@@ -39397,6 +42570,18 @@ Object.freeze({
|
|
|
39397
42570
|
addonId: null,
|
|
39398
42571
|
access: "view"
|
|
39399
42572
|
},
|
|
42573
|
+
"pipelineAnalytics.getDeviceLiveContribution": {
|
|
42574
|
+
capName: "pipeline-analytics",
|
|
42575
|
+
capScope: "device",
|
|
42576
|
+
addonId: null,
|
|
42577
|
+
access: "view"
|
|
42578
|
+
},
|
|
42579
|
+
"pipelineAnalytics.getDeviceSettingsContribution": {
|
|
42580
|
+
capName: "pipeline-analytics",
|
|
42581
|
+
capScope: "device",
|
|
42582
|
+
addonId: null,
|
|
42583
|
+
access: "view"
|
|
42584
|
+
},
|
|
39400
42585
|
"pipelineAnalytics.getEventDensity": {
|
|
39401
42586
|
capName: "pipeline-analytics",
|
|
39402
42587
|
capScope: "device",
|
|
@@ -39607,6 +42792,12 @@ Object.freeze({
|
|
|
39607
42792
|
addonId: null,
|
|
39608
42793
|
access: "view"
|
|
39609
42794
|
},
|
|
42795
|
+
"pipelineAnalytics.ownersWithMedia": {
|
|
42796
|
+
capName: "pipeline-analytics",
|
|
42797
|
+
capScope: "device",
|
|
42798
|
+
addonId: null,
|
|
42799
|
+
access: "view"
|
|
42800
|
+
},
|
|
39610
42801
|
"pipelineAnalytics.pauseForStorageMigration": {
|
|
39611
42802
|
capName: "pipeline-analytics",
|
|
39612
42803
|
capScope: "device",
|
|
@@ -39961,6 +43152,12 @@ Object.freeze({
|
|
|
39961
43152
|
addonId: null,
|
|
39962
43153
|
access: "view"
|
|
39963
43154
|
},
|
|
43155
|
+
"pipelineOrchestrator.applyDeviceSettingsPatch": {
|
|
43156
|
+
capName: "pipeline-orchestrator",
|
|
43157
|
+
capScope: "system",
|
|
43158
|
+
addonId: null,
|
|
43159
|
+
access: "create"
|
|
43160
|
+
},
|
|
39964
43161
|
"pipelineOrchestrator.assignAudio": {
|
|
39965
43162
|
capName: "pipeline-orchestrator",
|
|
39966
43163
|
capScope: "system",
|
|
@@ -40051,6 +43248,18 @@ Object.freeze({
|
|
|
40051
43248
|
addonId: null,
|
|
40052
43249
|
access: "view"
|
|
40053
43250
|
},
|
|
43251
|
+
"pipelineOrchestrator.getDeviceLiveContribution": {
|
|
43252
|
+
capName: "pipeline-orchestrator",
|
|
43253
|
+
capScope: "system",
|
|
43254
|
+
addonId: null,
|
|
43255
|
+
access: "view"
|
|
43256
|
+
},
|
|
43257
|
+
"pipelineOrchestrator.getDeviceSettingsContribution": {
|
|
43258
|
+
capName: "pipeline-orchestrator",
|
|
43259
|
+
capScope: "system",
|
|
43260
|
+
addonId: null,
|
|
43261
|
+
access: "view"
|
|
43262
|
+
},
|
|
40054
43263
|
"pipelineOrchestrator.getGlobalMetrics": {
|
|
40055
43264
|
capName: "pipeline-orchestrator",
|
|
40056
43265
|
capScope: "system",
|
|
@@ -40459,12 +43668,36 @@ Object.freeze({
|
|
|
40459
43668
|
addonId: null,
|
|
40460
43669
|
access: "view"
|
|
40461
43670
|
},
|
|
43671
|
+
"powerMeter.getStatus": {
|
|
43672
|
+
capName: "power-meter",
|
|
43673
|
+
capScope: "device",
|
|
43674
|
+
addonId: null,
|
|
43675
|
+
access: "view"
|
|
43676
|
+
},
|
|
43677
|
+
"presence.getStatus": {
|
|
43678
|
+
capName: "presence",
|
|
43679
|
+
capScope: "device",
|
|
43680
|
+
addonId: null,
|
|
43681
|
+
access: "view"
|
|
43682
|
+
},
|
|
43683
|
+
"pressureSensor.getStatus": {
|
|
43684
|
+
capName: "pressure-sensor",
|
|
43685
|
+
capScope: "device",
|
|
43686
|
+
addonId: null,
|
|
43687
|
+
access: "view"
|
|
43688
|
+
},
|
|
40462
43689
|
"privacyMask.getOptions": {
|
|
40463
43690
|
capName: "privacy-mask",
|
|
40464
43691
|
capScope: "device",
|
|
40465
43692
|
addonId: null,
|
|
40466
43693
|
access: "view"
|
|
40467
43694
|
},
|
|
43695
|
+
"privacyMask.getStatus": {
|
|
43696
|
+
capName: "privacy-mask",
|
|
43697
|
+
capScope: "device",
|
|
43698
|
+
addonId: null,
|
|
43699
|
+
access: "view"
|
|
43700
|
+
},
|
|
40468
43701
|
"privacyMask.setAudioEnabled": {
|
|
40469
43702
|
capName: "privacy-mask",
|
|
40470
43703
|
capScope: "device",
|
|
@@ -40525,6 +43758,12 @@ Object.freeze({
|
|
|
40525
43758
|
addonId: null,
|
|
40526
43759
|
access: "view"
|
|
40527
43760
|
},
|
|
43761
|
+
"ptz.getStatus": {
|
|
43762
|
+
capName: "ptz",
|
|
43763
|
+
capScope: "device",
|
|
43764
|
+
addonId: null,
|
|
43765
|
+
access: "view"
|
|
43766
|
+
},
|
|
40528
43767
|
"ptz.goHome": {
|
|
40529
43768
|
capName: "ptz",
|
|
40530
43769
|
capScope: "device",
|
|
@@ -40603,6 +43842,12 @@ Object.freeze({
|
|
|
40603
43842
|
addonId: null,
|
|
40604
43843
|
access: "create"
|
|
40605
43844
|
},
|
|
43845
|
+
"recording.applyDeviceSettingsPatch": {
|
|
43846
|
+
capName: "recording",
|
|
43847
|
+
capScope: "system",
|
|
43848
|
+
addonId: null,
|
|
43849
|
+
access: "create"
|
|
43850
|
+
},
|
|
40606
43851
|
"recording.cancelRelocateJob": {
|
|
40607
43852
|
capName: "recording",
|
|
40608
43853
|
capScope: "system",
|
|
@@ -40651,6 +43896,18 @@ Object.freeze({
|
|
|
40651
43896
|
addonId: null,
|
|
40652
43897
|
access: "view"
|
|
40653
43898
|
},
|
|
43899
|
+
"recording.getDeviceLiveContribution": {
|
|
43900
|
+
capName: "recording",
|
|
43901
|
+
capScope: "system",
|
|
43902
|
+
addonId: null,
|
|
43903
|
+
access: "view"
|
|
43904
|
+
},
|
|
43905
|
+
"recording.getDeviceSettingsContribution": {
|
|
43906
|
+
capName: "recording",
|
|
43907
|
+
capScope: "system",
|
|
43908
|
+
addonId: null,
|
|
43909
|
+
access: "view"
|
|
43910
|
+
},
|
|
40654
43911
|
"recording.getPlacement": {
|
|
40655
43912
|
capName: "recording",
|
|
40656
43913
|
capScope: "system",
|
|
@@ -40669,6 +43926,12 @@ Object.freeze({
|
|
|
40669
43926
|
addonId: null,
|
|
40670
43927
|
access: "view"
|
|
40671
43928
|
},
|
|
43929
|
+
"recording.getStatus": {
|
|
43930
|
+
capName: "recording",
|
|
43931
|
+
capScope: "system",
|
|
43932
|
+
addonId: null,
|
|
43933
|
+
access: "view"
|
|
43934
|
+
},
|
|
40672
43935
|
"recording.getStorageMigrationMoveStatus": {
|
|
40673
43936
|
capName: "recording",
|
|
40674
43937
|
capScope: "system",
|
|
@@ -40843,6 +44106,24 @@ Object.freeze({
|
|
|
40843
44106
|
addonId: null,
|
|
40844
44107
|
access: "view"
|
|
40845
44108
|
},
|
|
44109
|
+
"recordingOnboard.getOptions": {
|
|
44110
|
+
capName: "recording-onboard",
|
|
44111
|
+
capScope: "device",
|
|
44112
|
+
addonId: null,
|
|
44113
|
+
access: "view"
|
|
44114
|
+
},
|
|
44115
|
+
"recordingOnboard.getStatus": {
|
|
44116
|
+
capName: "recording-onboard",
|
|
44117
|
+
capScope: "device",
|
|
44118
|
+
addonId: null,
|
|
44119
|
+
access: "view"
|
|
44120
|
+
},
|
|
44121
|
+
"recordingOnboard.setSettings": {
|
|
44122
|
+
capName: "recording-onboard",
|
|
44123
|
+
capScope: "device",
|
|
44124
|
+
addonId: null,
|
|
44125
|
+
access: "create"
|
|
44126
|
+
},
|
|
40846
44127
|
"recordingSignal.getStatus": {
|
|
40847
44128
|
capName: "recording-signal",
|
|
40848
44129
|
capScope: "device",
|
|
@@ -40873,6 +44154,12 @@ Object.freeze({
|
|
|
40873
44154
|
addonId: null,
|
|
40874
44155
|
access: "delete"
|
|
40875
44156
|
},
|
|
44157
|
+
"sceneMonitor.getStatus": {
|
|
44158
|
+
capName: "scene-monitor",
|
|
44159
|
+
capScope: "device",
|
|
44160
|
+
addonId: null,
|
|
44161
|
+
access: "view"
|
|
44162
|
+
},
|
|
40876
44163
|
"sceneMonitor.listScenes": {
|
|
40877
44164
|
capName: "scene-monitor",
|
|
40878
44165
|
capScope: "device",
|
|
@@ -40903,6 +44190,12 @@ Object.freeze({
|
|
|
40903
44190
|
addonId: null,
|
|
40904
44191
|
access: "create"
|
|
40905
44192
|
},
|
|
44193
|
+
"scriptRunner.getStatus": {
|
|
44194
|
+
capName: "script-runner",
|
|
44195
|
+
capScope: "device",
|
|
44196
|
+
addonId: null,
|
|
44197
|
+
access: "view"
|
|
44198
|
+
},
|
|
40906
44199
|
"scriptRunner.run": {
|
|
40907
44200
|
capName: "script-runner",
|
|
40908
44201
|
capScope: "device",
|
|
@@ -41035,6 +44328,12 @@ Object.freeze({
|
|
|
41035
44328
|
addonId: null,
|
|
41036
44329
|
access: "create"
|
|
41037
44330
|
},
|
|
44331
|
+
"smoke.getStatus": {
|
|
44332
|
+
capName: "smoke",
|
|
44333
|
+
capScope: "device",
|
|
44334
|
+
addonId: null,
|
|
44335
|
+
access: "view"
|
|
44336
|
+
},
|
|
41038
44337
|
"smtpProvider.getStatus": {
|
|
41039
44338
|
capName: "smtp-provider",
|
|
41040
44339
|
capScope: "system",
|
|
@@ -41053,12 +44352,30 @@ Object.freeze({
|
|
|
41053
44352
|
addonId: null,
|
|
41054
44353
|
access: "view"
|
|
41055
44354
|
},
|
|
44355
|
+
"snapshot.applyDeviceSettingsPatch": {
|
|
44356
|
+
capName: "snapshot",
|
|
44357
|
+
capScope: "device",
|
|
44358
|
+
addonId: null,
|
|
44359
|
+
access: "create"
|
|
44360
|
+
},
|
|
41056
44361
|
"snapshot.getDebugState": {
|
|
41057
44362
|
capName: "snapshot",
|
|
41058
44363
|
capScope: "device",
|
|
41059
44364
|
addonId: null,
|
|
41060
44365
|
access: "view"
|
|
41061
44366
|
},
|
|
44367
|
+
"snapshot.getDeviceLiveContribution": {
|
|
44368
|
+
capName: "snapshot",
|
|
44369
|
+
capScope: "device",
|
|
44370
|
+
addonId: null,
|
|
44371
|
+
access: "view"
|
|
44372
|
+
},
|
|
44373
|
+
"snapshot.getDeviceSettingsContribution": {
|
|
44374
|
+
capName: "snapshot",
|
|
44375
|
+
capScope: "device",
|
|
44376
|
+
addonId: null,
|
|
44377
|
+
access: "view"
|
|
44378
|
+
},
|
|
41062
44379
|
"snapshot.getSnapshot": {
|
|
41063
44380
|
capName: "snapshot",
|
|
41064
44381
|
capScope: "device",
|
|
@@ -41077,6 +44394,12 @@ Object.freeze({
|
|
|
41077
44394
|
addonId: null,
|
|
41078
44395
|
access: "view"
|
|
41079
44396
|
},
|
|
44397
|
+
"snapshot.getStatus": {
|
|
44398
|
+
capName: "snapshot",
|
|
44399
|
+
capScope: "device",
|
|
44400
|
+
addonId: null,
|
|
44401
|
+
access: "view"
|
|
44402
|
+
},
|
|
41080
44403
|
"snapshot.invalidateCache": {
|
|
41081
44404
|
capName: "snapshot",
|
|
41082
44405
|
capScope: "device",
|
|
@@ -41413,6 +44736,12 @@ Object.freeze({
|
|
|
41413
44736
|
addonId: null,
|
|
41414
44737
|
access: "create"
|
|
41415
44738
|
},
|
|
44739
|
+
"streamBroker.applyDeviceSettingsPatch": {
|
|
44740
|
+
capName: "stream-broker",
|
|
44741
|
+
capScope: "system",
|
|
44742
|
+
addonId: null,
|
|
44743
|
+
access: "create"
|
|
44744
|
+
},
|
|
41416
44745
|
"streamBroker.assignProfile": {
|
|
41417
44746
|
capName: "stream-broker",
|
|
41418
44747
|
capScope: "system",
|
|
@@ -41449,6 +44778,18 @@ Object.freeze({
|
|
|
41449
44778
|
addonId: null,
|
|
41450
44779
|
access: "view"
|
|
41451
44780
|
},
|
|
44781
|
+
"streamBroker.getDeviceLiveContribution": {
|
|
44782
|
+
capName: "stream-broker",
|
|
44783
|
+
capScope: "system",
|
|
44784
|
+
addonId: null,
|
|
44785
|
+
access: "view"
|
|
44786
|
+
},
|
|
44787
|
+
"streamBroker.getDeviceSettingsContribution": {
|
|
44788
|
+
capName: "stream-broker",
|
|
44789
|
+
capScope: "system",
|
|
44790
|
+
addonId: null,
|
|
44791
|
+
access: "view"
|
|
44792
|
+
},
|
|
41452
44793
|
"streamBroker.getPreBufferInfo": {
|
|
41453
44794
|
capName: "stream-broker",
|
|
41454
44795
|
capScope: "system",
|
|
@@ -41647,6 +44988,12 @@ Object.freeze({
|
|
|
41647
44988
|
addonId: null,
|
|
41648
44989
|
access: "view"
|
|
41649
44990
|
},
|
|
44991
|
+
"streamParams.getStatus": {
|
|
44992
|
+
capName: "stream-params",
|
|
44993
|
+
capScope: "device",
|
|
44994
|
+
addonId: null,
|
|
44995
|
+
access: "view"
|
|
44996
|
+
},
|
|
41650
44997
|
"streamParams.setProfile": {
|
|
41651
44998
|
capName: "stream-params",
|
|
41652
44999
|
capScope: "device",
|
|
@@ -41659,6 +45006,12 @@ Object.freeze({
|
|
|
41659
45006
|
addonId: null,
|
|
41660
45007
|
access: "view"
|
|
41661
45008
|
},
|
|
45009
|
+
"switch.getStatus": {
|
|
45010
|
+
capName: "switch",
|
|
45011
|
+
capScope: "device",
|
|
45012
|
+
addonId: null,
|
|
45013
|
+
access: "view"
|
|
45014
|
+
},
|
|
41662
45015
|
"switch.setState": {
|
|
41663
45016
|
capName: "switch",
|
|
41664
45017
|
capScope: "device",
|
|
@@ -41755,6 +45108,18 @@ Object.freeze({
|
|
|
41755
45108
|
addonId: null,
|
|
41756
45109
|
access: "create"
|
|
41757
45110
|
},
|
|
45111
|
+
"tamper.getStatus": {
|
|
45112
|
+
capName: "tamper",
|
|
45113
|
+
capScope: "device",
|
|
45114
|
+
addonId: null,
|
|
45115
|
+
access: "view"
|
|
45116
|
+
},
|
|
45117
|
+
"temperatureSensor.getStatus": {
|
|
45118
|
+
capName: "temperature-sensor",
|
|
45119
|
+
capScope: "device",
|
|
45120
|
+
addonId: null,
|
|
45121
|
+
access: "view"
|
|
45122
|
+
},
|
|
41758
45123
|
"terminalSession.adoptLegacyMonitor": {
|
|
41759
45124
|
capName: "terminal-session",
|
|
41760
45125
|
capScope: "system",
|
|
@@ -41851,6 +45216,12 @@ Object.freeze({
|
|
|
41851
45216
|
addonId: null,
|
|
41852
45217
|
access: "view"
|
|
41853
45218
|
},
|
|
45219
|
+
"update.getStatus": {
|
|
45220
|
+
capName: "update",
|
|
45221
|
+
capScope: "device",
|
|
45222
|
+
addonId: null,
|
|
45223
|
+
access: "view"
|
|
45224
|
+
},
|
|
41854
45225
|
"update.installUpdate": {
|
|
41855
45226
|
capName: "update",
|
|
41856
45227
|
capScope: "device",
|
|
@@ -42073,6 +45444,12 @@ Object.freeze({
|
|
|
42073
45444
|
addonId: null,
|
|
42074
45445
|
access: "create"
|
|
42075
45446
|
},
|
|
45447
|
+
"vacuumControl.getStatus": {
|
|
45448
|
+
capName: "vacuum-control",
|
|
45449
|
+
capScope: "device",
|
|
45450
|
+
addonId: null,
|
|
45451
|
+
access: "view"
|
|
45452
|
+
},
|
|
42076
45453
|
"vacuumControl.locate": {
|
|
42077
45454
|
capName: "vacuum-control",
|
|
42078
45455
|
capScope: "device",
|
|
@@ -42115,6 +45492,12 @@ Object.freeze({
|
|
|
42115
45492
|
addonId: null,
|
|
42116
45493
|
access: "create"
|
|
42117
45494
|
},
|
|
45495
|
+
"valve.getStatus": {
|
|
45496
|
+
capName: "valve",
|
|
45497
|
+
capScope: "device",
|
|
45498
|
+
addonId: null,
|
|
45499
|
+
access: "view"
|
|
45500
|
+
},
|
|
42118
45501
|
"valve.open": {
|
|
42119
45502
|
capName: "valve",
|
|
42120
45503
|
capScope: "device",
|
|
@@ -42187,6 +45570,18 @@ Object.freeze({
|
|
|
42187
45570
|
addonId: null,
|
|
42188
45571
|
access: "create"
|
|
42189
45572
|
},
|
|
45573
|
+
"vibration.getStatus": {
|
|
45574
|
+
capName: "vibration",
|
|
45575
|
+
capScope: "device",
|
|
45576
|
+
addonId: null,
|
|
45577
|
+
access: "view"
|
|
45578
|
+
},
|
|
45579
|
+
"videoclips.dialClipStream": {
|
|
45580
|
+
capName: "videoclips",
|
|
45581
|
+
capScope: "device",
|
|
45582
|
+
addonId: null,
|
|
45583
|
+
access: "view"
|
|
45584
|
+
},
|
|
42190
45585
|
"videoclips.getClipPlayback": {
|
|
42191
45586
|
capName: "videoclips",
|
|
42192
45587
|
capScope: "device",
|
|
@@ -42199,6 +45594,18 @@ Object.freeze({
|
|
|
42199
45594
|
addonId: null,
|
|
42200
45595
|
access: "view"
|
|
42201
45596
|
},
|
|
45597
|
+
"videoclips.listSources": {
|
|
45598
|
+
capName: "videoclips",
|
|
45599
|
+
capScope: "device",
|
|
45600
|
+
addonId: null,
|
|
45601
|
+
access: "view"
|
|
45602
|
+
},
|
|
45603
|
+
"videoclips.readClipBytes": {
|
|
45604
|
+
capName: "videoclips",
|
|
45605
|
+
capScope: "device",
|
|
45606
|
+
addonId: null,
|
|
45607
|
+
access: "view"
|
|
45608
|
+
},
|
|
42202
45609
|
"viewerUi.getStaticDir": {
|
|
42203
45610
|
capName: "viewer-ui",
|
|
42204
45611
|
capScope: "system",
|
|
@@ -42211,6 +45618,12 @@ Object.freeze({
|
|
|
42211
45618
|
addonId: null,
|
|
42212
45619
|
access: "view"
|
|
42213
45620
|
},
|
|
45621
|
+
"waterHeater.getStatus": {
|
|
45622
|
+
capName: "water-heater",
|
|
45623
|
+
capScope: "device",
|
|
45624
|
+
addonId: null,
|
|
45625
|
+
access: "view"
|
|
45626
|
+
},
|
|
42214
45627
|
"waterHeater.setAway": {
|
|
42215
45628
|
capName: "water-heater",
|
|
42216
45629
|
capScope: "device",
|
|
@@ -42229,6 +45642,12 @@ Object.freeze({
|
|
|
42229
45642
|
addonId: null,
|
|
42230
45643
|
access: "create"
|
|
42231
45644
|
},
|
|
45645
|
+
"weather.getStatus": {
|
|
45646
|
+
capName: "weather",
|
|
45647
|
+
capScope: "device",
|
|
45648
|
+
addonId: null,
|
|
45649
|
+
access: "view"
|
|
45650
|
+
},
|
|
42232
45651
|
"webrtcSession.addIceCandidate": {
|
|
42233
45652
|
capName: "webrtc-session",
|
|
42234
45653
|
capScope: "device",
|
|
@@ -42253,6 +45672,12 @@ Object.freeze({
|
|
|
42253
45672
|
addonId: null,
|
|
42254
45673
|
access: "view"
|
|
42255
45674
|
},
|
|
45675
|
+
"webrtcSession.getSessionDebug": {
|
|
45676
|
+
capName: "webrtc-session",
|
|
45677
|
+
capScope: "device",
|
|
45678
|
+
addonId: null,
|
|
45679
|
+
access: "view"
|
|
45680
|
+
},
|
|
42256
45681
|
"webrtcSession.getSessionState": {
|
|
42257
45682
|
capName: "webrtc-session",
|
|
42258
45683
|
capScope: "device",
|
|
@@ -42585,6 +46010,11 @@ Object.freeze({
|
|
|
42585
46010
|
form: "single",
|
|
42586
46011
|
optional: true
|
|
42587
46012
|
}],
|
|
46013
|
+
"deviceAdminLink.getAdminLink": [{
|
|
46014
|
+
name: "deviceId",
|
|
46015
|
+
form: "single",
|
|
46016
|
+
optional: false
|
|
46017
|
+
}],
|
|
42588
46018
|
"deviceAdoption.release": [{
|
|
42589
46019
|
name: "camDeviceId",
|
|
42590
46020
|
form: "single",
|
|
@@ -43495,6 +46925,11 @@ Object.freeze({
|
|
|
43495
46925
|
form: "single",
|
|
43496
46926
|
optional: false
|
|
43497
46927
|
}],
|
|
46928
|
+
"pipelineAnalytics.ownersWithMedia": [{
|
|
46929
|
+
name: "deviceId",
|
|
46930
|
+
form: "single",
|
|
46931
|
+
optional: false
|
|
46932
|
+
}],
|
|
43498
46933
|
"pipelineAnalytics.proposeRetrainAnnotations": [{
|
|
43499
46934
|
name: "deviceId",
|
|
43500
46935
|
form: "single",
|
|
@@ -43965,6 +47400,16 @@ Object.freeze({
|
|
|
43965
47400
|
form: "single",
|
|
43966
47401
|
optional: true
|
|
43967
47402
|
}],
|
|
47403
|
+
"recordingOnboard.getOptions": [{
|
|
47404
|
+
name: "deviceId",
|
|
47405
|
+
form: "single",
|
|
47406
|
+
optional: false
|
|
47407
|
+
}],
|
|
47408
|
+
"recordingOnboard.setSettings": [{
|
|
47409
|
+
name: "deviceId",
|
|
47410
|
+
form: "single",
|
|
47411
|
+
optional: false
|
|
47412
|
+
}],
|
|
43968
47413
|
"recordingSignal.getStatus": [{
|
|
43969
47414
|
name: "deviceId",
|
|
43970
47415
|
form: "single",
|
|
@@ -44191,6 +47636,11 @@ Object.freeze({
|
|
|
44191
47636
|
form: "single",
|
|
44192
47637
|
optional: false
|
|
44193
47638
|
}],
|
|
47639
|
+
"videoclips.dialClipStream": [{
|
|
47640
|
+
name: "deviceId",
|
|
47641
|
+
form: "single",
|
|
47642
|
+
optional: false
|
|
47643
|
+
}],
|
|
44194
47644
|
"videoclips.getClipPlayback": [{
|
|
44195
47645
|
name: "deviceId",
|
|
44196
47646
|
form: "single",
|
|
@@ -44201,6 +47651,16 @@ Object.freeze({
|
|
|
44201
47651
|
form: "single",
|
|
44202
47652
|
optional: false
|
|
44203
47653
|
}],
|
|
47654
|
+
"videoclips.listSources": [{
|
|
47655
|
+
name: "deviceId",
|
|
47656
|
+
form: "single",
|
|
47657
|
+
optional: false
|
|
47658
|
+
}],
|
|
47659
|
+
"videoclips.readClipBytes": [{
|
|
47660
|
+
name: "deviceId",
|
|
47661
|
+
form: "single",
|
|
47662
|
+
optional: false
|
|
47663
|
+
}],
|
|
44204
47664
|
"waterHeater.setAway": [{
|
|
44205
47665
|
name: "deviceId",
|
|
44206
47666
|
form: "single",
|
|
@@ -44236,6 +47696,11 @@ Object.freeze({
|
|
|
44236
47696
|
form: "single",
|
|
44237
47697
|
optional: false
|
|
44238
47698
|
}],
|
|
47699
|
+
"webrtcSession.getSessionDebug": [{
|
|
47700
|
+
name: "deviceId",
|
|
47701
|
+
form: "single",
|
|
47702
|
+
optional: false
|
|
47703
|
+
}],
|
|
44239
47704
|
"webrtcSession.getSessionState": [{
|
|
44240
47705
|
name: "deviceId",
|
|
44241
47706
|
form: "single",
|
|
@@ -45063,6 +48528,154 @@ function buildControllerFormSchema() {
|
|
|
45063
48528
|
}] };
|
|
45064
48529
|
}
|
|
45065
48530
|
//#endregion
|
|
48531
|
+
//#region src/unifi-node-resolver.ts
|
|
48532
|
+
/**
|
|
48533
|
+
* Per-controller registry that device classes use to reach the live
|
|
48534
|
+
* {@link UnifiIntegrationManager} (and its infra + client snapshots) for a given
|
|
48535
|
+
* controller. Keyed on the controller
|
|
48536
|
+
* {@link import('@camstack/types').DeviceType.Hub} device's `stableId`
|
|
48537
|
+
* (standalone mode — the connection lives on the controller device, not a
|
|
48538
|
+
* broker). Mirrors the Velux `VeluxNodeResolver` / Ecowitt `EcowittFacadeResolver`.
|
|
48539
|
+
*
|
|
48540
|
+
* The controller device recreates the manager on reconnect / settings change and
|
|
48541
|
+
* re-publishes it here; infra + client children always read through the resolver,
|
|
48542
|
+
* so they transparently pick up the new manager.
|
|
48543
|
+
*/
|
|
48544
|
+
var UnifiControllerResolver = class {
|
|
48545
|
+
#managers = /* @__PURE__ */ new Map();
|
|
48546
|
+
/**
|
|
48547
|
+
* Per-controller poll-tick fan-out. Children subscribe here instead of
|
|
48548
|
+
* attaching a listener per child — the controller device pumps this bus once
|
|
48549
|
+
* per poll cycle. A station with 100+ clients would otherwise trip Node's
|
|
48550
|
+
* default MaxListeners cap; a plain `Set` of callbacks has no listener limit.
|
|
48551
|
+
*/
|
|
48552
|
+
#snapshotSubs = /* @__PURE__ */ new Map();
|
|
48553
|
+
/** Publish or remove the manager for a controller id. `null` removes the entry. */
|
|
48554
|
+
set(controllerId, manager) {
|
|
48555
|
+
if (manager === null) {
|
|
48556
|
+
this.#managers.delete(controllerId);
|
|
48557
|
+
return;
|
|
48558
|
+
}
|
|
48559
|
+
this.#managers.set(controllerId, manager);
|
|
48560
|
+
}
|
|
48561
|
+
/** The live manager for a controller id, or null when unknown / stopped. */
|
|
48562
|
+
getManager(controllerId) {
|
|
48563
|
+
return this.#managers.get(controllerId) ?? null;
|
|
48564
|
+
}
|
|
48565
|
+
/** All infra snapshots for a controller id, or [] when unknown. */
|
|
48566
|
+
getInfraList(controllerId) {
|
|
48567
|
+
return this.#managers.get(controllerId)?.getInfraList() ?? [];
|
|
48568
|
+
}
|
|
48569
|
+
/** Resolve a single infra snapshot for a controller, or null. */
|
|
48570
|
+
getInfra(controllerId, id) {
|
|
48571
|
+
return this.#managers.get(controllerId)?.getInfra(id) ?? null;
|
|
48572
|
+
}
|
|
48573
|
+
/** All client snapshots for a controller id, or [] when unknown. */
|
|
48574
|
+
getClientList(controllerId) {
|
|
48575
|
+
return this.#managers.get(controllerId)?.getClientList() ?? [];
|
|
48576
|
+
}
|
|
48577
|
+
/** Resolve a single client snapshot for a controller, or null. */
|
|
48578
|
+
getClient(controllerId, mac) {
|
|
48579
|
+
return this.#managers.get(controllerId)?.getClient(mac) ?? null;
|
|
48580
|
+
}
|
|
48581
|
+
/** Subscribe a child to per-controller poll ticks. Returns an unsubscribe fn. */
|
|
48582
|
+
onSnapshot(controllerId, cb) {
|
|
48583
|
+
let set = this.#snapshotSubs.get(controllerId);
|
|
48584
|
+
if (!set) {
|
|
48585
|
+
set = /* @__PURE__ */ new Set();
|
|
48586
|
+
this.#snapshotSubs.set(controllerId, set);
|
|
48587
|
+
}
|
|
48588
|
+
set.add(cb);
|
|
48589
|
+
return () => {
|
|
48590
|
+
const current = this.#snapshotSubs.get(controllerId);
|
|
48591
|
+
current?.delete(cb);
|
|
48592
|
+
if (current && current.size === 0) this.#snapshotSubs.delete(controllerId);
|
|
48593
|
+
};
|
|
48594
|
+
}
|
|
48595
|
+
/** Fan a single poll tick out to every subscribed child for a controller. */
|
|
48596
|
+
emitSnapshot(controllerId) {
|
|
48597
|
+
const set = this.#snapshotSubs.get(controllerId);
|
|
48598
|
+
if (!set) return;
|
|
48599
|
+
for (const cb of set) try {
|
|
48600
|
+
cb();
|
|
48601
|
+
} catch {}
|
|
48602
|
+
}
|
|
48603
|
+
/** Remove all registered managers (called on full shutdown). */
|
|
48604
|
+
clear() {
|
|
48605
|
+
this.#managers.clear();
|
|
48606
|
+
this.#snapshotSubs.clear();
|
|
48607
|
+
}
|
|
48608
|
+
};
|
|
48609
|
+
/** The single in-process per-controller manager resolver shared between the
|
|
48610
|
+
* controller device and its infra / client children. */
|
|
48611
|
+
var unifiControllers = new UnifiControllerResolver();
|
|
48612
|
+
//#endregion
|
|
48613
|
+
//#region src/unifi-admin-link.ts
|
|
48614
|
+
/**
|
|
48615
|
+
* `device-admin-link` for UniFi-backed devices.
|
|
48616
|
+
*
|
|
48617
|
+
* A UniFi access point or switch has no management page of its own — it is
|
|
48618
|
+
* adopted by, and configured from, the controller. So every device this addon
|
|
48619
|
+
* owns (the controller Hub, an infra Container, a port/feature child) resolves
|
|
48620
|
+
* to the CONTROLLER's UI, `target: 'integration'`, labelled as such so an
|
|
48621
|
+
* operator knows before clicking that he is not landing on the AP.
|
|
48622
|
+
*
|
|
48623
|
+
* ## What is known, and the one thing that is not
|
|
48624
|
+
*
|
|
48625
|
+
* `host` and `port` are the operator's own controller settings — `port` is
|
|
48626
|
+
* described in the schema as "Controller HTTPS port (443 on UniFi OS, 8443 on
|
|
48627
|
+
* legacy)", so it IS the UI's port. There is no `https` boolean because UniFi
|
|
48628
|
+
* has no plaintext mode; https is a fact about the product, not an assumption.
|
|
48629
|
+
*
|
|
48630
|
+
* What is NOT known is a per-device deep link. `UnifiInfraSnapshot` carries no
|
|
48631
|
+
* `ip` (id, name, model, cpu, clients, state, uptime, ports, led, online — that
|
|
48632
|
+
* is the whole shape), and the controller's own per-device route embeds the
|
|
48633
|
+
* site slug and a UI-version-dependent path. Inventing one would produce a
|
|
48634
|
+
* button that lands on a redirect-to-dashboard at best and a 404 after the next
|
|
48635
|
+
* controller upgrade. The controller root is what this provider can honestly
|
|
48636
|
+
* say, so it is what it says.
|
|
48637
|
+
*
|
|
48638
|
+
* `apiKey` / `username` / `password` sit in the same connection object and
|
|
48639
|
+
* never travel: `buildDeviceAdminUrl` has no parameter they could ride in.
|
|
48640
|
+
*/
|
|
48641
|
+
function registerUnifiAdminLink(ctx, deviceId, source) {
|
|
48642
|
+
ctx.registerNativeCap(deviceAdminLinkCapability, { getAdminLink: async (input) => {
|
|
48643
|
+
if (input.deviceId !== deviceId) throw new Error(`UnifiAdminLink: deviceId mismatch, expected ${deviceId}, got ${input.deviceId}`);
|
|
48644
|
+
let endpoint;
|
|
48645
|
+
if (source.kind === "own") endpoint = source.read();
|
|
48646
|
+
else {
|
|
48647
|
+
const manager = unifiControllers.getManager(source.controllerId);
|
|
48648
|
+
if (manager === null) {
|
|
48649
|
+
ctx.logger.debug("unifi admin link unavailable: no live manager", {
|
|
48650
|
+
tags: { deviceId },
|
|
48651
|
+
meta: { controllerId: source.controllerId }
|
|
48652
|
+
});
|
|
48653
|
+
return null;
|
|
48654
|
+
}
|
|
48655
|
+
endpoint = manager.getConnection();
|
|
48656
|
+
}
|
|
48657
|
+
const built = buildDeviceAdminUrl({
|
|
48658
|
+
host: endpoint.host,
|
|
48659
|
+
port: endpoint.port,
|
|
48660
|
+
scheme: "https",
|
|
48661
|
+
path: "/"
|
|
48662
|
+
});
|
|
48663
|
+
if (!built.ok) {
|
|
48664
|
+
ctx.logger.debug("unifi admin link unavailable", {
|
|
48665
|
+
tags: { deviceId },
|
|
48666
|
+
meta: { reason: built.reason }
|
|
48667
|
+
});
|
|
48668
|
+
return null;
|
|
48669
|
+
}
|
|
48670
|
+
return {
|
|
48671
|
+
url: built.url,
|
|
48672
|
+
label: "UniFi controller",
|
|
48673
|
+
target: "integration",
|
|
48674
|
+
host: endpoint.host
|
|
48675
|
+
};
|
|
48676
|
+
} });
|
|
48677
|
+
}
|
|
48678
|
+
//#endregion
|
|
45066
48679
|
//#region src/unifi-discovery.ts
|
|
45067
48680
|
/**
|
|
45068
48681
|
* The `childNativeId` an infra Container candidate is keyed on. The controller
|
|
@@ -45269,88 +48882,6 @@ function featuresEqual(a, b) {
|
|
|
45269
48882
|
return true;
|
|
45270
48883
|
}
|
|
45271
48884
|
//#endregion
|
|
45272
|
-
//#region src/unifi-node-resolver.ts
|
|
45273
|
-
/**
|
|
45274
|
-
* Per-controller registry that device classes use to reach the live
|
|
45275
|
-
* {@link UnifiIntegrationManager} (and its infra + client snapshots) for a given
|
|
45276
|
-
* controller. Keyed on the controller
|
|
45277
|
-
* {@link import('@camstack/types').DeviceType.Hub} device's `stableId`
|
|
45278
|
-
* (standalone mode — the connection lives on the controller device, not a
|
|
45279
|
-
* broker). Mirrors the Velux `VeluxNodeResolver` / Ecowitt `EcowittFacadeResolver`.
|
|
45280
|
-
*
|
|
45281
|
-
* The controller device recreates the manager on reconnect / settings change and
|
|
45282
|
-
* re-publishes it here; infra + client children always read through the resolver,
|
|
45283
|
-
* so they transparently pick up the new manager.
|
|
45284
|
-
*/
|
|
45285
|
-
var UnifiControllerResolver = class {
|
|
45286
|
-
#managers = /* @__PURE__ */ new Map();
|
|
45287
|
-
/**
|
|
45288
|
-
* Per-controller poll-tick fan-out. Children subscribe here instead of
|
|
45289
|
-
* attaching a listener per child — the controller device pumps this bus once
|
|
45290
|
-
* per poll cycle. A station with 100+ clients would otherwise trip Node's
|
|
45291
|
-
* default MaxListeners cap; a plain `Set` of callbacks has no listener limit.
|
|
45292
|
-
*/
|
|
45293
|
-
#snapshotSubs = /* @__PURE__ */ new Map();
|
|
45294
|
-
/** Publish or remove the manager for a controller id. `null` removes the entry. */
|
|
45295
|
-
set(controllerId, manager) {
|
|
45296
|
-
if (manager === null) {
|
|
45297
|
-
this.#managers.delete(controllerId);
|
|
45298
|
-
return;
|
|
45299
|
-
}
|
|
45300
|
-
this.#managers.set(controllerId, manager);
|
|
45301
|
-
}
|
|
45302
|
-
/** The live manager for a controller id, or null when unknown / stopped. */
|
|
45303
|
-
getManager(controllerId) {
|
|
45304
|
-
return this.#managers.get(controllerId) ?? null;
|
|
45305
|
-
}
|
|
45306
|
-
/** All infra snapshots for a controller id, or [] when unknown. */
|
|
45307
|
-
getInfraList(controllerId) {
|
|
45308
|
-
return this.#managers.get(controllerId)?.getInfraList() ?? [];
|
|
45309
|
-
}
|
|
45310
|
-
/** Resolve a single infra snapshot for a controller, or null. */
|
|
45311
|
-
getInfra(controllerId, id) {
|
|
45312
|
-
return this.#managers.get(controllerId)?.getInfra(id) ?? null;
|
|
45313
|
-
}
|
|
45314
|
-
/** All client snapshots for a controller id, or [] when unknown. */
|
|
45315
|
-
getClientList(controllerId) {
|
|
45316
|
-
return this.#managers.get(controllerId)?.getClientList() ?? [];
|
|
45317
|
-
}
|
|
45318
|
-
/** Resolve a single client snapshot for a controller, or null. */
|
|
45319
|
-
getClient(controllerId, mac) {
|
|
45320
|
-
return this.#managers.get(controllerId)?.getClient(mac) ?? null;
|
|
45321
|
-
}
|
|
45322
|
-
/** Subscribe a child to per-controller poll ticks. Returns an unsubscribe fn. */
|
|
45323
|
-
onSnapshot(controllerId, cb) {
|
|
45324
|
-
let set = this.#snapshotSubs.get(controllerId);
|
|
45325
|
-
if (!set) {
|
|
45326
|
-
set = /* @__PURE__ */ new Set();
|
|
45327
|
-
this.#snapshotSubs.set(controllerId, set);
|
|
45328
|
-
}
|
|
45329
|
-
set.add(cb);
|
|
45330
|
-
return () => {
|
|
45331
|
-
const current = this.#snapshotSubs.get(controllerId);
|
|
45332
|
-
current?.delete(cb);
|
|
45333
|
-
if (current && current.size === 0) this.#snapshotSubs.delete(controllerId);
|
|
45334
|
-
};
|
|
45335
|
-
}
|
|
45336
|
-
/** Fan a single poll tick out to every subscribed child for a controller. */
|
|
45337
|
-
emitSnapshot(controllerId) {
|
|
45338
|
-
const set = this.#snapshotSubs.get(controllerId);
|
|
45339
|
-
if (!set) return;
|
|
45340
|
-
for (const cb of set) try {
|
|
45341
|
-
cb();
|
|
45342
|
-
} catch {}
|
|
45343
|
-
}
|
|
45344
|
-
/** Remove all registered managers (called on full shutdown). */
|
|
45345
|
-
clear() {
|
|
45346
|
-
this.#managers.clear();
|
|
45347
|
-
this.#snapshotSubs.clear();
|
|
45348
|
-
}
|
|
45349
|
-
};
|
|
45350
|
-
/** The single in-process per-controller manager resolver shared between the
|
|
45351
|
-
* controller device and its infra / client children. */
|
|
45352
|
-
var unifiControllers = new UnifiControllerResolver();
|
|
45353
|
-
//#endregion
|
|
45354
48885
|
//#region src/unifi-integration-manager.ts
|
|
45355
48886
|
/**
|
|
45356
48887
|
* Default client factory — INTENTIONALLY unimplemented. No UniFi controller-API
|
|
@@ -45575,6 +49106,10 @@ var UnifiFeatureDevice = class extends BaseDevice {
|
|
|
45575
49106
|
this.kind = cfg.kind;
|
|
45576
49107
|
this.controllerId = cfg.controllerId;
|
|
45577
49108
|
this.infraNativeId = cfg.infraNativeId;
|
|
49109
|
+
registerUnifiAdminLink(ctx, this.id, {
|
|
49110
|
+
kind: "controller",
|
|
49111
|
+
controllerId: this.controllerId
|
|
49112
|
+
});
|
|
45578
49113
|
this.switchFeature = cfg.switchFeature;
|
|
45579
49114
|
this.buttonAction = cfg.buttonAction;
|
|
45580
49115
|
this.portIdx = cfg.portIdx;
|
|
@@ -45802,6 +49337,10 @@ var UnifiInfraDevice = class extends BaseDevice {
|
|
|
45802
49337
|
super(ctx, unifiInfraSchema, { type: DeviceType.Container });
|
|
45803
49338
|
this.controllerId = cfg.controllerId;
|
|
45804
49339
|
this.infraNativeId = cfg.infraNativeId;
|
|
49340
|
+
registerUnifiAdminLink(ctx, this.id, {
|
|
49341
|
+
kind: "controller",
|
|
49342
|
+
controllerId: this.controllerId
|
|
49343
|
+
});
|
|
45805
49344
|
this.online = unifiControllers.getInfra(this.controllerId, this.infraNativeId)?.online ?? false;
|
|
45806
49345
|
this.updateSourceInfo({
|
|
45807
49346
|
id: `${this.controllerId}:${this.infraNativeId}`,
|
|
@@ -45867,6 +49406,10 @@ var UnifiControllerDevice = class extends BaseDevice {
|
|
|
45867
49406
|
super(ctx, unifiControllerSchema, { type: DeviceType.Hub });
|
|
45868
49407
|
this.online = false;
|
|
45869
49408
|
this.registerDeviceDiscovery();
|
|
49409
|
+
registerUnifiAdminLink(ctx, this.id, {
|
|
49410
|
+
kind: "own",
|
|
49411
|
+
read: () => this.config.values.connection
|
|
49412
|
+
});
|
|
45870
49413
|
this.updateSourceInfo({
|
|
45871
49414
|
id: this.stableId,
|
|
45872
49415
|
system: "unifi"
|
|
@@ -46281,4 +49824,4 @@ function readConnection(config) {
|
|
|
46281
49824
|
return parsed.success ? parsed.data : null;
|
|
46282
49825
|
}
|
|
46283
49826
|
//#endregion
|
|
46284
|
-
export { unifiControllerSchema as C, unifiInfraSchema as E, unifiConnectionSchema as S, unifiFeatureSchema as T, UnifiProviderAddon, UnifiSensorMetricSchema as _, defaultUnifiClientFactory as a, settingsToConnection as b,
|
|
49827
|
+
export { unifiControllerSchema as C, unifiInfraSchema as E, unifiConnectionSchema as S, unifiFeatureSchema as T, UnifiProviderAddon, UnifiSensorMetricSchema as _, defaultUnifiClientFactory as a, settingsToConnection as b, featuresEqual as c, infraNativeId as d, parseNativeId as f, UnifiFeatureKindSchema as g, UnifiButtonActionSchema as h, UnifiIntegrationManager as i, buildDiscoveryCandidates as l, unifiControllers as m, UnifiInfraDevice as n, buildInfraFeatures as o, UnifiControllerResolver as p, UnifiFeatureDevice as r, capForMetric as s, UnifiControllerDevice as t, clientNativeId as u, UnifiSwitchFeatureSchema as v, unifiFeatureDescriptorSchema as w, slugifyHost as x, buildControllerFormSchema as y };
|