@camstack/addon-export-hap 1.2.26 → 1.2.28
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/hap-export.addon.js +2793 -301
- package/dist/hap-export.addon.mjs +2793 -301
- package/package.json +3 -4
package/dist/hap-export.addon.js
CHANGED
|
@@ -3082,6 +3082,9 @@ function handlePipeResult(left, next, ctx) {
|
|
|
3082
3082
|
fallback: left.fallback
|
|
3083
3083
|
}, ctx);
|
|
3084
3084
|
}
|
|
3085
|
+
var $ZodPreprocess = /*@__PURE__*/ $constructor("$ZodPreprocess", (inst, def) => {
|
|
3086
|
+
$ZodPipe.init(inst, def);
|
|
3087
|
+
});
|
|
3085
3088
|
var $ZodReadonly = /*@__PURE__*/ $constructor("$ZodReadonly", (inst, def) => {
|
|
3086
3089
|
$ZodType.init(inst, def);
|
|
3087
3090
|
defineLazy(inst._zod, "propValues", () => def.innerType._zod.propValues);
|
|
@@ -5267,6 +5270,10 @@ function pipe(in_, out) {
|
|
|
5267
5270
|
out
|
|
5268
5271
|
});
|
|
5269
5272
|
}
|
|
5273
|
+
var ZodPreprocess = /*@__PURE__*/ $constructor("ZodPreprocess", (inst, def) => {
|
|
5274
|
+
ZodPipe.init(inst, def);
|
|
5275
|
+
$ZodPreprocess.init(inst, def);
|
|
5276
|
+
});
|
|
5270
5277
|
var ZodReadonly = /*@__PURE__*/ $constructor("ZodReadonly", (inst, def) => {
|
|
5271
5278
|
$ZodReadonly.init(inst, def);
|
|
5272
5279
|
ZodType.init(inst, def);
|
|
@@ -5325,6 +5332,13 @@ function _instanceof(cls, params = {}) {
|
|
|
5325
5332
|
};
|
|
5326
5333
|
return inst;
|
|
5327
5334
|
}
|
|
5335
|
+
function preprocess(fn, schema) {
|
|
5336
|
+
return new ZodPreprocess({
|
|
5337
|
+
type: "pipe",
|
|
5338
|
+
in: transform(fn),
|
|
5339
|
+
out: schema
|
|
5340
|
+
});
|
|
5341
|
+
}
|
|
5328
5342
|
//#endregion
|
|
5329
5343
|
//#region ../../node_modules/zod/v4/classic/compat.js
|
|
5330
5344
|
/** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
|
|
@@ -7601,7 +7615,7 @@ import { errMsg } from '@camstack/types'
|
|
|
7601
7615
|
* Extract a human-readable message from an unknown error value.
|
|
7602
7616
|
* Replaces the ubiquitous `errMsg(err)` pattern.
|
|
7603
7617
|
*/
|
|
7604
|
-
function errMsg$
|
|
7618
|
+
function errMsg$15(err) {
|
|
7605
7619
|
if (err instanceof Error) return err.message;
|
|
7606
7620
|
if (typeof err === "string") return err;
|
|
7607
7621
|
return String(err);
|
|
@@ -13738,11 +13752,33 @@ var NotificationFormatSchema = _enum([
|
|
|
13738
13752
|
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
13739
13753
|
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
13740
13754
|
* differently.
|
|
13755
|
+
*
|
|
13756
|
+
* ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
|
|
13757
|
+
*
|
|
13758
|
+
* These names are for US. **No adapter may forward one verbatim.** Each maps
|
|
13759
|
+
* the whole set onto its own renderer's vocabulary through a
|
|
13760
|
+
* `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
|
|
13761
|
+
* fallback, so adding a member here fails every adapter's build until someone
|
|
13762
|
+
* decides its glyph, which is the only place that decision can be made
|
|
13763
|
+
* honestly.
|
|
13764
|
+
*
|
|
13765
|
+
* This paragraph is the bug. Zentik declared `actionIcons: true` and passed
|
|
13766
|
+
* `disarm` straight through; iOS feeds that string to
|
|
13767
|
+
* `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
|
|
13768
|
+
* and every snooze and alarm button arrived BLANK. A pass-through is not a
|
|
13769
|
+
* mapping, and "the field is documented" is not "the value renders".
|
|
13770
|
+
*
|
|
13771
|
+
* Adding a member is TRAIN-BOUND. The enum lives in the published
|
|
13772
|
+
* `@camstack/server` closure and the cap seam validates against the HUB's copy,
|
|
13773
|
+
* so an addon that emits a token the running hub does not know does not lose an
|
|
13774
|
+
* icon — its whole `send` fails Zod validation and the notification never
|
|
13775
|
+
* arrives. Never emit a new token from an addon before the train carrying it.
|
|
13741
13776
|
*/
|
|
13742
13777
|
var NotificationActionIconSchema = _enum([
|
|
13743
13778
|
"acknowledge",
|
|
13744
13779
|
"dismiss",
|
|
13745
13780
|
"silence",
|
|
13781
|
+
"snooze",
|
|
13746
13782
|
"view",
|
|
13747
13783
|
"play",
|
|
13748
13784
|
"open",
|
|
@@ -13750,9 +13786,13 @@ var NotificationActionIconSchema = _enum([
|
|
|
13750
13786
|
"lock",
|
|
13751
13787
|
"unlock",
|
|
13752
13788
|
"arm",
|
|
13789
|
+
"arm-home",
|
|
13790
|
+
"arm-away",
|
|
13791
|
+
"arm-night",
|
|
13753
13792
|
"disarm",
|
|
13754
13793
|
"light",
|
|
13755
|
-
"alert"
|
|
13794
|
+
"alert",
|
|
13795
|
+
"camera"
|
|
13756
13796
|
]);
|
|
13757
13797
|
/** A single tap-through action button. */
|
|
13758
13798
|
var NotificationActionSchema = object({
|
|
@@ -13952,6 +13992,24 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
|
|
|
13952
13992
|
targetId: string(),
|
|
13953
13993
|
enabled: boolean()
|
|
13954
13994
|
}), _void(), { kind: "mutation" });
|
|
13995
|
+
new Set([
|
|
13996
|
+
{
|
|
13997
|
+
id: "person",
|
|
13998
|
+
name: "Person"
|
|
13999
|
+
},
|
|
14000
|
+
{
|
|
14001
|
+
id: "vehicle",
|
|
14002
|
+
name: "Vehicle"
|
|
14003
|
+
},
|
|
14004
|
+
{
|
|
14005
|
+
id: "animal",
|
|
14006
|
+
name: "Animal"
|
|
14007
|
+
},
|
|
14008
|
+
{
|
|
14009
|
+
id: "package",
|
|
14010
|
+
name: "Package"
|
|
14011
|
+
}
|
|
14012
|
+
].map((l) => l.id));
|
|
13955
14013
|
var COCO_TO_MACRO = {
|
|
13956
14014
|
mapping: {
|
|
13957
14015
|
person: "person",
|
|
@@ -14749,6 +14807,8 @@ var NcSystemEventKindSchema = _enum([
|
|
|
14749
14807
|
"alarm-triggered",
|
|
14750
14808
|
"alarm-armed",
|
|
14751
14809
|
"alarm-disarmed",
|
|
14810
|
+
"alarm-arming",
|
|
14811
|
+
"alarm-arm-refused",
|
|
14752
14812
|
"camera-online",
|
|
14753
14813
|
"camera-offline",
|
|
14754
14814
|
"camera-disabled",
|
|
@@ -15957,7 +16017,87 @@ var MethodAccessSchema = _enum([
|
|
|
15957
16017
|
var AllowedProviderSchema = union([literal("*"), array(string())]);
|
|
15958
16018
|
var AllowedDevicesSchema = record(string(), union([literal("*"), array(string())]));
|
|
15959
16019
|
var CapScopeSchema = _enum(["device", "system"]);
|
|
15960
|
-
|
|
16020
|
+
/**
|
|
16021
|
+
* DeviceSelector (scope model v3 — 2026-08-12).
|
|
16022
|
+
*
|
|
16023
|
+
* A `device` grant no longer carries a frozen list of deviceIds. It carries
|
|
16024
|
+
* a SELECTOR the matcher resolves against the live fleet, so the grant can be
|
|
16025
|
+
* DYNAMIC: a `types:['camera']` selector automatically covers a camera added
|
|
16026
|
+
* AFTER the grant was minted — no re-grant, no re-login.
|
|
16027
|
+
*
|
|
16028
|
+
* - `all` — every device in the deployment. The broad viewer/operator
|
|
16029
|
+
* lever without a `category` grant (a `category` grant also covers device
|
|
16030
|
+
* caps that carry no deviceId; `all` is specifically the device set).
|
|
16031
|
+
* - `ids` — an explicit deviceId list. This is what a v2 `device:[…]`
|
|
16032
|
+
* grant migrates to (see {@link TokenScopeSchema}); STATIC — a new camera
|
|
16033
|
+
* is NOT covered until the grant is edited.
|
|
16034
|
+
* - `types` — every device of a `DeviceType` (e.g. every `camera`).
|
|
16035
|
+
* DYNAMIC. A device that changes type, or a new device of the type,
|
|
16036
|
+
* re-resolves on the next request.
|
|
16037
|
+
* - `locations` — every device whose operator-assigned `location` label is
|
|
16038
|
+
* in the set (e.g. "Garden", "Front door"). DYNAMIC. A device with a
|
|
16039
|
+
* null/unset location matches NO `locations` selector.
|
|
16040
|
+
*/
|
|
16041
|
+
var DeviceSelectorSchema = discriminatedUnion("kind", [
|
|
16042
|
+
object({ kind: literal("all") }),
|
|
16043
|
+
object({
|
|
16044
|
+
kind: literal("ids"),
|
|
16045
|
+
ids: array(number().int()).min(1)
|
|
16046
|
+
}),
|
|
16047
|
+
object({
|
|
16048
|
+
kind: literal("types"),
|
|
16049
|
+
types: array(_enum(DeviceType)).min(1)
|
|
16050
|
+
}),
|
|
16051
|
+
object({
|
|
16052
|
+
kind: literal("locations"),
|
|
16053
|
+
locations: array(string().min(1)).min(1)
|
|
16054
|
+
})
|
|
16055
|
+
]);
|
|
16056
|
+
var DeviceTokenScopeSchema = object({
|
|
16057
|
+
type: literal("device"),
|
|
16058
|
+
/** The device SET this grant covers — resolved against the live fleet. */
|
|
16059
|
+
selector: DeviceSelectorSchema,
|
|
16060
|
+
access: array(MethodAccessSchema).min(1),
|
|
16061
|
+
/**
|
|
16062
|
+
* Whether a grant on a PARENT device transparently covers its accessory
|
|
16063
|
+
* CHILDREN (siren / floodlight / PIR) via the persisted-parentage walk.
|
|
16064
|
+
* Direction is parent → children ONLY.
|
|
16065
|
+
*
|
|
16066
|
+
* Absent → the matcher DERIVES it from the access flavour: `view`
|
|
16067
|
+
* inherits (a camera viewer sees the camera's accessories), `create` /
|
|
16068
|
+
* `delete` do NOT (actuating/removing a child is an explicit act the
|
|
16069
|
+
* operator must grant on the child, not inherit from the parent). Set it
|
|
16070
|
+
* explicitly to override that default per grant.
|
|
16071
|
+
*/
|
|
16072
|
+
includeLinked: boolean().optional()
|
|
16073
|
+
});
|
|
16074
|
+
/**
|
|
16075
|
+
* v2 → v3 lazy migration. A pre-v3 `device` grant carried
|
|
16076
|
+
* `targets: string[]` (stringified deviceIds); it rewrites to the equivalent
|
|
16077
|
+
* `selector: {kind:'ids', ids}`. Applied as a `preprocess` so it runs on
|
|
16078
|
+
* EVERY parse path — stored records AND the JWT-carried scope arrays
|
|
16079
|
+
* normalised at the request boundary ({@link normalizeTokenScopes} in
|
|
16080
|
+
* `device-selector.ts`). Chosen over a one-time DB migration because a
|
|
16081
|
+
* migration cannot reach a JWT already in a client's hands; parse-time
|
|
16082
|
+
* migration covers both without a flag day. No cast — the raw object is read
|
|
16083
|
+
* through `Reflect.get` (its static type is `unknown`).
|
|
16084
|
+
*/
|
|
16085
|
+
function migrateLegacyTokenScope(raw) {
|
|
16086
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return raw;
|
|
16087
|
+
if (Reflect.get(raw, "type") !== "device") return raw;
|
|
16088
|
+
if (Reflect.get(raw, "selector") !== void 0) return raw;
|
|
16089
|
+
const targets = Reflect.get(raw, "targets");
|
|
16090
|
+
if (!Array.isArray(targets)) return raw;
|
|
16091
|
+
return {
|
|
16092
|
+
type: "device",
|
|
16093
|
+
selector: {
|
|
16094
|
+
kind: "ids",
|
|
16095
|
+
ids: targets.map((t) => typeof t === "string" ? Number(t) : t).filter((n) => typeof n === "number" && Number.isInteger(n))
|
|
16096
|
+
},
|
|
16097
|
+
access: Reflect.get(raw, "access")
|
|
16098
|
+
};
|
|
16099
|
+
}
|
|
16100
|
+
var TokenScopeSchema = preprocess(migrateLegacyTokenScope, discriminatedUnion("type", [
|
|
15961
16101
|
object({
|
|
15962
16102
|
type: literal("category"),
|
|
15963
16103
|
target: CapScopeSchema,
|
|
@@ -15973,18 +16113,8 @@ var TokenScopeSchema = discriminatedUnion("type", [
|
|
|
15973
16113
|
target: string(),
|
|
15974
16114
|
access: array(MethodAccessSchema).min(1)
|
|
15975
16115
|
}),
|
|
15976
|
-
|
|
15977
|
-
|
|
15978
|
-
/**
|
|
15979
|
-
* One or more deviceIds (serialised as strings for wire-format
|
|
15980
|
-
* consistency with the rest of the union). Matcher accepts if
|
|
15981
|
-
* `input.deviceId` ∈ `targets`. Array shape avoids the row-explosion
|
|
15982
|
-
* of one scope-per-device when granting access to a set of cameras.
|
|
15983
|
-
*/
|
|
15984
|
-
targets: array(string()).min(1),
|
|
15985
|
-
access: array(MethodAccessSchema).min(1)
|
|
15986
|
-
})
|
|
15987
|
-
]);
|
|
16116
|
+
DeviceTokenScopeSchema
|
|
16117
|
+
]));
|
|
15988
16118
|
object({
|
|
15989
16119
|
id: string(),
|
|
15990
16120
|
username: string(),
|
|
@@ -17981,10 +18111,26 @@ var detectionFpsField = {
|
|
|
17981
18111
|
default: 10,
|
|
17982
18112
|
step: 1
|
|
17983
18113
|
};
|
|
18114
|
+
/**
|
|
18115
|
+
* The occupancy re-check interval. DEFAULT 300 s (2026-08-13 — was 30 s).
|
|
18116
|
+
*
|
|
18117
|
+
* The recheck is now on by default (a parked car is invisible to occupancy
|
|
18118
|
+
* rules until the stationary registry has been rebuilt by motion, which after a
|
|
18119
|
+
* restart may be never on a quiet camera). Each cycle re-subscribes a detection
|
|
18120
|
+
* session — an RTSP re-dial — so the switch is only affordable at a WIDE
|
|
18121
|
+
* interval: 300 s is ~12 re-dials an hour per camera, against 120 at the old
|
|
18122
|
+
* 30 s. A parked car is therefore counted within 5 minutes of a restart.
|
|
18123
|
+
*
|
|
18124
|
+
* Why not wider: `max` is 300 and raising it is TRAIN-BOUND, not addon-bound —
|
|
18125
|
+
* the host validates `attachCamera` against ITS copy of this schema, so a
|
|
18126
|
+
* runner asked for 600 would be rejected by the hub until a `@camstack/server`
|
|
18127
|
+
* carrying the wider bound is installed everywhere. 300 is the widest value
|
|
18128
|
+
* that ships with an addon deploy.
|
|
18129
|
+
*/
|
|
17984
18130
|
var occupancyRecheckSecField = {
|
|
17985
18131
|
min: 0,
|
|
17986
18132
|
max: 300,
|
|
17987
|
-
default:
|
|
18133
|
+
default: 300,
|
|
17988
18134
|
step: 5
|
|
17989
18135
|
};
|
|
17990
18136
|
var occupancyRecheckFramesField = {
|
|
@@ -18182,15 +18328,21 @@ var RunnerCameraConfigSchema = object({
|
|
|
18182
18328
|
*/
|
|
18183
18329
|
onboardMotionDrivesAnalyzer: boolean().default(true),
|
|
18184
18330
|
/**
|
|
18185
|
-
* Master toggle for the occupancy re-check. When `false`
|
|
18186
|
-
*
|
|
18187
|
-
* this is off by default because the recheck re-subscribes a detection session
|
|
18188
|
-
* every N seconds while `watching`, a major source of pull-decoder re-dial
|
|
18189
|
-
* churn (each cycle creates+tears a session → RTSP re-dial → latency). The
|
|
18331
|
+
* Master toggle for the occupancy re-check. When `false` the runner never arms
|
|
18332
|
+
* the periodic recheck timer, regardless of `occupancyRecheckSec`; the
|
|
18190
18333
|
* `occupancyRecheckSec` / `occupancyRecheckFrames` sliders only take effect
|
|
18191
18334
|
* (and only render) when this is enabled.
|
|
18192
|
-
|
|
18193
|
-
|
|
18335
|
+
*
|
|
18336
|
+
* DEFAULT `true` since 2026-08-13 (was `false`). It was off because the
|
|
18337
|
+
* recheck re-subscribes a detection session every N seconds while `watching`
|
|
18338
|
+
* — each cycle creates+tears a session ⇒ an RTSP re-dial ⇒ latency, a major
|
|
18339
|
+
* pull-decoder churn source. What that bought was a blind spot: a STATIONARY
|
|
18340
|
+
* object is counted only while the stationary registry holds it, and the
|
|
18341
|
+
* registry rebuilds from motion, so after a restart a parked car was invisible
|
|
18342
|
+
* to every occupancy rule until something moved in front of it. The churn is
|
|
18343
|
+
* now paid on the interval instead — see `occupancyRecheckSecField`.
|
|
18344
|
+
*/
|
|
18345
|
+
occupancyRecheckEnabled: boolean().default(true),
|
|
18194
18346
|
occupancyRecheckSec: number().min(occupancyRecheckSecField.min).max(occupancyRecheckSecField.max).default(occupancyRecheckSecField.default),
|
|
18195
18347
|
occupancyRecheckFrames: number().min(occupancyRecheckFramesField.min).max(occupancyRecheckFramesField.max).default(occupancyRecheckFramesField.default),
|
|
18196
18348
|
/**
|
|
@@ -20591,7 +20743,11 @@ object({
|
|
|
20591
20743
|
precision: number().int().min(0).max(10).optional()
|
|
20592
20744
|
});
|
|
20593
20745
|
DeviceType.Sensor;
|
|
20594
|
-
|
|
20746
|
+
/**
|
|
20747
|
+
* Ambient illuminance reading in lux. Drives Home Assistant `sensor`
|
|
20748
|
+
* entries with `device_class: illuminance`.
|
|
20749
|
+
*/
|
|
20750
|
+
var AmbientLightSensorStatusSchema = object({
|
|
20595
20751
|
/** Current illuminance in lux (lx). */
|
|
20596
20752
|
lux: number().min(0),
|
|
20597
20753
|
/** Ms epoch when the slice was last updated. */
|
|
@@ -20864,7 +21020,11 @@ DeviceType.Camera, method(object({ deviceId: number() }), CameraCredentialsSchem
|
|
|
20864
21020
|
kind: "query",
|
|
20865
21021
|
auth: "admin"
|
|
20866
21022
|
});
|
|
20867
|
-
|
|
21023
|
+
/**
|
|
21024
|
+
* Carbon-monoxide alarm sensor. Drives Home Assistant `binary_sensor`
|
|
21025
|
+
* entries with `device_class: carbon_monoxide`. Push-driven.
|
|
21026
|
+
*/
|
|
21027
|
+
var CarbonMonoxideStatusSchema = object({
|
|
20868
21028
|
detected: boolean(),
|
|
20869
21029
|
/** Ms epoch of the last transition. 0 if never observed. */
|
|
20870
21030
|
lastChangedAt: number()
|
|
@@ -21152,7 +21312,19 @@ Object.values(DeviceType), method(object({
|
|
|
21152
21312
|
kind: "mutation",
|
|
21153
21313
|
auth: "admin"
|
|
21154
21314
|
}), ConsumablesStatusSchema.extend({ lastFetchedAt: number() });
|
|
21155
|
-
|
|
21315
|
+
/**
|
|
21316
|
+
* Door / window / opening / garage / valve contact sensor. Boolean
|
|
21317
|
+
* "is the entry currently open" with the timestamp of the last
|
|
21318
|
+
* transition. Drives Home Assistant `binary_sensor` entries whose
|
|
21319
|
+
* `device_class` is `door`, `window`, `opening`, `garage`, or
|
|
21320
|
+
* `garage_door` — and any future native integration that needs
|
|
21321
|
+
* the same semantics.
|
|
21322
|
+
*
|
|
21323
|
+
* Push-driven: providers update the slice on transition events from
|
|
21324
|
+
* the upstream source (HA WebSocket `state_changed`, ZWave
|
|
21325
|
+
* `notification` …). Consumers read the slice; no polling.
|
|
21326
|
+
*/
|
|
21327
|
+
var ContactStatusSchema = object({
|
|
21156
21328
|
/** True when the entry is open; false when closed. */
|
|
21157
21329
|
entryOpen: boolean(),
|
|
21158
21330
|
/** Ms epoch of the last open↔closed transition. 0 if never observed. */
|
|
@@ -21751,7 +21923,15 @@ object({
|
|
|
21751
21923
|
deviceId: number(),
|
|
21752
21924
|
status: FeatureProbeStatusSchema
|
|
21753
21925
|
});
|
|
21754
|
-
|
|
21926
|
+
/**
|
|
21927
|
+
* Water leak / moisture sensor. Boolean "is liquid currently
|
|
21928
|
+
* detected" with the timestamp of the last transition. Drives Home
|
|
21929
|
+
* Assistant `binary_sensor` entries with `device_class: moisture`,
|
|
21930
|
+
* and any future native flood sensor.
|
|
21931
|
+
*
|
|
21932
|
+
* Push-driven from the upstream source.
|
|
21933
|
+
*/
|
|
21934
|
+
var FloodStatusSchema = object({
|
|
21755
21935
|
/** True when leak is currently detected. */
|
|
21756
21936
|
flooded: boolean(),
|
|
21757
21937
|
/** Ms epoch of the last flooded↔dry transition. 0 if never observed. */
|
|
@@ -21805,7 +21985,15 @@ DeviceType.Humidifier, method(object({
|
|
|
21805
21985
|
kind: "mutation",
|
|
21806
21986
|
auth: "admin"
|
|
21807
21987
|
});
|
|
21808
|
-
|
|
21988
|
+
/**
|
|
21989
|
+
* Single-metric humidity reading. Drives Home Assistant `sensor`
|
|
21990
|
+
* entries with `device_class: humidity`.
|
|
21991
|
+
*
|
|
21992
|
+
* Unit normalisation: percent. The canonical display unit (`%`) is a
|
|
21993
|
+
* descriptor constant in the UI (ROLE_DESCRIPTOR), not stored in
|
|
21994
|
+
* `sourceInfo`.
|
|
21995
|
+
*/
|
|
21996
|
+
var HumiditySensorStatusSchema = object({
|
|
21809
21997
|
/** Current relative humidity, 0..100. */
|
|
21810
21998
|
percent: number().min(0).max(100),
|
|
21811
21999
|
/** Ms epoch when the slice was last updated. */
|
|
@@ -22427,7 +22615,7 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
|
|
|
22427
22615
|
* tunnel always emits `https://` regardless. */
|
|
22428
22616
|
scheme: _enum(["http", "https"]).optional()
|
|
22429
22617
|
}), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" });
|
|
22430
|
-
object({
|
|
22618
|
+
var LockControlStatusSchema = object({
|
|
22431
22619
|
/** Lifecycle state of the lock. `jammed` means the motor reported
|
|
22432
22620
|
* failure to reach the target — operator intervention required. */
|
|
22433
22621
|
state: _enum([
|
|
@@ -22765,7 +22953,19 @@ authKey: string().optional() }), object({
|
|
|
22765
22953
|
/** Human-readable error when `ok: false`. */
|
|
22766
22954
|
error: string().optional()
|
|
22767
22955
|
}), { kind: "mutation" });
|
|
22768
|
-
|
|
22956
|
+
/**
|
|
22957
|
+
* Hardware / firmware motion sensor cap — binary detected state plus
|
|
22958
|
+
* a timestamp of the last observation. Distinct from
|
|
22959
|
+
* `motion-detection.cap.ts` which owns the LOCAL ML motion pipeline;
|
|
22960
|
+
* `motion` is the lightweight readout from on-camera motion (Reolink
|
|
22961
|
+
* `GetMdState`, Baichuan push `type: motion`, ONVIF analytics).
|
|
22962
|
+
*
|
|
22963
|
+
* Native-motion providers also fan out to `detection.camera-native`
|
|
22964
|
+
* with `source: 'onboard'` so cross-cutting system services
|
|
22965
|
+
* (alert-center, advanced-notifier) can subscribe once and receive
|
|
22966
|
+
* motion from every camera.
|
|
22967
|
+
*/
|
|
22968
|
+
var MotionStatusSchema = object({
|
|
22769
22969
|
detected: boolean(),
|
|
22770
22970
|
/** Ms epoch of the last detected-true observation. Null if never detected. */
|
|
22771
22971
|
lastDetectedAt: number().nullable(),
|
|
@@ -23877,7 +24077,7 @@ var GpsLocationSchema = object({
|
|
|
23877
24077
|
/** Reported accuracy in meters (lower = better). */
|
|
23878
24078
|
accuracyMeters: number().nonnegative()
|
|
23879
24079
|
});
|
|
23880
|
-
object({
|
|
24080
|
+
var PresenceStatusSchema = object({
|
|
23881
24081
|
/** `home` / `not_home` / any user-defined zone name. */
|
|
23882
24082
|
state: string(),
|
|
23883
24083
|
/** Optional textual location label (zone name, city, address). Null
|
|
@@ -24293,7 +24493,7 @@ method(object({
|
|
|
24293
24493
|
toMs: number()
|
|
24294
24494
|
}), RecordingAvailabilitySchema, {
|
|
24295
24495
|
kind: "query",
|
|
24296
|
-
auth: "
|
|
24496
|
+
auth: "protected"
|
|
24297
24497
|
}), method(object({
|
|
24298
24498
|
deviceId: number(),
|
|
24299
24499
|
fromMs: number(),
|
|
@@ -24301,14 +24501,14 @@ method(object({
|
|
|
24301
24501
|
tzOffsetMinutes: number()
|
|
24302
24502
|
}), RecordingDaysSchema, {
|
|
24303
24503
|
kind: "query",
|
|
24304
|
-
auth: "
|
|
24504
|
+
auth: "protected"
|
|
24305
24505
|
}), method(object({
|
|
24306
24506
|
deviceId: number(),
|
|
24307
24507
|
fromMs: number(),
|
|
24308
24508
|
toMs: number()
|
|
24309
24509
|
}), RecordingManifestSchema, {
|
|
24310
24510
|
kind: "query",
|
|
24311
|
-
auth: "
|
|
24511
|
+
auth: "protected"
|
|
24312
24512
|
}), method(object({}), RecordingStorageUsageSchema, {
|
|
24313
24513
|
kind: "query",
|
|
24314
24514
|
auth: "admin"
|
|
@@ -24837,7 +25037,16 @@ DeviceType.Script, method(object({
|
|
|
24837
25037
|
kind: "mutation",
|
|
24838
25038
|
auth: "admin"
|
|
24839
25039
|
});
|
|
24840
|
-
|
|
25040
|
+
/**
|
|
25041
|
+
* Smoke alarm sensor — boolean "is smoke currently detected" with
|
|
25042
|
+
* timestamp of the last transition. Drives Home Assistant
|
|
25043
|
+
* `binary_sensor` entries with `device_class: smoke`.
|
|
25044
|
+
*
|
|
25045
|
+
* Push-driven: a smoke event is critical, so the slice updates
|
|
25046
|
+
* immediately on the upstream signal. Auto-clearing back to false is
|
|
25047
|
+
* provider-controlled (some alarms latch until manually reset).
|
|
25048
|
+
*/
|
|
25049
|
+
var SmokeStatusSchema = object({
|
|
24841
25050
|
detected: boolean(),
|
|
24842
25051
|
/** Ms epoch of the last transition. 0 if never observed. */
|
|
24843
25052
|
lastChangedAt: number()
|
|
@@ -25046,7 +25255,23 @@ object({
|
|
|
25046
25255
|
lastChangedAt: number()
|
|
25047
25256
|
});
|
|
25048
25257
|
DeviceType.Sensor;
|
|
25049
|
-
|
|
25258
|
+
/**
|
|
25259
|
+
* Single-metric temperature reading. Drives Home Assistant `sensor`
|
|
25260
|
+
* entries with `device_class: temperature` and any future native
|
|
25261
|
+
* thermometer.
|
|
25262
|
+
*
|
|
25263
|
+
* Unit normalisation: providers convert to Celsius before storing.
|
|
25264
|
+
* The slice value is always Celsius so cross-cap aggregators
|
|
25265
|
+
* (climate-control's `currentTemp`, energy analytics) can compose
|
|
25266
|
+
* without per-source unit fixups. The canonical display unit (`°C`) is
|
|
25267
|
+
* a descriptor constant in the UI (ROLE_DESCRIPTOR), not stored in
|
|
25268
|
+
* `sourceInfo`.
|
|
25269
|
+
*
|
|
25270
|
+
* Status `lastFetchedAt` lets staleness-aware consumers detect a
|
|
25271
|
+
* frozen feed (provider hung) distinct from a "temperature hasn't
|
|
25272
|
+
* changed" steady state.
|
|
25273
|
+
*/
|
|
25274
|
+
var TemperatureSensorStatusSchema = object({
|
|
25050
25275
|
/** Current temperature in Celsius. */
|
|
25051
25276
|
celsius: number(),
|
|
25052
25277
|
/** Ms epoch when the slice was last updated (push or poll). */
|
|
@@ -31307,6 +31532,1678 @@ Object.freeze({
|
|
|
31307
31532
|
access: "create"
|
|
31308
31533
|
}
|
|
31309
31534
|
});
|
|
31535
|
+
Object.freeze({
|
|
31536
|
+
"accessories.setChildHidden": [{
|
|
31537
|
+
name: "childDeviceId",
|
|
31538
|
+
form: "single",
|
|
31539
|
+
optional: false
|
|
31540
|
+
}, {
|
|
31541
|
+
name: "deviceId",
|
|
31542
|
+
form: "single",
|
|
31543
|
+
optional: false
|
|
31544
|
+
}],
|
|
31545
|
+
"addonSettings.getDeviceSettings": [{
|
|
31546
|
+
name: "deviceId",
|
|
31547
|
+
form: "single",
|
|
31548
|
+
optional: false
|
|
31549
|
+
}],
|
|
31550
|
+
"addonSettings.updateDeviceSettings": [{
|
|
31551
|
+
name: "deviceId",
|
|
31552
|
+
form: "single",
|
|
31553
|
+
optional: false
|
|
31554
|
+
}],
|
|
31555
|
+
"alarmPanel.arm": [{
|
|
31556
|
+
name: "deviceId",
|
|
31557
|
+
form: "single",
|
|
31558
|
+
optional: false
|
|
31559
|
+
}],
|
|
31560
|
+
"alarmPanel.disarm": [{
|
|
31561
|
+
name: "deviceId",
|
|
31562
|
+
form: "single",
|
|
31563
|
+
optional: false
|
|
31564
|
+
}],
|
|
31565
|
+
"alarmPanel.trigger": [{
|
|
31566
|
+
name: "deviceId",
|
|
31567
|
+
form: "single",
|
|
31568
|
+
optional: false
|
|
31569
|
+
}],
|
|
31570
|
+
"audioAnalysis.resolveDeviceSettings": [{
|
|
31571
|
+
name: "deviceId",
|
|
31572
|
+
form: "single",
|
|
31573
|
+
optional: false
|
|
31574
|
+
}],
|
|
31575
|
+
"audioAnalyzer.classify": [{
|
|
31576
|
+
name: "deviceId",
|
|
31577
|
+
form: "single",
|
|
31578
|
+
optional: true
|
|
31579
|
+
}],
|
|
31580
|
+
"audioMetrics.getCurrentSnapshot": [{
|
|
31581
|
+
name: "deviceId",
|
|
31582
|
+
form: "single",
|
|
31583
|
+
optional: false
|
|
31584
|
+
}],
|
|
31585
|
+
"audioMetrics.getHistory": [{
|
|
31586
|
+
name: "deviceId",
|
|
31587
|
+
form: "single",
|
|
31588
|
+
optional: false
|
|
31589
|
+
}],
|
|
31590
|
+
"automationControl.disable": [{
|
|
31591
|
+
name: "deviceId",
|
|
31592
|
+
form: "single",
|
|
31593
|
+
optional: false
|
|
31594
|
+
}],
|
|
31595
|
+
"automationControl.enable": [{
|
|
31596
|
+
name: "deviceId",
|
|
31597
|
+
form: "single",
|
|
31598
|
+
optional: false
|
|
31599
|
+
}],
|
|
31600
|
+
"automationControl.trigger": [{
|
|
31601
|
+
name: "deviceId",
|
|
31602
|
+
form: "single",
|
|
31603
|
+
optional: false
|
|
31604
|
+
}],
|
|
31605
|
+
"battery.wakeForStream": [{
|
|
31606
|
+
name: "deviceId",
|
|
31607
|
+
form: "single",
|
|
31608
|
+
optional: false
|
|
31609
|
+
}],
|
|
31610
|
+
"brightness.setBrightness": [{
|
|
31611
|
+
name: "deviceId",
|
|
31612
|
+
form: "single",
|
|
31613
|
+
optional: false
|
|
31614
|
+
}],
|
|
31615
|
+
"button.press": [{
|
|
31616
|
+
name: "deviceId",
|
|
31617
|
+
form: "single",
|
|
31618
|
+
optional: false
|
|
31619
|
+
}],
|
|
31620
|
+
"cameraCredentials.getCredentials": [{
|
|
31621
|
+
name: "deviceId",
|
|
31622
|
+
form: "single",
|
|
31623
|
+
optional: false
|
|
31624
|
+
}],
|
|
31625
|
+
"cameraStreams.getBrokerStreams": [{
|
|
31626
|
+
name: "deviceId",
|
|
31627
|
+
form: "single",
|
|
31628
|
+
optional: false
|
|
31629
|
+
}],
|
|
31630
|
+
"cameraStreams.getCameraStreams": [{
|
|
31631
|
+
name: "deviceId",
|
|
31632
|
+
form: "single",
|
|
31633
|
+
optional: false
|
|
31634
|
+
}],
|
|
31635
|
+
"cameraStreams.getProfileRtspEntries": [{
|
|
31636
|
+
name: "deviceId",
|
|
31637
|
+
form: "single",
|
|
31638
|
+
optional: false
|
|
31639
|
+
}],
|
|
31640
|
+
"cameraStreams.getRtspEntries": [{
|
|
31641
|
+
name: "deviceId",
|
|
31642
|
+
form: "single",
|
|
31643
|
+
optional: false
|
|
31644
|
+
}],
|
|
31645
|
+
"cameraStreams.pickStream": [{
|
|
31646
|
+
name: "deviceId",
|
|
31647
|
+
form: "single",
|
|
31648
|
+
optional: false
|
|
31649
|
+
}],
|
|
31650
|
+
"climateControl.setFanMode": [{
|
|
31651
|
+
name: "deviceId",
|
|
31652
|
+
form: "single",
|
|
31653
|
+
optional: false
|
|
31654
|
+
}],
|
|
31655
|
+
"climateControl.setMode": [{
|
|
31656
|
+
name: "deviceId",
|
|
31657
|
+
form: "single",
|
|
31658
|
+
optional: false
|
|
31659
|
+
}],
|
|
31660
|
+
"climateControl.setPreset": [{
|
|
31661
|
+
name: "deviceId",
|
|
31662
|
+
form: "single",
|
|
31663
|
+
optional: false
|
|
31664
|
+
}],
|
|
31665
|
+
"climateControl.setSwingHorizontal": [{
|
|
31666
|
+
name: "deviceId",
|
|
31667
|
+
form: "single",
|
|
31668
|
+
optional: false
|
|
31669
|
+
}],
|
|
31670
|
+
"climateControl.setSwingVertical": [{
|
|
31671
|
+
name: "deviceId",
|
|
31672
|
+
form: "single",
|
|
31673
|
+
optional: false
|
|
31674
|
+
}],
|
|
31675
|
+
"climateControl.setTarget": [{
|
|
31676
|
+
name: "deviceId",
|
|
31677
|
+
form: "single",
|
|
31678
|
+
optional: false
|
|
31679
|
+
}],
|
|
31680
|
+
"climateControl.setTargetHumidity": [{
|
|
31681
|
+
name: "deviceId",
|
|
31682
|
+
form: "single",
|
|
31683
|
+
optional: false
|
|
31684
|
+
}],
|
|
31685
|
+
"climateControl.setTargetRange": [{
|
|
31686
|
+
name: "deviceId",
|
|
31687
|
+
form: "single",
|
|
31688
|
+
optional: false
|
|
31689
|
+
}],
|
|
31690
|
+
"color.setColor": [{
|
|
31691
|
+
name: "deviceId",
|
|
31692
|
+
form: "single",
|
|
31693
|
+
optional: false
|
|
31694
|
+
}],
|
|
31695
|
+
"consumables.reset": [{
|
|
31696
|
+
name: "deviceId",
|
|
31697
|
+
form: "single",
|
|
31698
|
+
optional: false
|
|
31699
|
+
}],
|
|
31700
|
+
"control.setValue": [{
|
|
31701
|
+
name: "deviceId",
|
|
31702
|
+
form: "single",
|
|
31703
|
+
optional: false
|
|
31704
|
+
}],
|
|
31705
|
+
"cover.close": [{
|
|
31706
|
+
name: "deviceId",
|
|
31707
|
+
form: "single",
|
|
31708
|
+
optional: false
|
|
31709
|
+
}],
|
|
31710
|
+
"cover.open": [{
|
|
31711
|
+
name: "deviceId",
|
|
31712
|
+
form: "single",
|
|
31713
|
+
optional: false
|
|
31714
|
+
}],
|
|
31715
|
+
"cover.setPosition": [{
|
|
31716
|
+
name: "deviceId",
|
|
31717
|
+
form: "single",
|
|
31718
|
+
optional: false
|
|
31719
|
+
}],
|
|
31720
|
+
"cover.setTiltPosition": [{
|
|
31721
|
+
name: "deviceId",
|
|
31722
|
+
form: "single",
|
|
31723
|
+
optional: false
|
|
31724
|
+
}],
|
|
31725
|
+
"cover.stop": [{
|
|
31726
|
+
name: "deviceId",
|
|
31727
|
+
form: "single",
|
|
31728
|
+
optional: false
|
|
31729
|
+
}],
|
|
31730
|
+
"dayNight.getOptions": [{
|
|
31731
|
+
name: "deviceId",
|
|
31732
|
+
form: "single",
|
|
31733
|
+
optional: false
|
|
31734
|
+
}],
|
|
31735
|
+
"dayNight.setSettings": [{
|
|
31736
|
+
name: "deviceId",
|
|
31737
|
+
form: "single",
|
|
31738
|
+
optional: false
|
|
31739
|
+
}],
|
|
31740
|
+
"decoder.createSession": [{
|
|
31741
|
+
name: "deviceId",
|
|
31742
|
+
form: "single",
|
|
31743
|
+
optional: true
|
|
31744
|
+
}],
|
|
31745
|
+
"deviceAdoption.release": [{
|
|
31746
|
+
name: "camDeviceId",
|
|
31747
|
+
form: "single",
|
|
31748
|
+
optional: false
|
|
31749
|
+
}],
|
|
31750
|
+
"deviceAdoption.resync": [{
|
|
31751
|
+
name: "camDeviceId",
|
|
31752
|
+
form: "single",
|
|
31753
|
+
optional: false
|
|
31754
|
+
}],
|
|
31755
|
+
"deviceDiscovery.adoptDevice": [{
|
|
31756
|
+
name: "deviceId",
|
|
31757
|
+
form: "single",
|
|
31758
|
+
optional: false
|
|
31759
|
+
}],
|
|
31760
|
+
"deviceDiscovery.listDiscovered": [{
|
|
31761
|
+
name: "deviceId",
|
|
31762
|
+
form: "single",
|
|
31763
|
+
optional: false
|
|
31764
|
+
}],
|
|
31765
|
+
"deviceDiscovery.refreshDiscovery": [{
|
|
31766
|
+
name: "deviceId",
|
|
31767
|
+
form: "single",
|
|
31768
|
+
optional: false
|
|
31769
|
+
}],
|
|
31770
|
+
"deviceDiscovery.releaseDevice": [{
|
|
31771
|
+
name: "childDeviceId",
|
|
31772
|
+
form: "single",
|
|
31773
|
+
optional: false
|
|
31774
|
+
}, {
|
|
31775
|
+
name: "deviceId",
|
|
31776
|
+
form: "single",
|
|
31777
|
+
optional: false
|
|
31778
|
+
}],
|
|
31779
|
+
"deviceManager.adoptionRelease": [{
|
|
31780
|
+
name: "camDeviceId",
|
|
31781
|
+
form: "single",
|
|
31782
|
+
optional: false
|
|
31783
|
+
}],
|
|
31784
|
+
"deviceManager.adoptionResync": [{
|
|
31785
|
+
name: "camDeviceId",
|
|
31786
|
+
form: "single",
|
|
31787
|
+
optional: false
|
|
31788
|
+
}],
|
|
31789
|
+
"deviceManager.applyInitialMeta": [{
|
|
31790
|
+
name: "deviceId",
|
|
31791
|
+
form: "single",
|
|
31792
|
+
optional: false
|
|
31793
|
+
}, {
|
|
31794
|
+
name: "linkDeviceId",
|
|
31795
|
+
form: "single",
|
|
31796
|
+
optional: true
|
|
31797
|
+
}],
|
|
31798
|
+
"deviceManager.disable": [{
|
|
31799
|
+
name: "deviceId",
|
|
31800
|
+
form: "single",
|
|
31801
|
+
optional: false
|
|
31802
|
+
}],
|
|
31803
|
+
"deviceManager.enable": [{
|
|
31804
|
+
name: "deviceId",
|
|
31805
|
+
form: "single",
|
|
31806
|
+
optional: false
|
|
31807
|
+
}],
|
|
31808
|
+
"deviceManager.getBindings": [{
|
|
31809
|
+
name: "deviceId",
|
|
31810
|
+
form: "single",
|
|
31811
|
+
optional: false
|
|
31812
|
+
}],
|
|
31813
|
+
"deviceManager.getChildren": [{
|
|
31814
|
+
name: "parentDeviceId",
|
|
31815
|
+
form: "single",
|
|
31816
|
+
optional: false
|
|
31817
|
+
}],
|
|
31818
|
+
"deviceManager.getConfigSchema": [{
|
|
31819
|
+
name: "deviceId",
|
|
31820
|
+
form: "single",
|
|
31821
|
+
optional: false
|
|
31822
|
+
}],
|
|
31823
|
+
"deviceManager.getDevice": [{
|
|
31824
|
+
name: "deviceId",
|
|
31825
|
+
form: "single",
|
|
31826
|
+
optional: false
|
|
31827
|
+
}],
|
|
31828
|
+
"deviceManager.getDeviceAggregate": [{
|
|
31829
|
+
name: "deviceId",
|
|
31830
|
+
form: "single",
|
|
31831
|
+
optional: false
|
|
31832
|
+
}],
|
|
31833
|
+
"deviceManager.getDeviceLiveInfoAggregate": [{
|
|
31834
|
+
name: "deviceId",
|
|
31835
|
+
form: "single",
|
|
31836
|
+
optional: false
|
|
31837
|
+
}],
|
|
31838
|
+
"deviceManager.getDeviceSettingsAggregate": [{
|
|
31839
|
+
name: "deviceId",
|
|
31840
|
+
form: "single",
|
|
31841
|
+
optional: false
|
|
31842
|
+
}],
|
|
31843
|
+
"deviceManager.getDeviceStatusAggregate": [{
|
|
31844
|
+
name: "deviceId",
|
|
31845
|
+
form: "single",
|
|
31846
|
+
optional: false
|
|
31847
|
+
}],
|
|
31848
|
+
"deviceManager.getDeviceStatusAggregateBatch": [{
|
|
31849
|
+
name: "deviceIds",
|
|
31850
|
+
form: "array",
|
|
31851
|
+
optional: false
|
|
31852
|
+
}],
|
|
31853
|
+
"deviceManager.getLinkedDevices": [{
|
|
31854
|
+
name: "deviceId",
|
|
31855
|
+
form: "single",
|
|
31856
|
+
optional: false
|
|
31857
|
+
}],
|
|
31858
|
+
"deviceManager.getSettingsSchema": [{
|
|
31859
|
+
name: "deviceId",
|
|
31860
|
+
form: "single",
|
|
31861
|
+
optional: false
|
|
31862
|
+
}],
|
|
31863
|
+
"deviceManager.getStreamProfileMap": [{
|
|
31864
|
+
name: "deviceId",
|
|
31865
|
+
form: "single",
|
|
31866
|
+
optional: false
|
|
31867
|
+
}],
|
|
31868
|
+
"deviceManager.getStreamSources": [{
|
|
31869
|
+
name: "deviceId",
|
|
31870
|
+
form: "single",
|
|
31871
|
+
optional: false
|
|
31872
|
+
}],
|
|
31873
|
+
"deviceManager.getWireableFields": [{
|
|
31874
|
+
name: "deviceId",
|
|
31875
|
+
form: "single",
|
|
31876
|
+
optional: false
|
|
31877
|
+
}],
|
|
31878
|
+
"deviceManager.loadConfig": [{
|
|
31879
|
+
name: "deviceId",
|
|
31880
|
+
form: "single",
|
|
31881
|
+
optional: false
|
|
31882
|
+
}],
|
|
31883
|
+
"deviceManager.loadMeta": [{
|
|
31884
|
+
name: "deviceId",
|
|
31885
|
+
form: "single",
|
|
31886
|
+
optional: false
|
|
31887
|
+
}],
|
|
31888
|
+
"deviceManager.loadRuntimeState": [{
|
|
31889
|
+
name: "deviceId",
|
|
31890
|
+
form: "single",
|
|
31891
|
+
optional: false
|
|
31892
|
+
}],
|
|
31893
|
+
"deviceManager.persistConfig": [{
|
|
31894
|
+
name: "deviceId",
|
|
31895
|
+
form: "single",
|
|
31896
|
+
optional: false
|
|
31897
|
+
}],
|
|
31898
|
+
"deviceManager.probeStreams": [{
|
|
31899
|
+
name: "deviceId",
|
|
31900
|
+
form: "single",
|
|
31901
|
+
optional: false
|
|
31902
|
+
}],
|
|
31903
|
+
"deviceManager.registerDevice": [{
|
|
31904
|
+
name: "parentDeviceId",
|
|
31905
|
+
form: "single",
|
|
31906
|
+
optional: true
|
|
31907
|
+
}],
|
|
31908
|
+
"deviceManager.remove": [{
|
|
31909
|
+
name: "deviceId",
|
|
31910
|
+
form: "single",
|
|
31911
|
+
optional: false
|
|
31912
|
+
}],
|
|
31913
|
+
"deviceManager.removeDevice": [{
|
|
31914
|
+
name: "deviceId",
|
|
31915
|
+
form: "single",
|
|
31916
|
+
optional: false
|
|
31917
|
+
}],
|
|
31918
|
+
"deviceManager.runDeviceAction": [{
|
|
31919
|
+
name: "deviceId",
|
|
31920
|
+
form: "single",
|
|
31921
|
+
optional: false
|
|
31922
|
+
}],
|
|
31923
|
+
"deviceManager.setChildLayout": [{
|
|
31924
|
+
name: "deviceId",
|
|
31925
|
+
form: "single",
|
|
31926
|
+
optional: false
|
|
31927
|
+
}],
|
|
31928
|
+
"deviceManager.setDisabled": [{
|
|
31929
|
+
name: "deviceId",
|
|
31930
|
+
form: "single",
|
|
31931
|
+
optional: false
|
|
31932
|
+
}],
|
|
31933
|
+
"deviceManager.setDisplay": [{
|
|
31934
|
+
name: "deviceId",
|
|
31935
|
+
form: "single",
|
|
31936
|
+
optional: false
|
|
31937
|
+
}],
|
|
31938
|
+
"deviceManager.setIntegrationId": [{
|
|
31939
|
+
name: "deviceId",
|
|
31940
|
+
form: "single",
|
|
31941
|
+
optional: false
|
|
31942
|
+
}],
|
|
31943
|
+
"deviceManager.setLinkDeviceId": [{
|
|
31944
|
+
name: "deviceId",
|
|
31945
|
+
form: "single",
|
|
31946
|
+
optional: false
|
|
31947
|
+
}, {
|
|
31948
|
+
name: "linkDeviceId",
|
|
31949
|
+
form: "single",
|
|
31950
|
+
optional: true
|
|
31951
|
+
}],
|
|
31952
|
+
"deviceManager.setLocation": [{
|
|
31953
|
+
name: "deviceId",
|
|
31954
|
+
form: "single",
|
|
31955
|
+
optional: false
|
|
31956
|
+
}],
|
|
31957
|
+
"deviceManager.setMetadata": [{
|
|
31958
|
+
name: "deviceId",
|
|
31959
|
+
form: "single",
|
|
31960
|
+
optional: false
|
|
31961
|
+
}],
|
|
31962
|
+
"deviceManager.setName": [{
|
|
31963
|
+
name: "deviceId",
|
|
31964
|
+
form: "single",
|
|
31965
|
+
optional: false
|
|
31966
|
+
}],
|
|
31967
|
+
"deviceManager.setPrimaryChildEntityId": [{
|
|
31968
|
+
name: "deviceId",
|
|
31969
|
+
form: "single",
|
|
31970
|
+
optional: false
|
|
31971
|
+
}],
|
|
31972
|
+
"deviceManager.setRole": [{
|
|
31973
|
+
name: "deviceId",
|
|
31974
|
+
form: "single",
|
|
31975
|
+
optional: false
|
|
31976
|
+
}],
|
|
31977
|
+
"deviceManager.setStreamProfileMap": [{
|
|
31978
|
+
name: "deviceId",
|
|
31979
|
+
form: "single",
|
|
31980
|
+
optional: false
|
|
31981
|
+
}],
|
|
31982
|
+
"deviceManager.setType": [{
|
|
31983
|
+
name: "deviceId",
|
|
31984
|
+
form: "single",
|
|
31985
|
+
optional: false
|
|
31986
|
+
}],
|
|
31987
|
+
"deviceManager.setWrapperActive": [{
|
|
31988
|
+
name: "deviceId",
|
|
31989
|
+
form: "single",
|
|
31990
|
+
optional: false
|
|
31991
|
+
}],
|
|
31992
|
+
"deviceManager.testField": [{
|
|
31993
|
+
name: "deviceId",
|
|
31994
|
+
form: "single",
|
|
31995
|
+
optional: false
|
|
31996
|
+
}],
|
|
31997
|
+
"deviceManager.updateConfig": [{
|
|
31998
|
+
name: "deviceId",
|
|
31999
|
+
form: "single",
|
|
32000
|
+
optional: false
|
|
32001
|
+
}],
|
|
32002
|
+
"deviceManager.updateDeviceField": [{
|
|
32003
|
+
name: "deviceId",
|
|
32004
|
+
form: "single",
|
|
32005
|
+
optional: false
|
|
32006
|
+
}],
|
|
32007
|
+
"deviceManager.updateDeviceFieldsBatch": [{
|
|
32008
|
+
name: "deviceId",
|
|
32009
|
+
form: "single",
|
|
32010
|
+
optional: false
|
|
32011
|
+
}],
|
|
32012
|
+
"deviceOps.getConfigEntries": [{
|
|
32013
|
+
name: "deviceId",
|
|
32014
|
+
form: "single",
|
|
32015
|
+
optional: false
|
|
32016
|
+
}],
|
|
32017
|
+
"deviceOps.getRawState": [{
|
|
32018
|
+
name: "deviceId",
|
|
32019
|
+
form: "single",
|
|
32020
|
+
optional: false
|
|
32021
|
+
}],
|
|
32022
|
+
"deviceOps.getSettingsSchema": [{
|
|
32023
|
+
name: "deviceId",
|
|
32024
|
+
form: "single",
|
|
32025
|
+
optional: false
|
|
32026
|
+
}],
|
|
32027
|
+
"deviceOps.getStreamSources": [{
|
|
32028
|
+
name: "deviceId",
|
|
32029
|
+
form: "single",
|
|
32030
|
+
optional: false
|
|
32031
|
+
}],
|
|
32032
|
+
"deviceOps.removeDevice": [{
|
|
32033
|
+
name: "deviceId",
|
|
32034
|
+
form: "single",
|
|
32035
|
+
optional: false
|
|
32036
|
+
}],
|
|
32037
|
+
"deviceOps.runAction": [{
|
|
32038
|
+
name: "deviceId",
|
|
32039
|
+
form: "single",
|
|
32040
|
+
optional: false
|
|
32041
|
+
}],
|
|
32042
|
+
"deviceOps.setConfig": [{
|
|
32043
|
+
name: "deviceId",
|
|
32044
|
+
form: "single",
|
|
32045
|
+
optional: false
|
|
32046
|
+
}],
|
|
32047
|
+
"deviceState.getCapSlice": [{
|
|
32048
|
+
name: "deviceId",
|
|
32049
|
+
form: "single",
|
|
32050
|
+
optional: false
|
|
32051
|
+
}],
|
|
32052
|
+
"deviceState.getSnapshot": [{
|
|
32053
|
+
name: "deviceId",
|
|
32054
|
+
form: "single",
|
|
32055
|
+
optional: false
|
|
32056
|
+
}],
|
|
32057
|
+
"deviceState.setCapSlice": [{
|
|
32058
|
+
name: "deviceId",
|
|
32059
|
+
form: "single",
|
|
32060
|
+
optional: false
|
|
32061
|
+
}],
|
|
32062
|
+
"events.getEventClipUrl": [{
|
|
32063
|
+
name: "deviceId",
|
|
32064
|
+
form: "single",
|
|
32065
|
+
optional: false
|
|
32066
|
+
}],
|
|
32067
|
+
"events.getEvents": [{
|
|
32068
|
+
name: "deviceId",
|
|
32069
|
+
form: "single",
|
|
32070
|
+
optional: false
|
|
32071
|
+
}],
|
|
32072
|
+
"events.getEventThumbnail": [{
|
|
32073
|
+
name: "deviceId",
|
|
32074
|
+
form: "single",
|
|
32075
|
+
optional: false
|
|
32076
|
+
}],
|
|
32077
|
+
"faceGallery.getFaceByTrack": [{
|
|
32078
|
+
name: "deviceId",
|
|
32079
|
+
form: "single",
|
|
32080
|
+
optional: false
|
|
32081
|
+
}],
|
|
32082
|
+
"faceGallery.listRecentFaces": [{
|
|
32083
|
+
name: "deviceId",
|
|
32084
|
+
form: "single",
|
|
32085
|
+
optional: true
|
|
32086
|
+
}],
|
|
32087
|
+
"fanControl.setDirection": [{
|
|
32088
|
+
name: "deviceId",
|
|
32089
|
+
form: "single",
|
|
32090
|
+
optional: false
|
|
32091
|
+
}],
|
|
32092
|
+
"fanControl.setOscillating": [{
|
|
32093
|
+
name: "deviceId",
|
|
32094
|
+
form: "single",
|
|
32095
|
+
optional: false
|
|
32096
|
+
}],
|
|
32097
|
+
"fanControl.setPercentage": [{
|
|
32098
|
+
name: "deviceId",
|
|
32099
|
+
form: "single",
|
|
32100
|
+
optional: false
|
|
32101
|
+
}],
|
|
32102
|
+
"fanControl.setPreset": [{
|
|
32103
|
+
name: "deviceId",
|
|
32104
|
+
form: "single",
|
|
32105
|
+
optional: false
|
|
32106
|
+
}],
|
|
32107
|
+
"humidifier.setMode": [{
|
|
32108
|
+
name: "deviceId",
|
|
32109
|
+
form: "single",
|
|
32110
|
+
optional: false
|
|
32111
|
+
}],
|
|
32112
|
+
"humidifier.setOn": [{
|
|
32113
|
+
name: "deviceId",
|
|
32114
|
+
form: "single",
|
|
32115
|
+
optional: false
|
|
32116
|
+
}],
|
|
32117
|
+
"humidifier.setTargetHumidity": [{
|
|
32118
|
+
name: "deviceId",
|
|
32119
|
+
form: "single",
|
|
32120
|
+
optional: false
|
|
32121
|
+
}],
|
|
32122
|
+
"imageSettings.getOptions": [{
|
|
32123
|
+
name: "deviceId",
|
|
32124
|
+
form: "single",
|
|
32125
|
+
optional: false
|
|
32126
|
+
}],
|
|
32127
|
+
"imageSettings.setSettings": [{
|
|
32128
|
+
name: "deviceId",
|
|
32129
|
+
form: "single",
|
|
32130
|
+
optional: false
|
|
32131
|
+
}],
|
|
32132
|
+
"intercom.endTalkSession": [{
|
|
32133
|
+
name: "deviceId",
|
|
32134
|
+
form: "single",
|
|
32135
|
+
optional: false
|
|
32136
|
+
}],
|
|
32137
|
+
"intercom.handleAnswer": [{
|
|
32138
|
+
name: "deviceId",
|
|
32139
|
+
form: "single",
|
|
32140
|
+
optional: false
|
|
32141
|
+
}],
|
|
32142
|
+
"intercom.pushTalkAudio": [{
|
|
32143
|
+
name: "deviceId",
|
|
32144
|
+
form: "single",
|
|
32145
|
+
optional: false
|
|
32146
|
+
}],
|
|
32147
|
+
"intercom.startSession": [{
|
|
32148
|
+
name: "deviceId",
|
|
32149
|
+
form: "single",
|
|
32150
|
+
optional: false
|
|
32151
|
+
}],
|
|
32152
|
+
"intercom.startTalkSession": [{
|
|
32153
|
+
name: "deviceId",
|
|
32154
|
+
form: "single",
|
|
32155
|
+
optional: false
|
|
32156
|
+
}],
|
|
32157
|
+
"intercom.stopSession": [{
|
|
32158
|
+
name: "deviceId",
|
|
32159
|
+
form: "single",
|
|
32160
|
+
optional: false
|
|
32161
|
+
}],
|
|
32162
|
+
"lawnMowerControl.dock": [{
|
|
32163
|
+
name: "deviceId",
|
|
32164
|
+
form: "single",
|
|
32165
|
+
optional: false
|
|
32166
|
+
}],
|
|
32167
|
+
"lawnMowerControl.pause": [{
|
|
32168
|
+
name: "deviceId",
|
|
32169
|
+
form: "single",
|
|
32170
|
+
optional: false
|
|
32171
|
+
}],
|
|
32172
|
+
"lawnMowerControl.startMowing": [{
|
|
32173
|
+
name: "deviceId",
|
|
32174
|
+
form: "single",
|
|
32175
|
+
optional: false
|
|
32176
|
+
}],
|
|
32177
|
+
"lockControl.lock": [{
|
|
32178
|
+
name: "deviceId",
|
|
32179
|
+
form: "single",
|
|
32180
|
+
optional: false
|
|
32181
|
+
}],
|
|
32182
|
+
"lockControl.open": [{
|
|
32183
|
+
name: "deviceId",
|
|
32184
|
+
form: "single",
|
|
32185
|
+
optional: false
|
|
32186
|
+
}],
|
|
32187
|
+
"lockControl.unlock": [{
|
|
32188
|
+
name: "deviceId",
|
|
32189
|
+
form: "single",
|
|
32190
|
+
optional: false
|
|
32191
|
+
}],
|
|
32192
|
+
"mediaPlayer.next": [{
|
|
32193
|
+
name: "deviceId",
|
|
32194
|
+
form: "single",
|
|
32195
|
+
optional: false
|
|
32196
|
+
}],
|
|
32197
|
+
"mediaPlayer.pause": [{
|
|
32198
|
+
name: "deviceId",
|
|
32199
|
+
form: "single",
|
|
32200
|
+
optional: false
|
|
32201
|
+
}],
|
|
32202
|
+
"mediaPlayer.play": [{
|
|
32203
|
+
name: "deviceId",
|
|
32204
|
+
form: "single",
|
|
32205
|
+
optional: false
|
|
32206
|
+
}],
|
|
32207
|
+
"mediaPlayer.playMedia": [{
|
|
32208
|
+
name: "deviceId",
|
|
32209
|
+
form: "single",
|
|
32210
|
+
optional: false
|
|
32211
|
+
}],
|
|
32212
|
+
"mediaPlayer.previous": [{
|
|
32213
|
+
name: "deviceId",
|
|
32214
|
+
form: "single",
|
|
32215
|
+
optional: false
|
|
32216
|
+
}],
|
|
32217
|
+
"mediaPlayer.seek": [{
|
|
32218
|
+
name: "deviceId",
|
|
32219
|
+
form: "single",
|
|
32220
|
+
optional: false
|
|
32221
|
+
}],
|
|
32222
|
+
"mediaPlayer.selectSource": [{
|
|
32223
|
+
name: "deviceId",
|
|
32224
|
+
form: "single",
|
|
32225
|
+
optional: false
|
|
32226
|
+
}],
|
|
32227
|
+
"mediaPlayer.setMute": [{
|
|
32228
|
+
name: "deviceId",
|
|
32229
|
+
form: "single",
|
|
32230
|
+
optional: false
|
|
32231
|
+
}],
|
|
32232
|
+
"mediaPlayer.setRepeat": [{
|
|
32233
|
+
name: "deviceId",
|
|
32234
|
+
form: "single",
|
|
32235
|
+
optional: false
|
|
32236
|
+
}],
|
|
32237
|
+
"mediaPlayer.setShuffle": [{
|
|
32238
|
+
name: "deviceId",
|
|
32239
|
+
form: "single",
|
|
32240
|
+
optional: false
|
|
32241
|
+
}],
|
|
32242
|
+
"mediaPlayer.setVolume": [{
|
|
32243
|
+
name: "deviceId",
|
|
32244
|
+
form: "single",
|
|
32245
|
+
optional: false
|
|
32246
|
+
}],
|
|
32247
|
+
"mediaPlayer.stop": [{
|
|
32248
|
+
name: "deviceId",
|
|
32249
|
+
form: "single",
|
|
32250
|
+
optional: false
|
|
32251
|
+
}],
|
|
32252
|
+
"motion.isDetected": [{
|
|
32253
|
+
name: "deviceId",
|
|
32254
|
+
form: "single",
|
|
32255
|
+
optional: false
|
|
32256
|
+
}],
|
|
32257
|
+
"motionDetection.analyze": [{
|
|
32258
|
+
name: "deviceId",
|
|
32259
|
+
form: "single",
|
|
32260
|
+
optional: false
|
|
32261
|
+
}],
|
|
32262
|
+
"motionDetection.removeCamera": [{
|
|
32263
|
+
name: "deviceId",
|
|
32264
|
+
form: "single",
|
|
32265
|
+
optional: false
|
|
32266
|
+
}],
|
|
32267
|
+
"motionTrigger.setMotionTrigger": [{
|
|
32268
|
+
name: "deviceId",
|
|
32269
|
+
form: "single",
|
|
32270
|
+
optional: false
|
|
32271
|
+
}],
|
|
32272
|
+
"motionZones.getOptions": [{
|
|
32273
|
+
name: "deviceId",
|
|
32274
|
+
form: "single",
|
|
32275
|
+
optional: false
|
|
32276
|
+
}],
|
|
32277
|
+
"motionZones.setZone": [{
|
|
32278
|
+
name: "deviceId",
|
|
32279
|
+
form: "single",
|
|
32280
|
+
optional: false
|
|
32281
|
+
}],
|
|
32282
|
+
"nativeObjectDetection.setEnabled": [{
|
|
32283
|
+
name: "deviceId",
|
|
32284
|
+
form: "single",
|
|
32285
|
+
optional: false
|
|
32286
|
+
}],
|
|
32287
|
+
"networkQuality.getDeviceStats": [{
|
|
32288
|
+
name: "deviceId",
|
|
32289
|
+
form: "single",
|
|
32290
|
+
optional: false
|
|
32291
|
+
}],
|
|
32292
|
+
"networkQuality.reportClientStats": [{
|
|
32293
|
+
name: "deviceId",
|
|
32294
|
+
form: "single",
|
|
32295
|
+
optional: false
|
|
32296
|
+
}],
|
|
32297
|
+
"notificationRules.setDeviceMuted": [{
|
|
32298
|
+
name: "deviceId",
|
|
32299
|
+
form: "single",
|
|
32300
|
+
optional: false
|
|
32301
|
+
}],
|
|
32302
|
+
"notifier.cancel": [{
|
|
32303
|
+
name: "deviceId",
|
|
32304
|
+
form: "single",
|
|
32305
|
+
optional: false
|
|
32306
|
+
}],
|
|
32307
|
+
"notifier.send": [{
|
|
32308
|
+
name: "deviceId",
|
|
32309
|
+
form: "single",
|
|
32310
|
+
optional: false
|
|
32311
|
+
}],
|
|
32312
|
+
"osd.setOverlay": [{
|
|
32313
|
+
name: "deviceId",
|
|
32314
|
+
form: "single",
|
|
32315
|
+
optional: false
|
|
32316
|
+
}],
|
|
32317
|
+
"osdManager.clearSlotBinding": [{
|
|
32318
|
+
name: "deviceId",
|
|
32319
|
+
form: "single",
|
|
32320
|
+
optional: false
|
|
32321
|
+
}],
|
|
32322
|
+
"osdManager.copyDeviceConfiguration": [{
|
|
32323
|
+
name: "sourceDeviceId",
|
|
32324
|
+
form: "single",
|
|
32325
|
+
optional: false
|
|
32326
|
+
}, {
|
|
32327
|
+
name: "targetDeviceId",
|
|
32328
|
+
form: "single",
|
|
32329
|
+
optional: false
|
|
32330
|
+
}],
|
|
32331
|
+
"osdManager.getDeviceOsd": [{
|
|
32332
|
+
name: "deviceId",
|
|
32333
|
+
form: "single",
|
|
32334
|
+
optional: false
|
|
32335
|
+
}],
|
|
32336
|
+
"osdManager.getSourceCatalog": [{
|
|
32337
|
+
name: "deviceId",
|
|
32338
|
+
form: "single",
|
|
32339
|
+
optional: false
|
|
32340
|
+
}],
|
|
32341
|
+
"osdManager.previewSlot": [{
|
|
32342
|
+
name: "deviceId",
|
|
32343
|
+
form: "single",
|
|
32344
|
+
optional: false
|
|
32345
|
+
}],
|
|
32346
|
+
"osdManager.renderDevice": [{
|
|
32347
|
+
name: "deviceId",
|
|
32348
|
+
form: "single",
|
|
32349
|
+
optional: false
|
|
32350
|
+
}],
|
|
32351
|
+
"osdManager.setSlotBinding": [{
|
|
32352
|
+
name: "deviceId",
|
|
32353
|
+
form: "single",
|
|
32354
|
+
optional: false
|
|
32355
|
+
}],
|
|
32356
|
+
"petFeeder.callPet": [{
|
|
32357
|
+
name: "deviceId",
|
|
32358
|
+
form: "single",
|
|
32359
|
+
optional: false
|
|
32360
|
+
}],
|
|
32361
|
+
"petFeeder.cancelFeed": [{
|
|
32362
|
+
name: "deviceId",
|
|
32363
|
+
form: "single",
|
|
32364
|
+
optional: false
|
|
32365
|
+
}],
|
|
32366
|
+
"petFeeder.feed": [{
|
|
32367
|
+
name: "deviceId",
|
|
32368
|
+
form: "single",
|
|
32369
|
+
optional: false
|
|
32370
|
+
}],
|
|
32371
|
+
"petFeeder.markFoodReplenished": [{
|
|
32372
|
+
name: "deviceId",
|
|
32373
|
+
form: "single",
|
|
32374
|
+
optional: false
|
|
32375
|
+
}],
|
|
32376
|
+
"petFeeder.playSound": [{
|
|
32377
|
+
name: "deviceId",
|
|
32378
|
+
form: "single",
|
|
32379
|
+
optional: false
|
|
32380
|
+
}],
|
|
32381
|
+
"petFeeder.resetDesiccant": [{
|
|
32382
|
+
name: "deviceId",
|
|
32383
|
+
form: "single",
|
|
32384
|
+
optional: false
|
|
32385
|
+
}],
|
|
32386
|
+
"petFeeder.setChildLock": [{
|
|
32387
|
+
name: "deviceId",
|
|
32388
|
+
form: "single",
|
|
32389
|
+
optional: false
|
|
32390
|
+
}],
|
|
32391
|
+
"petFeeder.setFeedSound": [{
|
|
32392
|
+
name: "deviceId",
|
|
32393
|
+
form: "single",
|
|
32394
|
+
optional: false
|
|
32395
|
+
}],
|
|
32396
|
+
"petFeeder.setIndicatorLight": [{
|
|
32397
|
+
name: "deviceId",
|
|
32398
|
+
form: "single",
|
|
32399
|
+
optional: false
|
|
32400
|
+
}],
|
|
32401
|
+
"petFeeder.setVolume": [{
|
|
32402
|
+
name: "deviceId",
|
|
32403
|
+
form: "single",
|
|
32404
|
+
optional: false
|
|
32405
|
+
}],
|
|
32406
|
+
"pipelineAnalytics.clearTracks": [{
|
|
32407
|
+
name: "deviceId",
|
|
32408
|
+
form: "single",
|
|
32409
|
+
optional: false
|
|
32410
|
+
}],
|
|
32411
|
+
"pipelineAnalytics.completeRetrainTrack": [{
|
|
32412
|
+
name: "deviceId",
|
|
32413
|
+
form: "single",
|
|
32414
|
+
optional: false
|
|
32415
|
+
}],
|
|
32416
|
+
"pipelineAnalytics.deleteDeviceEvents": [{
|
|
32417
|
+
name: "deviceId",
|
|
32418
|
+
form: "single",
|
|
32419
|
+
optional: false
|
|
32420
|
+
}],
|
|
32421
|
+
"pipelineAnalytics.deleteTracks": [{
|
|
32422
|
+
name: "deviceId",
|
|
32423
|
+
form: "single",
|
|
32424
|
+
optional: false
|
|
32425
|
+
}],
|
|
32426
|
+
"pipelineAnalytics.deselectRetrainFrame": [{
|
|
32427
|
+
name: "deviceId",
|
|
32428
|
+
form: "single",
|
|
32429
|
+
optional: false
|
|
32430
|
+
}],
|
|
32431
|
+
"pipelineAnalytics.getActiveTracks": [{
|
|
32432
|
+
name: "deviceId",
|
|
32433
|
+
form: "single",
|
|
32434
|
+
optional: false
|
|
32435
|
+
}],
|
|
32436
|
+
"pipelineAnalytics.getAudioEvents": [{
|
|
32437
|
+
name: "deviceId",
|
|
32438
|
+
form: "single",
|
|
32439
|
+
optional: false
|
|
32440
|
+
}],
|
|
32441
|
+
"pipelineAnalytics.getEventDensity": [{
|
|
32442
|
+
name: "deviceId",
|
|
32443
|
+
form: "single",
|
|
32444
|
+
optional: false
|
|
32445
|
+
}],
|
|
32446
|
+
"pipelineAnalytics.getKeyEvents": [{
|
|
32447
|
+
name: "deviceId",
|
|
32448
|
+
form: "single",
|
|
32449
|
+
optional: false
|
|
32450
|
+
}],
|
|
32451
|
+
"pipelineAnalytics.getMotionEvents": [{
|
|
32452
|
+
name: "deviceId",
|
|
32453
|
+
form: "single",
|
|
32454
|
+
optional: false
|
|
32455
|
+
}],
|
|
32456
|
+
"pipelineAnalytics.getObjectEvents": [{
|
|
32457
|
+
name: "deviceId",
|
|
32458
|
+
form: "single",
|
|
32459
|
+
optional: false
|
|
32460
|
+
}],
|
|
32461
|
+
"pipelineAnalytics.getRetrainExportUrl": [{
|
|
32462
|
+
name: "deviceIds",
|
|
32463
|
+
form: "array",
|
|
32464
|
+
optional: true
|
|
32465
|
+
}],
|
|
32466
|
+
"pipelineAnalytics.getSensorEvents": [{
|
|
32467
|
+
name: "deviceId",
|
|
32468
|
+
form: "single",
|
|
32469
|
+
optional: false
|
|
32470
|
+
}],
|
|
32471
|
+
"pipelineAnalytics.getTrack": [{
|
|
32472
|
+
name: "deviceId",
|
|
32473
|
+
form: "single",
|
|
32474
|
+
optional: false
|
|
32475
|
+
}],
|
|
32476
|
+
"pipelineAnalytics.getTrainingExportSummary": [{
|
|
32477
|
+
name: "deviceIds",
|
|
32478
|
+
form: "array",
|
|
32479
|
+
optional: true
|
|
32480
|
+
}],
|
|
32481
|
+
"pipelineAnalytics.getTrainingExportUrl": [{
|
|
32482
|
+
name: "deviceIds",
|
|
32483
|
+
form: "array",
|
|
32484
|
+
optional: true
|
|
32485
|
+
}],
|
|
32486
|
+
"pipelineAnalytics.listEventKinds": [{
|
|
32487
|
+
name: "deviceId",
|
|
32488
|
+
form: "single",
|
|
32489
|
+
optional: false
|
|
32490
|
+
}],
|
|
32491
|
+
"pipelineAnalytics.listEventKindsBatch": [{
|
|
32492
|
+
name: "deviceIds",
|
|
32493
|
+
form: "array",
|
|
32494
|
+
optional: false
|
|
32495
|
+
}],
|
|
32496
|
+
"pipelineAnalytics.listOpsLog": [{
|
|
32497
|
+
name: "deviceId",
|
|
32498
|
+
form: "single",
|
|
32499
|
+
optional: true
|
|
32500
|
+
}],
|
|
32501
|
+
"pipelineAnalytics.listRecentTracks": [{
|
|
32502
|
+
name: "deviceIds",
|
|
32503
|
+
form: "array",
|
|
32504
|
+
optional: false
|
|
32505
|
+
}],
|
|
32506
|
+
"pipelineAnalytics.listRetrainStaging": [{
|
|
32507
|
+
name: "deviceIds",
|
|
32508
|
+
form: "array",
|
|
32509
|
+
optional: true
|
|
32510
|
+
}],
|
|
32511
|
+
"pipelineAnalytics.listTracks": [{
|
|
32512
|
+
name: "deviceId",
|
|
32513
|
+
form: "single",
|
|
32514
|
+
optional: false
|
|
32515
|
+
}],
|
|
32516
|
+
"pipelineAnalytics.proposeRetrainAnnotations": [{
|
|
32517
|
+
name: "deviceId",
|
|
32518
|
+
form: "single",
|
|
32519
|
+
optional: false
|
|
32520
|
+
}],
|
|
32521
|
+
"pipelineAnalytics.pruneEventsBefore": [{
|
|
32522
|
+
name: "deviceId",
|
|
32523
|
+
form: "single",
|
|
32524
|
+
optional: false
|
|
32525
|
+
}],
|
|
32526
|
+
"pipelineAnalytics.pruneTracksBefore": [{
|
|
32527
|
+
name: "deviceId",
|
|
32528
|
+
form: "single",
|
|
32529
|
+
optional: false
|
|
32530
|
+
}],
|
|
32531
|
+
"pipelineAnalytics.rebuildObjectEmbeddings": [{
|
|
32532
|
+
name: "deviceId",
|
|
32533
|
+
form: "single",
|
|
32534
|
+
optional: true
|
|
32535
|
+
}],
|
|
32536
|
+
"pipelineAnalytics.restageRetrainTrack": [{
|
|
32537
|
+
name: "deviceId",
|
|
32538
|
+
form: "single",
|
|
32539
|
+
optional: false
|
|
32540
|
+
}],
|
|
32541
|
+
"pipelineAnalytics.saveRetrainAnnotations": [{
|
|
32542
|
+
name: "deviceId",
|
|
32543
|
+
form: "single",
|
|
32544
|
+
optional: false
|
|
32545
|
+
}],
|
|
32546
|
+
"pipelineAnalytics.searchObjectEvents": [{
|
|
32547
|
+
name: "deviceId",
|
|
32548
|
+
form: "single",
|
|
32549
|
+
optional: true
|
|
32550
|
+
}],
|
|
32551
|
+
"pipelineAnalytics.selectRetrainFrames": [{
|
|
32552
|
+
name: "deviceId",
|
|
32553
|
+
form: "single",
|
|
32554
|
+
optional: false
|
|
32555
|
+
}],
|
|
32556
|
+
"pipelineAnalytics.setTrackFlags": [{
|
|
32557
|
+
name: "deviceId",
|
|
32558
|
+
form: "single",
|
|
32559
|
+
optional: false
|
|
32560
|
+
}],
|
|
32561
|
+
"pipelineAnalytics.wipeAllAnalytics": [{
|
|
32562
|
+
name: "deviceId",
|
|
32563
|
+
form: "single",
|
|
32564
|
+
optional: false
|
|
32565
|
+
}],
|
|
32566
|
+
"pipelineExecutor.runPipeline": [{
|
|
32567
|
+
name: "deviceId",
|
|
32568
|
+
form: "single",
|
|
32569
|
+
optional: true
|
|
32570
|
+
}],
|
|
32571
|
+
"pipelineExecutor.runPipelineBatch": [{
|
|
32572
|
+
name: "deviceId",
|
|
32573
|
+
form: "single",
|
|
32574
|
+
optional: true
|
|
32575
|
+
}],
|
|
32576
|
+
"pipelineOrchestrator.assignAudio": [{
|
|
32577
|
+
name: "deviceId",
|
|
32578
|
+
form: "single",
|
|
32579
|
+
optional: false
|
|
32580
|
+
}],
|
|
32581
|
+
"pipelineOrchestrator.assignPipeline": [{
|
|
32582
|
+
name: "deviceId",
|
|
32583
|
+
form: "single",
|
|
32584
|
+
optional: false
|
|
32585
|
+
}],
|
|
32586
|
+
"pipelineOrchestrator.getAudioAssignment": [{
|
|
32587
|
+
name: "deviceId",
|
|
32588
|
+
form: "single",
|
|
32589
|
+
optional: false
|
|
32590
|
+
}],
|
|
32591
|
+
"pipelineOrchestrator.getCameraMetrics": [{
|
|
32592
|
+
name: "deviceId",
|
|
32593
|
+
form: "single",
|
|
32594
|
+
optional: false
|
|
32595
|
+
}],
|
|
32596
|
+
"pipelineOrchestrator.getCameraSettings": [{
|
|
32597
|
+
name: "deviceId",
|
|
32598
|
+
form: "single",
|
|
32599
|
+
optional: false
|
|
32600
|
+
}],
|
|
32601
|
+
"pipelineOrchestrator.getCameraStatus": [{
|
|
32602
|
+
name: "deviceId",
|
|
32603
|
+
form: "single",
|
|
32604
|
+
optional: false
|
|
32605
|
+
}],
|
|
32606
|
+
"pipelineOrchestrator.getCameraStatuses": [{
|
|
32607
|
+
name: "deviceIds",
|
|
32608
|
+
form: "array",
|
|
32609
|
+
optional: true
|
|
32610
|
+
}],
|
|
32611
|
+
"pipelineOrchestrator.getCameraStepOverrides": [{
|
|
32612
|
+
name: "deviceId",
|
|
32613
|
+
form: "single",
|
|
32614
|
+
optional: false
|
|
32615
|
+
}],
|
|
32616
|
+
"pipelineOrchestrator.getCameraSwitches": [{
|
|
32617
|
+
name: "deviceId",
|
|
32618
|
+
form: "single",
|
|
32619
|
+
optional: false
|
|
32620
|
+
}],
|
|
32621
|
+
"pipelineOrchestrator.getPipelineAssignment": [{
|
|
32622
|
+
name: "deviceId",
|
|
32623
|
+
form: "single",
|
|
32624
|
+
optional: false
|
|
32625
|
+
}],
|
|
32626
|
+
"pipelineOrchestrator.getPipelineDevicePin": [{
|
|
32627
|
+
name: "deviceId",
|
|
32628
|
+
form: "single",
|
|
32629
|
+
optional: false
|
|
32630
|
+
}],
|
|
32631
|
+
"pipelineOrchestrator.resolvePipeline": [{
|
|
32632
|
+
name: "deviceId",
|
|
32633
|
+
form: "single",
|
|
32634
|
+
optional: false
|
|
32635
|
+
}],
|
|
32636
|
+
"pipelineOrchestrator.setCameraPipelineForAgent": [{
|
|
32637
|
+
name: "deviceId",
|
|
32638
|
+
form: "single",
|
|
32639
|
+
optional: false
|
|
32640
|
+
}],
|
|
32641
|
+
"pipelineOrchestrator.setCameraStepOverride": [{
|
|
32642
|
+
name: "deviceId",
|
|
32643
|
+
form: "single",
|
|
32644
|
+
optional: false
|
|
32645
|
+
}],
|
|
32646
|
+
"pipelineOrchestrator.setCameraStepToggle": [{
|
|
32647
|
+
name: "deviceId",
|
|
32648
|
+
form: "single",
|
|
32649
|
+
optional: false
|
|
32650
|
+
}],
|
|
32651
|
+
"pipelineOrchestrator.setCameraSwitch": [{
|
|
32652
|
+
name: "deviceId",
|
|
32653
|
+
form: "single",
|
|
32654
|
+
optional: false
|
|
32655
|
+
}],
|
|
32656
|
+
"pipelineOrchestrator.setPipelineDevicePin": [{
|
|
32657
|
+
name: "deviceId",
|
|
32658
|
+
form: "single",
|
|
32659
|
+
optional: false
|
|
32660
|
+
}],
|
|
32661
|
+
"pipelineOrchestrator.unassignAudio": [{
|
|
32662
|
+
name: "deviceId",
|
|
32663
|
+
form: "single",
|
|
32664
|
+
optional: false
|
|
32665
|
+
}],
|
|
32666
|
+
"pipelineOrchestrator.unassignPipeline": [{
|
|
32667
|
+
name: "deviceId",
|
|
32668
|
+
form: "single",
|
|
32669
|
+
optional: false
|
|
32670
|
+
}],
|
|
32671
|
+
"pipelineRunner.attachCamera": [{
|
|
32672
|
+
name: "deviceId",
|
|
32673
|
+
form: "single",
|
|
32674
|
+
optional: false
|
|
32675
|
+
}],
|
|
32676
|
+
"pipelineRunner.detachCamera": [{
|
|
32677
|
+
name: "deviceId",
|
|
32678
|
+
form: "single",
|
|
32679
|
+
optional: false
|
|
32680
|
+
}],
|
|
32681
|
+
"pipelineRunner.getCameraMetrics": [{
|
|
32682
|
+
name: "deviceId",
|
|
32683
|
+
form: "single",
|
|
32684
|
+
optional: false
|
|
32685
|
+
}],
|
|
32686
|
+
"pipelineRunner.reportMotion": [{
|
|
32687
|
+
name: "deviceId",
|
|
32688
|
+
form: "single",
|
|
32689
|
+
optional: false
|
|
32690
|
+
}],
|
|
32691
|
+
"pipelineRunner.runDetailSubtree": [{
|
|
32692
|
+
name: "deviceId",
|
|
32693
|
+
form: "single",
|
|
32694
|
+
optional: false
|
|
32695
|
+
}],
|
|
32696
|
+
"pipelineRunner.runStatelessStep": [{
|
|
32697
|
+
name: "sourceDeviceId",
|
|
32698
|
+
form: "single",
|
|
32699
|
+
optional: false
|
|
32700
|
+
}],
|
|
32701
|
+
"plateGallery.getPlateByTrack": [{
|
|
32702
|
+
name: "deviceId",
|
|
32703
|
+
form: "single",
|
|
32704
|
+
optional: false
|
|
32705
|
+
}],
|
|
32706
|
+
"plateGallery.listPlates": [{
|
|
32707
|
+
name: "deviceId",
|
|
32708
|
+
form: "single",
|
|
32709
|
+
optional: true
|
|
32710
|
+
}],
|
|
32711
|
+
"privacyMask.getOptions": [{
|
|
32712
|
+
name: "deviceId",
|
|
32713
|
+
form: "single",
|
|
32714
|
+
optional: false
|
|
32715
|
+
}],
|
|
32716
|
+
"privacyMask.setAudioEnabled": [{
|
|
32717
|
+
name: "deviceId",
|
|
32718
|
+
form: "single",
|
|
32719
|
+
optional: false
|
|
32720
|
+
}],
|
|
32721
|
+
"privacyMask.setMask": [{
|
|
32722
|
+
name: "deviceId",
|
|
32723
|
+
form: "single",
|
|
32724
|
+
optional: false
|
|
32725
|
+
}],
|
|
32726
|
+
"ptz.continuousMove": [{
|
|
32727
|
+
name: "deviceId",
|
|
32728
|
+
form: "single",
|
|
32729
|
+
optional: false
|
|
32730
|
+
}],
|
|
32731
|
+
"ptz.deletePreset": [{
|
|
32732
|
+
name: "deviceId",
|
|
32733
|
+
form: "single",
|
|
32734
|
+
optional: false
|
|
32735
|
+
}],
|
|
32736
|
+
"ptz.getOptions": [{
|
|
32737
|
+
name: "deviceId",
|
|
32738
|
+
form: "single",
|
|
32739
|
+
optional: false
|
|
32740
|
+
}],
|
|
32741
|
+
"ptz.getPosition": [{
|
|
32742
|
+
name: "deviceId",
|
|
32743
|
+
form: "single",
|
|
32744
|
+
optional: false
|
|
32745
|
+
}],
|
|
32746
|
+
"ptz.getPresets": [{
|
|
32747
|
+
name: "deviceId",
|
|
32748
|
+
form: "single",
|
|
32749
|
+
optional: false
|
|
32750
|
+
}],
|
|
32751
|
+
"ptz.goHome": [{
|
|
32752
|
+
name: "deviceId",
|
|
32753
|
+
form: "single",
|
|
32754
|
+
optional: false
|
|
32755
|
+
}],
|
|
32756
|
+
"ptz.goToPreset": [{
|
|
32757
|
+
name: "deviceId",
|
|
32758
|
+
form: "single",
|
|
32759
|
+
optional: false
|
|
32760
|
+
}],
|
|
32761
|
+
"ptz.move": [{
|
|
32762
|
+
name: "deviceId",
|
|
32763
|
+
form: "single",
|
|
32764
|
+
optional: false
|
|
32765
|
+
}],
|
|
32766
|
+
"ptz.savePreset": [{
|
|
32767
|
+
name: "deviceId",
|
|
32768
|
+
form: "single",
|
|
32769
|
+
optional: false
|
|
32770
|
+
}],
|
|
32771
|
+
"ptz.setAutofocus": [{
|
|
32772
|
+
name: "deviceId",
|
|
32773
|
+
form: "single",
|
|
32774
|
+
optional: false
|
|
32775
|
+
}],
|
|
32776
|
+
"ptz.stop": [{
|
|
32777
|
+
name: "deviceId",
|
|
32778
|
+
form: "single",
|
|
32779
|
+
optional: false
|
|
32780
|
+
}],
|
|
32781
|
+
"ptzAutotrack.getSettings": [{
|
|
32782
|
+
name: "deviceId",
|
|
32783
|
+
form: "single",
|
|
32784
|
+
optional: false
|
|
32785
|
+
}],
|
|
32786
|
+
"ptzAutotrack.getStatus": [{
|
|
32787
|
+
name: "deviceId",
|
|
32788
|
+
form: "single",
|
|
32789
|
+
optional: false
|
|
32790
|
+
}],
|
|
32791
|
+
"ptzAutotrack.setEnabled": [{
|
|
32792
|
+
name: "deviceId",
|
|
32793
|
+
form: "single",
|
|
32794
|
+
optional: false
|
|
32795
|
+
}],
|
|
32796
|
+
"ptzAutotrack.setSettings": [{
|
|
32797
|
+
name: "deviceId",
|
|
32798
|
+
form: "single",
|
|
32799
|
+
optional: false
|
|
32800
|
+
}],
|
|
32801
|
+
"reboot.reboot": [{
|
|
32802
|
+
name: "deviceId",
|
|
32803
|
+
form: "single",
|
|
32804
|
+
optional: false
|
|
32805
|
+
}],
|
|
32806
|
+
"recording.deleteFootprint": [{
|
|
32807
|
+
name: "deviceId",
|
|
32808
|
+
form: "single",
|
|
32809
|
+
optional: false
|
|
32810
|
+
}],
|
|
32811
|
+
"recording.getAvailability": [{
|
|
32812
|
+
name: "deviceId",
|
|
32813
|
+
form: "single",
|
|
32814
|
+
optional: false
|
|
32815
|
+
}],
|
|
32816
|
+
"recording.getDaysWithRecordings": [{
|
|
32817
|
+
name: "deviceId",
|
|
32818
|
+
form: "single",
|
|
32819
|
+
optional: false
|
|
32820
|
+
}],
|
|
32821
|
+
"recording.getDeviceConfig": [{
|
|
32822
|
+
name: "deviceId",
|
|
32823
|
+
form: "single",
|
|
32824
|
+
optional: false
|
|
32825
|
+
}],
|
|
32826
|
+
"recording.getPlaybackManifest": [{
|
|
32827
|
+
name: "deviceId",
|
|
32828
|
+
form: "single",
|
|
32829
|
+
optional: false
|
|
32830
|
+
}],
|
|
32831
|
+
"recording.listOpsLog": [{
|
|
32832
|
+
name: "deviceId",
|
|
32833
|
+
form: "single",
|
|
32834
|
+
optional: true
|
|
32835
|
+
}],
|
|
32836
|
+
"recording.locateSegment": [{
|
|
32837
|
+
name: "deviceId",
|
|
32838
|
+
form: "single",
|
|
32839
|
+
optional: false
|
|
32840
|
+
}],
|
|
32841
|
+
"recording.pruneFootage": [{
|
|
32842
|
+
name: "deviceId",
|
|
32843
|
+
form: "single",
|
|
32844
|
+
optional: false
|
|
32845
|
+
}],
|
|
32846
|
+
"recording.readGopBytes": [{
|
|
32847
|
+
name: "deviceId",
|
|
32848
|
+
form: "single",
|
|
32849
|
+
optional: false
|
|
32850
|
+
}],
|
|
32851
|
+
"recording.readSegmentBytes": [{
|
|
32852
|
+
name: "deviceId",
|
|
32853
|
+
form: "single",
|
|
32854
|
+
optional: false
|
|
32855
|
+
}],
|
|
32856
|
+
"recording.relocateFootage": [{
|
|
32857
|
+
name: "deviceId",
|
|
32858
|
+
form: "single",
|
|
32859
|
+
optional: true
|
|
32860
|
+
}],
|
|
32861
|
+
"recording.renderClip": [{
|
|
32862
|
+
name: "deviceId",
|
|
32863
|
+
form: "single",
|
|
32864
|
+
optional: false
|
|
32865
|
+
}],
|
|
32866
|
+
"recording.renderGif": [{
|
|
32867
|
+
name: "deviceId",
|
|
32868
|
+
form: "single",
|
|
32869
|
+
optional: false
|
|
32870
|
+
}],
|
|
32871
|
+
"recording.rescanStorage": [{
|
|
32872
|
+
name: "deviceId",
|
|
32873
|
+
form: "single",
|
|
32874
|
+
optional: false
|
|
32875
|
+
}],
|
|
32876
|
+
"recording.setDeviceConfig": [{
|
|
32877
|
+
name: "deviceId",
|
|
32878
|
+
form: "single",
|
|
32879
|
+
optional: false
|
|
32880
|
+
}],
|
|
32881
|
+
"recording.startStorageMigrationMove": [{
|
|
32882
|
+
name: "deviceId",
|
|
32883
|
+
form: "single",
|
|
32884
|
+
optional: true
|
|
32885
|
+
}],
|
|
32886
|
+
"recordingExport.createExport": [{
|
|
32887
|
+
name: "deviceId",
|
|
32888
|
+
form: "single",
|
|
32889
|
+
optional: false
|
|
32890
|
+
}],
|
|
32891
|
+
"recordingExport.listExports": [{
|
|
32892
|
+
name: "deviceId",
|
|
32893
|
+
form: "single",
|
|
32894
|
+
optional: true
|
|
32895
|
+
}],
|
|
32896
|
+
"sceneMonitor.captureReference": [{
|
|
32897
|
+
name: "deviceId",
|
|
32898
|
+
form: "single",
|
|
32899
|
+
optional: false
|
|
32900
|
+
}],
|
|
32901
|
+
"sceneMonitor.createScene": [{
|
|
32902
|
+
name: "deviceId",
|
|
32903
|
+
form: "single",
|
|
32904
|
+
optional: false
|
|
32905
|
+
}],
|
|
32906
|
+
"sceneMonitor.deleteReference": [{
|
|
32907
|
+
name: "deviceId",
|
|
32908
|
+
form: "single",
|
|
32909
|
+
optional: false
|
|
32910
|
+
}],
|
|
32911
|
+
"sceneMonitor.deleteScene": [{
|
|
32912
|
+
name: "deviceId",
|
|
32913
|
+
form: "single",
|
|
32914
|
+
optional: false
|
|
32915
|
+
}],
|
|
32916
|
+
"sceneMonitor.listScenes": [{
|
|
32917
|
+
name: "deviceId",
|
|
32918
|
+
form: "single",
|
|
32919
|
+
optional: false
|
|
32920
|
+
}],
|
|
32921
|
+
"sceneMonitor.recheckNow": [{
|
|
32922
|
+
name: "deviceId",
|
|
32923
|
+
form: "single",
|
|
32924
|
+
optional: false
|
|
32925
|
+
}],
|
|
32926
|
+
"sceneMonitor.updateScene": [{
|
|
32927
|
+
name: "deviceId",
|
|
32928
|
+
form: "single",
|
|
32929
|
+
optional: false
|
|
32930
|
+
}],
|
|
32931
|
+
"scriptRunner.run": [{
|
|
32932
|
+
name: "deviceId",
|
|
32933
|
+
form: "single",
|
|
32934
|
+
optional: false
|
|
32935
|
+
}],
|
|
32936
|
+
"scriptRunner.stop": [{
|
|
32937
|
+
name: "deviceId",
|
|
32938
|
+
form: "single",
|
|
32939
|
+
optional: false
|
|
32940
|
+
}],
|
|
32941
|
+
"snapshot.getSnapshot": [{
|
|
32942
|
+
name: "deviceId",
|
|
32943
|
+
form: "single",
|
|
32944
|
+
optional: false
|
|
32945
|
+
}],
|
|
32946
|
+
"snapshot.getSnapshotOverview": [{
|
|
32947
|
+
name: "deviceIds",
|
|
32948
|
+
form: "array",
|
|
32949
|
+
optional: false
|
|
32950
|
+
}],
|
|
32951
|
+
"snapshot.invalidateCache": [{
|
|
32952
|
+
name: "deviceId",
|
|
32953
|
+
form: "single",
|
|
32954
|
+
optional: false
|
|
32955
|
+
}],
|
|
32956
|
+
"streamBroker.acquireEgressTranscode": [{
|
|
32957
|
+
name: "deviceId",
|
|
32958
|
+
form: "single",
|
|
32959
|
+
optional: false
|
|
32960
|
+
}],
|
|
32961
|
+
"streamBroker.assignProfile": [{
|
|
32962
|
+
name: "deviceId",
|
|
32963
|
+
form: "single",
|
|
32964
|
+
optional: false
|
|
32965
|
+
}],
|
|
32966
|
+
"streamBroker.getDeviceAudioMute": [{
|
|
32967
|
+
name: "deviceId",
|
|
32968
|
+
form: "single",
|
|
32969
|
+
optional: false
|
|
32970
|
+
}],
|
|
32971
|
+
"streamBroker.getStreamWithCodec": [{
|
|
32972
|
+
name: "deviceId",
|
|
32973
|
+
form: "single",
|
|
32974
|
+
optional: false
|
|
32975
|
+
}],
|
|
32976
|
+
"streamBroker.produceEventMedia": [{
|
|
32977
|
+
name: "deviceId",
|
|
32978
|
+
form: "single",
|
|
32979
|
+
optional: false
|
|
32980
|
+
}],
|
|
32981
|
+
"streamBroker.publishCameraStream": [{
|
|
32982
|
+
name: "deviceId",
|
|
32983
|
+
form: "single",
|
|
32984
|
+
optional: false
|
|
32985
|
+
}],
|
|
32986
|
+
"streamBroker.renderPreBufferClip": [{
|
|
32987
|
+
name: "deviceId",
|
|
32988
|
+
form: "single",
|
|
32989
|
+
optional: false
|
|
32990
|
+
}],
|
|
32991
|
+
"streamBroker.restartProfile": [{
|
|
32992
|
+
name: "deviceId",
|
|
32993
|
+
form: "single",
|
|
32994
|
+
optional: false
|
|
32995
|
+
}],
|
|
32996
|
+
"streamBroker.retractCameraStream": [{
|
|
32997
|
+
name: "deviceId",
|
|
32998
|
+
form: "single",
|
|
32999
|
+
optional: false
|
|
33000
|
+
}],
|
|
33001
|
+
"streamBroker.setDeviceAudioMute": [{
|
|
33002
|
+
name: "deviceId",
|
|
33003
|
+
form: "single",
|
|
33004
|
+
optional: false
|
|
33005
|
+
}],
|
|
33006
|
+
"streamBroker.unassignProfile": [{
|
|
33007
|
+
name: "deviceId",
|
|
33008
|
+
form: "single",
|
|
33009
|
+
optional: false
|
|
33010
|
+
}],
|
|
33011
|
+
"streamCatalog.getCatalog": [{
|
|
33012
|
+
name: "deviceId",
|
|
33013
|
+
form: "single",
|
|
33014
|
+
optional: false
|
|
33015
|
+
}],
|
|
33016
|
+
"streamParams.getConfigSchema": [{
|
|
33017
|
+
name: "deviceId",
|
|
33018
|
+
form: "single",
|
|
33019
|
+
optional: false
|
|
33020
|
+
}],
|
|
33021
|
+
"streamParams.getOptions": [{
|
|
33022
|
+
name: "deviceId",
|
|
33023
|
+
form: "single",
|
|
33024
|
+
optional: false
|
|
33025
|
+
}],
|
|
33026
|
+
"streamParams.setProfile": [{
|
|
33027
|
+
name: "deviceId",
|
|
33028
|
+
form: "single",
|
|
33029
|
+
optional: false
|
|
33030
|
+
}],
|
|
33031
|
+
"switch.setState": [{
|
|
33032
|
+
name: "deviceId",
|
|
33033
|
+
form: "single",
|
|
33034
|
+
optional: false
|
|
33035
|
+
}],
|
|
33036
|
+
"vacuumControl.locate": [{
|
|
33037
|
+
name: "deviceId",
|
|
33038
|
+
form: "single",
|
|
33039
|
+
optional: false
|
|
33040
|
+
}],
|
|
33041
|
+
"vacuumControl.pause": [{
|
|
33042
|
+
name: "deviceId",
|
|
33043
|
+
form: "single",
|
|
33044
|
+
optional: false
|
|
33045
|
+
}],
|
|
33046
|
+
"vacuumControl.returnToBase": [{
|
|
33047
|
+
name: "deviceId",
|
|
33048
|
+
form: "single",
|
|
33049
|
+
optional: false
|
|
33050
|
+
}],
|
|
33051
|
+
"vacuumControl.setFanSpeed": [{
|
|
33052
|
+
name: "deviceId",
|
|
33053
|
+
form: "single",
|
|
33054
|
+
optional: false
|
|
33055
|
+
}],
|
|
33056
|
+
"vacuumControl.start": [{
|
|
33057
|
+
name: "deviceId",
|
|
33058
|
+
form: "single",
|
|
33059
|
+
optional: false
|
|
33060
|
+
}],
|
|
33061
|
+
"vacuumControl.stop": [{
|
|
33062
|
+
name: "deviceId",
|
|
33063
|
+
form: "single",
|
|
33064
|
+
optional: false
|
|
33065
|
+
}],
|
|
33066
|
+
"valve.close": [{
|
|
33067
|
+
name: "deviceId",
|
|
33068
|
+
form: "single",
|
|
33069
|
+
optional: false
|
|
33070
|
+
}],
|
|
33071
|
+
"valve.open": [{
|
|
33072
|
+
name: "deviceId",
|
|
33073
|
+
form: "single",
|
|
33074
|
+
optional: false
|
|
33075
|
+
}],
|
|
33076
|
+
"valve.setPosition": [{
|
|
33077
|
+
name: "deviceId",
|
|
33078
|
+
form: "single",
|
|
33079
|
+
optional: false
|
|
33080
|
+
}],
|
|
33081
|
+
"valve.stop": [{
|
|
33082
|
+
name: "deviceId",
|
|
33083
|
+
form: "single",
|
|
33084
|
+
optional: false
|
|
33085
|
+
}],
|
|
33086
|
+
"videoclips.getClipPlayback": [{
|
|
33087
|
+
name: "deviceId",
|
|
33088
|
+
form: "single",
|
|
33089
|
+
optional: false
|
|
33090
|
+
}],
|
|
33091
|
+
"videoclips.listClips": [{
|
|
33092
|
+
name: "deviceId",
|
|
33093
|
+
form: "single",
|
|
33094
|
+
optional: false
|
|
33095
|
+
}],
|
|
33096
|
+
"waterHeater.setAway": [{
|
|
33097
|
+
name: "deviceId",
|
|
33098
|
+
form: "single",
|
|
33099
|
+
optional: false
|
|
33100
|
+
}],
|
|
33101
|
+
"waterHeater.setOperationMode": [{
|
|
33102
|
+
name: "deviceId",
|
|
33103
|
+
form: "single",
|
|
33104
|
+
optional: false
|
|
33105
|
+
}],
|
|
33106
|
+
"waterHeater.setTargetTemp": [{
|
|
33107
|
+
name: "deviceId",
|
|
33108
|
+
form: "single",
|
|
33109
|
+
optional: false
|
|
33110
|
+
}],
|
|
33111
|
+
"webrtcSession.addIceCandidate": [{
|
|
33112
|
+
name: "deviceId",
|
|
33113
|
+
form: "single",
|
|
33114
|
+
optional: false
|
|
33115
|
+
}],
|
|
33116
|
+
"webrtcSession.closeSession": [{
|
|
33117
|
+
name: "deviceId",
|
|
33118
|
+
form: "single",
|
|
33119
|
+
optional: false
|
|
33120
|
+
}],
|
|
33121
|
+
"webrtcSession.createSession": [{
|
|
33122
|
+
name: "deviceId",
|
|
33123
|
+
form: "single",
|
|
33124
|
+
optional: false
|
|
33125
|
+
}],
|
|
33126
|
+
"webrtcSession.getIceCandidates": [{
|
|
33127
|
+
name: "deviceId",
|
|
33128
|
+
form: "single",
|
|
33129
|
+
optional: false
|
|
33130
|
+
}],
|
|
33131
|
+
"webrtcSession.getSessionState": [{
|
|
33132
|
+
name: "deviceId",
|
|
33133
|
+
form: "single",
|
|
33134
|
+
optional: false
|
|
33135
|
+
}],
|
|
33136
|
+
"webrtcSession.handleAnswer": [{
|
|
33137
|
+
name: "deviceId",
|
|
33138
|
+
form: "single",
|
|
33139
|
+
optional: false
|
|
33140
|
+
}],
|
|
33141
|
+
"webrtcSession.handleOffer": [{
|
|
33142
|
+
name: "deviceId",
|
|
33143
|
+
form: "single",
|
|
33144
|
+
optional: false
|
|
33145
|
+
}],
|
|
33146
|
+
"webrtcSession.hasAdaptiveBitrate": [{
|
|
33147
|
+
name: "deviceId",
|
|
33148
|
+
form: "single",
|
|
33149
|
+
optional: false
|
|
33150
|
+
}],
|
|
33151
|
+
"webrtcSession.listStreams": [{
|
|
33152
|
+
name: "deviceId",
|
|
33153
|
+
form: "single",
|
|
33154
|
+
optional: false
|
|
33155
|
+
}],
|
|
33156
|
+
"zoneAnalytics.getCameraHistory": [{
|
|
33157
|
+
name: "deviceId",
|
|
33158
|
+
form: "single",
|
|
33159
|
+
optional: false
|
|
33160
|
+
}],
|
|
33161
|
+
"zoneAnalytics.getCurrentSnapshot": [{
|
|
33162
|
+
name: "deviceId",
|
|
33163
|
+
form: "single",
|
|
33164
|
+
optional: false
|
|
33165
|
+
}],
|
|
33166
|
+
"zoneAnalytics.getUnzonedHistory": [{
|
|
33167
|
+
name: "deviceId",
|
|
33168
|
+
form: "single",
|
|
33169
|
+
optional: false
|
|
33170
|
+
}],
|
|
33171
|
+
"zoneAnalytics.getZoneHistory": [{
|
|
33172
|
+
name: "deviceId",
|
|
33173
|
+
form: "single",
|
|
33174
|
+
optional: false
|
|
33175
|
+
}],
|
|
33176
|
+
"zoneRules.listRules": [{
|
|
33177
|
+
name: "deviceId",
|
|
33178
|
+
form: "single",
|
|
33179
|
+
optional: false
|
|
33180
|
+
}],
|
|
33181
|
+
"zoneRules.setRules": [{
|
|
33182
|
+
name: "deviceId",
|
|
33183
|
+
form: "single",
|
|
33184
|
+
optional: false
|
|
33185
|
+
}],
|
|
33186
|
+
"zones.addZone": [{
|
|
33187
|
+
name: "deviceId",
|
|
33188
|
+
form: "single",
|
|
33189
|
+
optional: false
|
|
33190
|
+
}],
|
|
33191
|
+
"zones.listZones": [{
|
|
33192
|
+
name: "deviceId",
|
|
33193
|
+
form: "single",
|
|
33194
|
+
optional: false
|
|
33195
|
+
}],
|
|
33196
|
+
"zones.removeZone": [{
|
|
33197
|
+
name: "deviceId",
|
|
33198
|
+
form: "single",
|
|
33199
|
+
optional: false
|
|
33200
|
+
}],
|
|
33201
|
+
"zones.updateZone": [{
|
|
33202
|
+
name: "deviceId",
|
|
33203
|
+
form: "single",
|
|
33204
|
+
optional: false
|
|
33205
|
+
}]
|
|
33206
|
+
});
|
|
31310
33207
|
Object.freeze({
|
|
31311
33208
|
"broker": "broker",
|
|
31312
33209
|
"device-export": "device-export",
|
|
@@ -32026,7 +33923,7 @@ var Fmp4FragmentChild = class {
|
|
|
32026
33923
|
meta: {
|
|
32027
33924
|
sourceId: this.args.sourceId,
|
|
32028
33925
|
decodeHwAccel: requested,
|
|
32029
|
-
error: errMsg$
|
|
33926
|
+
error: errMsg$15(err)
|
|
32030
33927
|
}
|
|
32031
33928
|
});
|
|
32032
33929
|
this.killChild();
|
|
@@ -32205,7 +34102,7 @@ var Fmp4FragmentChild = class {
|
|
|
32205
34102
|
tags: { deviceId: this.args.deviceId },
|
|
32206
34103
|
meta: {
|
|
32207
34104
|
sourceId: this.args.sourceId,
|
|
32208
|
-
error: errMsg$
|
|
34105
|
+
error: errMsg$15(err)
|
|
32209
34106
|
}
|
|
32210
34107
|
});
|
|
32211
34108
|
}
|
|
@@ -78550,7 +80447,7 @@ function clearPairingFiles(accessoryUuid, logger) {
|
|
|
78550
80447
|
}
|
|
78551
80448
|
//#endregion
|
|
78552
80449
|
//#region src/hap-setup-uri.ts
|
|
78553
|
-
function errMsg$
|
|
80450
|
+
function errMsg$14(e) {
|
|
78554
80451
|
return e instanceof Error ? e.message : String(e);
|
|
78555
80452
|
}
|
|
78556
80453
|
/**
|
|
@@ -78577,11 +80474,79 @@ function firstExposedAccessorySetupUri(exposed, logger) {
|
|
|
78577
80474
|
try {
|
|
78578
80475
|
return first.setupURI();
|
|
78579
80476
|
} catch (err) {
|
|
78580
|
-
logger.debug("export-hap: setupURI failed on first exposed accessory", { meta: { error: errMsg$
|
|
80477
|
+
logger.debug("export-hap: setupURI failed on first exposed accessory", { meta: { error: errMsg$14(err) } });
|
|
78581
80478
|
return;
|
|
78582
80479
|
}
|
|
78583
80480
|
}
|
|
78584
80481
|
}
|
|
80482
|
+
//#endregion
|
|
80483
|
+
//#region src/mappers/builders/accessory-info.ts
|
|
80484
|
+
/**
|
|
80485
|
+
* `Service.AccessoryInformation` — the manufacturer / model / firmware /
|
|
80486
|
+
* serial block every HomeKit accessory carries.
|
|
80487
|
+
*
|
|
80488
|
+
* One implementation for both accessory shapes (camera and generic): the
|
|
80489
|
+
* fields come from the device's own metadata either way, and a second copy
|
|
80490
|
+
* would be a second answer to "what serial does this device publish".
|
|
80491
|
+
* Metadata is best-effort — a device that cannot answer still publishes.
|
|
80492
|
+
*/
|
|
80493
|
+
async function populateAccessoryInfo(accessory, proxy, displayName, modelFallback) {
|
|
80494
|
+
const info = accessory.getService(import_dist.Service.AccessoryInformation);
|
|
80495
|
+
if (!info) return;
|
|
80496
|
+
try {
|
|
80497
|
+
const device = await proxy.deviceManager?.getDevice({});
|
|
80498
|
+
const metadata = readMetadata(device);
|
|
80499
|
+
info.setCharacteristic(import_dist.Characteristic.Name, device?.name ?? displayName);
|
|
80500
|
+
info.setCharacteristic(import_dist.Characteristic.Manufacturer, stringOr(metadata?.manufacturer, "CamStack"));
|
|
80501
|
+
info.setCharacteristic(import_dist.Characteristic.Model, stringOr(metadata?.model, modelFallback));
|
|
80502
|
+
info.setCharacteristic(import_dist.Characteristic.FirmwareRevision, stringOr(metadata?.firmware, "0.0.0"));
|
|
80503
|
+
info.setCharacteristic(import_dist.Characteristic.SerialNumber, stringOr(metadata?.sn, `camstack-${proxy.deviceId}`));
|
|
80504
|
+
} catch {}
|
|
80505
|
+
}
|
|
80506
|
+
/** The four fields this module reads, or `null` — the device record's
|
|
80507
|
+
* `metadata` is an open bag and nothing else here depends on its shape. */
|
|
80508
|
+
function readMetadata(device) {
|
|
80509
|
+
const metadata = device?.metadata;
|
|
80510
|
+
if (metadata === null || typeof metadata !== "object") return {};
|
|
80511
|
+
const entries = new Map(Object.entries(metadata));
|
|
80512
|
+
return {
|
|
80513
|
+
model: stringOrNull(entries.get("model")),
|
|
80514
|
+
manufacturer: stringOrNull(entries.get("manufacturer")),
|
|
80515
|
+
firmware: stringOrNull(entries.get("firmware")),
|
|
80516
|
+
sn: stringOrNull(entries.get("sn"))
|
|
80517
|
+
};
|
|
80518
|
+
}
|
|
80519
|
+
function stringOrNull(value) {
|
|
80520
|
+
return typeof value === "string" ? value : null;
|
|
80521
|
+
}
|
|
80522
|
+
function stringOr(value, fallback) {
|
|
80523
|
+
return typeof value === "string" && value.length > 0 ? value : fallback;
|
|
80524
|
+
}
|
|
80525
|
+
//#endregion
|
|
80526
|
+
//#region src/mappers/builders/generic/characteristic-update.ts
|
|
80527
|
+
/**
|
|
80528
|
+
* Parse `status` with the capability's OWN Zod schema and turn it into
|
|
80529
|
+
* characteristic writes.
|
|
80530
|
+
*
|
|
80531
|
+
* Duck-typing the payload here would be the second source of truth about what a
|
|
80532
|
+
* cap reports; the schema is the first and only one. A payload that does not
|
|
80533
|
+
* match yields NO updates — never a partial or invented value — and the caller
|
|
80534
|
+
* logs the drop, because a sensor that silently stops moving is
|
|
80535
|
+
* indistinguishable from a sensor that never changed.
|
|
80536
|
+
*
|
|
80537
|
+
* Rows `.pick()` only the fields they read, so a provider omitting a timestamp
|
|
80538
|
+
* cannot silence a sensor.
|
|
80539
|
+
*/
|
|
80540
|
+
function reader(schema, toUpdates) {
|
|
80541
|
+
return (status) => {
|
|
80542
|
+
const parsed = schema.safeParse(status);
|
|
80543
|
+
return parsed.success ? toUpdates(parsed.data) : [];
|
|
80544
|
+
};
|
|
80545
|
+
}
|
|
80546
|
+
/** Push every update onto `service`. */
|
|
80547
|
+
function applyUpdates(service, updates) {
|
|
80548
|
+
for (const update of updates) service.updateCharacteristic(update.characteristic, update.value);
|
|
80549
|
+
}
|
|
78585
80550
|
/**
|
|
78586
80551
|
* hap-nodejs' `checkName` regex, verbatim.
|
|
78587
80552
|
*
|
|
@@ -78718,6 +80683,92 @@ function titleCase(raw) {
|
|
|
78718
80683
|
return raw.split(/[-_\s]+/u).filter((part) => part.length > 0).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
78719
80684
|
}
|
|
78720
80685
|
//#endregion
|
|
80686
|
+
//#region src/mappers/builders/service-label.ts
|
|
80687
|
+
/**
|
|
80688
|
+
* The ONE place a secondary service on the camera accessory gets its label.
|
|
80689
|
+
*
|
|
80690
|
+
* A "secondary service" here is a Switch or Lightbulb published alongside the
|
|
80691
|
+
* camera on the same accessory — the privacy switch, each accessory child
|
|
80692
|
+
* (siren, floodlight), each PTZ action. iOS Home renders these as their own
|
|
80693
|
+
* controls, and the operator has seen them as "Interruttore 1", "Interruttore
|
|
80694
|
+
* 2" through three separate rounds of fixes.
|
|
80695
|
+
*
|
|
80696
|
+
* ## Why `Name` alone cannot rename anything
|
|
80697
|
+
*
|
|
80698
|
+
* Two facts about hap-nodejs 2.1.7, both measured against the installed copy
|
|
80699
|
+
* rather than reasoned about:
|
|
80700
|
+
*
|
|
80701
|
+
* 1. `accessory.addService(Type, displayName, subtype)` ALREADY writes
|
|
80702
|
+
* `displayName` to `Characteristic.Name` (`Service` constructor). So every
|
|
80703
|
+
* round of this bug — including the one that moved the label onto
|
|
80704
|
+
* `ConfiguredName` — shipped with `Name` correctly set. "iOS had no name
|
|
80705
|
+
* to render" was never true.
|
|
80706
|
+
* 2. The mDNS configuration number (`c#`) is a sha1 over
|
|
80707
|
+
* `internalHAPRepresentation(false)`, which OMITS characteristic VALUES.
|
|
80708
|
+
* Changing the string in `Name` therefore does not bump `c#`, a paired
|
|
80709
|
+
* controller gets no signal to re-read `/accessories`, and the name it
|
|
80710
|
+
* cached at first enumeration stands forever.
|
|
80711
|
+
*
|
|
80712
|
+
* `Name` is also declared `pr` only — paired read, no write, no notify. It is
|
|
80713
|
+
* the seed a controller seeds its database from once; it is not a channel.
|
|
80714
|
+
*
|
|
80715
|
+
* ## Why `ConfiguredName`
|
|
80716
|
+
*
|
|
80717
|
+
* `ConfiguredName` (`000000E3`) is declared `pr | pw | ev` — the only name
|
|
80718
|
+
* characteristic a controller may write and may subscribe to. It is what iOS
|
|
80719
|
+
* 16+ reads for a service the user can rename, and adding it CHANGES the
|
|
80720
|
+
* accessory structure, so `c#` does bump and the controller re-reads.
|
|
80721
|
+
*
|
|
80722
|
+
* It was removed once because hap-nodejs logged
|
|
80723
|
+
*
|
|
80724
|
+
* ```
|
|
80725
|
+
* Characteristic not in required or optional characteristic section for
|
|
80726
|
+
* service Switch. Adding anyway.
|
|
80727
|
+
* ```
|
|
80728
|
+
*
|
|
80729
|
+
* That line is a WARNING, not a rejection: `Service.getCharacteristic` calls
|
|
80730
|
+
* `addCharacteristic` unconditionally and only then emits the warning. The
|
|
80731
|
+
* characteristic was always present and always published. hap-nodejs'
|
|
80732
|
+
* per-service optional lists simply predate `ConfiguredName` being valid on
|
|
80733
|
+
* any service.
|
|
80734
|
+
*
|
|
80735
|
+
* Registering it with {@link Service.addOptionalCharacteristic} first takes
|
|
80736
|
+
* the branch above the warning, so the accessory still builds with ZERO
|
|
80737
|
+
* characteristic warnings — which is what `service-naming.spec.ts` asserts.
|
|
80738
|
+
*
|
|
80739
|
+
* ## Scope: EVERY service, including the sensors
|
|
80740
|
+
*
|
|
80741
|
+
* An earlier round applied this to Switch- and Lightbulb-shaped services only,
|
|
80742
|
+
* on the theory that `Service.MotionSensor` and `Service.Battery` are not
|
|
80743
|
+
* separately named tiles in iOS Home and that naming them would be a guess.
|
|
80744
|
+
*
|
|
80745
|
+
* That theory was never measured, and it had a cost: it left services on the
|
|
80746
|
+
* accessory whose name a paired controller could never be told about, and it
|
|
80747
|
+
* made "did `ConfiguredName` fix the operator's 'Interruttore N'?" unanswerable
|
|
80748
|
+
* — a negative result on a partial application proves nothing about the
|
|
80749
|
+
* mechanism. Every service this addon publishes now carries both
|
|
80750
|
+
* characteristics, on the camera accessory and on the generic one.
|
|
80751
|
+
*
|
|
80752
|
+
* The reasoning above is mechanism, not measurement: it says why `Name` alone
|
|
80753
|
+
* CANNOT work and why `ConfiguredName` is the only characteristic that can. It
|
|
80754
|
+
* does not prove iOS renders it on every service shape. That is an observation
|
|
80755
|
+
* only a re-paired controller can make — and after a change like this one, the
|
|
80756
|
+
* controller must be re-paired, because a cached accessory database is exactly
|
|
80757
|
+
* what the whole mechanism is about.
|
|
80758
|
+
*/
|
|
80759
|
+
/**
|
|
80760
|
+
* Publish `name` as both the immutable `Name` and the controller-visible
|
|
80761
|
+
* `ConfiguredName` of `service`.
|
|
80762
|
+
*
|
|
80763
|
+
* `name` must already be HAP-valid — build it with `service-names.ts`, which
|
|
80764
|
+
* cannot return a string hap-nodejs' `checkName` would warn about.
|
|
80765
|
+
*/
|
|
80766
|
+
function applyServiceLabel(service, name) {
|
|
80767
|
+
service.setCharacteristic(import_dist.Characteristic.Name, name);
|
|
80768
|
+
if (!service.optionalCharacteristics.some((characteristic) => characteristic.UUID === import_dist.Characteristic.ConfiguredName.UUID)) service.addOptionalCharacteristic(import_dist.Characteristic.ConfiguredName);
|
|
80769
|
+
service.setCharacteristic(import_dist.Characteristic.ConfiguredName, name);
|
|
80770
|
+
}
|
|
80771
|
+
//#endregion
|
|
78721
80772
|
//#region src/mappers/builders/battery.ts
|
|
78722
80773
|
/**
|
|
78723
80774
|
* Battery builder — surfaces a battery-operated camera's power state
|
|
@@ -78727,34 +80778,56 @@ function titleCase(raw) {
|
|
|
78727
80778
|
* subscribes to the runtime-state slice so iOS Home reflects level /
|
|
78728
80779
|
* charging changes pushed by the firmware without a poll loop.
|
|
78729
80780
|
*
|
|
80781
|
+
* The status→characteristic mapping is {@link batteryCharacteristicUpdates},
|
|
80782
|
+
* exported because the generic (non-camera) export path publishes the same
|
|
80783
|
+
* `Service.Battery` from the same cap — one derivation, two accessory shapes.
|
|
80784
|
+
*
|
|
80785
|
+
* Skipped silently when the `battery` cap is not bound — caller checks
|
|
80786
|
+
* cap presence before invoking this builder (see `camera-accessory.ts`).
|
|
80787
|
+
*/
|
|
80788
|
+
var LOW_BATTERY_THRESHOLD_PCT = 20;
|
|
80789
|
+
/**
|
|
78730
80790
|
* Mapping:
|
|
78731
80791
|
* - `BatteryStatus.percentage` (0..100) → `Characteristic.BatteryLevel`
|
|
78732
80792
|
* - `BatteryStatus.charging`:
|
|
78733
80793
|
* `'none'` → `ChargingState.NOT_CHARGING`
|
|
78734
80794
|
* `'dc' | 'solar'` → `ChargingState.CHARGING`
|
|
78735
80795
|
* - `percentage <= LOW_BATTERY_THRESHOLD_PCT` → `StatusLowBattery.LOW`
|
|
78736
|
-
*
|
|
78737
|
-
* Skipped silently when the `battery` cap is not bound — caller checks
|
|
78738
|
-
* cap presence before invoking this builder (see `camera-accessory.ts`).
|
|
78739
80796
|
*/
|
|
78740
|
-
var
|
|
80797
|
+
var batteryCharacteristicUpdates = reader(BatteryStatusSchema.pick({ percentage: true }).extend({ charging: BatteryStatusSchema.shape.charging.optional() }), (status) => {
|
|
80798
|
+
const pct = Math.max(0, Math.min(100, Math.round(status.percentage)));
|
|
80799
|
+
return [
|
|
80800
|
+
{
|
|
80801
|
+
characteristic: import_dist.Characteristic.BatteryLevel,
|
|
80802
|
+
value: pct
|
|
80803
|
+
},
|
|
80804
|
+
...status.charging === void 0 ? [] : [{
|
|
80805
|
+
characteristic: import_dist.Characteristic.ChargingState,
|
|
80806
|
+
value: status.charging === "none" ? import_dist.Characteristic.ChargingState.NOT_CHARGING : import_dist.Characteristic.ChargingState.CHARGING
|
|
80807
|
+
}],
|
|
80808
|
+
{
|
|
80809
|
+
characteristic: import_dist.Characteristic.StatusLowBattery,
|
|
80810
|
+
value: pct <= LOW_BATTERY_THRESHOLD_PCT ? import_dist.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : import_dist.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL
|
|
80811
|
+
}
|
|
80812
|
+
];
|
|
80813
|
+
});
|
|
78741
80814
|
async function buildBattery(bctx) {
|
|
78742
80815
|
const { ctx, accessory, proxy, numericDeviceId, displayName } = bctx;
|
|
78743
80816
|
const log = ctx.logger.withTags({ deviceId: numericDeviceId });
|
|
78744
|
-
const
|
|
80817
|
+
const label = hapServiceName([displayName], `Camera ${numericDeviceId}`);
|
|
80818
|
+
const service = accessory.addService(import_dist.Service.Battery, label);
|
|
80819
|
+
applyServiceLabel(service, label);
|
|
78745
80820
|
try {
|
|
78746
80821
|
const status = await proxy.battery?.getStatus({});
|
|
78747
|
-
if (status) applyToService(service, status);
|
|
80822
|
+
if (status !== void 0 && status !== null) applyToService(service, status);
|
|
78748
80823
|
} catch (err) {
|
|
78749
|
-
log.debug("export-hap: battery getStatus hydrate failed (non-fatal)", { meta: { error: errMsg$
|
|
80824
|
+
log.debug("export-hap: battery getStatus hydrate failed (non-fatal)", { meta: { error: errMsg$13(err) } });
|
|
78750
80825
|
}
|
|
78751
80826
|
const unsubscribes = [];
|
|
78752
80827
|
if (proxy.state.battery) {
|
|
78753
80828
|
const unsub = proxy.state.battery.subscribe((value) => {
|
|
78754
80829
|
if (!value) return;
|
|
78755
|
-
|
|
78756
|
-
if (typeof status.percentage !== "number") return;
|
|
78757
|
-
applyToService(service, status);
|
|
80830
|
+
applyToService(service, value);
|
|
78758
80831
|
});
|
|
78759
80832
|
unsubscribes.push(unsub);
|
|
78760
80833
|
}
|
|
@@ -78765,14 +80838,9 @@ async function buildBattery(bctx) {
|
|
|
78765
80838
|
} };
|
|
78766
80839
|
}
|
|
78767
80840
|
function applyToService(service, status) {
|
|
78768
|
-
|
|
78769
|
-
service.updateCharacteristic(import_dist.Characteristic.BatteryLevel, pct);
|
|
78770
|
-
const chargingState = status.charging === "none" ? import_dist.Characteristic.ChargingState.NOT_CHARGING : import_dist.Characteristic.ChargingState.CHARGING;
|
|
78771
|
-
service.updateCharacteristic(import_dist.Characteristic.ChargingState, chargingState);
|
|
78772
|
-
const lowBattery = pct <= LOW_BATTERY_THRESHOLD_PCT ? import_dist.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : import_dist.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL;
|
|
78773
|
-
service.updateCharacteristic(import_dist.Characteristic.StatusLowBattery, lowBattery);
|
|
80841
|
+
applyUpdates(service, batteryCharacteristicUpdates(status));
|
|
78774
80842
|
}
|
|
78775
|
-
function errMsg$
|
|
80843
|
+
function errMsg$13(err) {
|
|
78776
80844
|
return err instanceof Error ? err.message : String(err);
|
|
78777
80845
|
}
|
|
78778
80846
|
//#endregion
|
|
@@ -89524,7 +91592,7 @@ var VIDEO_LOOPBACK_RCVBUF_BYTES = 8 * 1024 * 1024;
|
|
|
89524
91592
|
* not burst size.
|
|
89525
91593
|
*/
|
|
89526
91594
|
var AUDIO_LOOPBACK_RCVBUF_BYTES = 1024 * 1024;
|
|
89527
|
-
function errMsg$
|
|
91595
|
+
function errMsg$12(err) {
|
|
89528
91596
|
return err instanceof Error ? err.message : String(err);
|
|
89529
91597
|
}
|
|
89530
91598
|
/**
|
|
@@ -89539,13 +91607,13 @@ function applyReceiveBuffer(socket, requestedBytes) {
|
|
|
89539
91607
|
try {
|
|
89540
91608
|
socket.setRecvBufferSize(requestedBytes);
|
|
89541
91609
|
} catch (err) {
|
|
89542
|
-
error = errMsg$
|
|
91610
|
+
error = errMsg$12(err);
|
|
89543
91611
|
}
|
|
89544
91612
|
let effectiveBytes = null;
|
|
89545
91613
|
try {
|
|
89546
91614
|
effectiveBytes = socket.getRecvBufferSize();
|
|
89547
91615
|
} catch (err) {
|
|
89548
|
-
if (error === null) error = errMsg$
|
|
91616
|
+
if (error === null) error = errMsg$12(err);
|
|
89549
91617
|
}
|
|
89550
91618
|
return {
|
|
89551
91619
|
requestedBytes,
|
|
@@ -89805,20 +91873,20 @@ function buildCameraStreamingDelegate(bctx, advertised) {
|
|
|
89805
91873
|
delegate: {
|
|
89806
91874
|
handleSnapshotRequest(request, callback) {
|
|
89807
91875
|
handleSnapshot(bctx, request).then((buf) => callback(void 0, buf)).catch((err) => {
|
|
89808
|
-
log.warn("export-hap: snapshot failed", { meta: { error: errMsg$
|
|
89809
|
-
callback(err instanceof Error ? err : new Error(errMsg$
|
|
91876
|
+
log.warn("export-hap: snapshot failed", { meta: { error: errMsg$11(err) } });
|
|
91877
|
+
callback(err instanceof Error ? err : new Error(errMsg$11(err)));
|
|
89810
91878
|
});
|
|
89811
91879
|
},
|
|
89812
91880
|
prepareStream(request, callback) {
|
|
89813
91881
|
prepareStream(request, sessions, bctx).then((resp) => callback(void 0, resp)).catch((err) => {
|
|
89814
|
-
log.warn("export-hap: prepareStream failed", { meta: { error: errMsg$
|
|
89815
|
-
callback(err instanceof Error ? err : new Error(errMsg$
|
|
91882
|
+
log.warn("export-hap: prepareStream failed", { meta: { error: errMsg$11(err) } });
|
|
91883
|
+
callback(err instanceof Error ? err : new Error(errMsg$11(err)));
|
|
89816
91884
|
});
|
|
89817
91885
|
},
|
|
89818
91886
|
handleStreamRequest(request, callback) {
|
|
89819
91887
|
handleStreamRequest(request, sessions, bctx, advertised).then(() => callback()).catch((err) => {
|
|
89820
|
-
log.warn("export-hap: handleStreamRequest failed", { meta: { error: errMsg$
|
|
89821
|
-
callback(err instanceof Error ? err : new Error(errMsg$
|
|
91888
|
+
log.warn("export-hap: handleStreamRequest failed", { meta: { error: errMsg$11(err) } });
|
|
91889
|
+
callback(err instanceof Error ? err : new Error(errMsg$11(err)));
|
|
89822
91890
|
});
|
|
89823
91891
|
}
|
|
89824
91892
|
},
|
|
@@ -89934,7 +92002,7 @@ async function prepareStream(request, sessions, bctx) {
|
|
|
89934
92002
|
closeSocket(audioUdp);
|
|
89935
92003
|
closeSocket(videoLoopUdp);
|
|
89936
92004
|
closeSocket(audioLoopUdp);
|
|
89937
|
-
throw new Error(`export-hap: outbound SrtpSession init failed: ${errMsg$
|
|
92005
|
+
throw new Error(`export-hap: outbound SrtpSession init failed: ${errMsg$11(err)}`, { cause: err });
|
|
89938
92006
|
}
|
|
89939
92007
|
let upstreamAudioSrtp = null;
|
|
89940
92008
|
try {
|
|
@@ -89948,7 +92016,7 @@ async function prepareStream(request, sessions, bctx) {
|
|
|
89948
92016
|
profile: import_src.ProtectionProfileAes128CmHmacSha1_80
|
|
89949
92017
|
});
|
|
89950
92018
|
} catch (err) {
|
|
89951
|
-
bctx.ctx.logger.withTags({ deviceId: bctx.numericDeviceId }).warn("export-hap: SrtpSession init failed (upstream audio decrypt disabled)", { meta: { error: errMsg$
|
|
92019
|
+
bctx.ctx.logger.withTags({ deviceId: bctx.numericDeviceId }).warn("export-hap: SrtpSession init failed (upstream audio decrypt disabled)", { meta: { error: errMsg$11(err) } });
|
|
89952
92020
|
}
|
|
89953
92021
|
const videoSsrc = randomSsrc();
|
|
89954
92022
|
const audioSsrc = randomSsrc();
|
|
@@ -90065,7 +92133,7 @@ async function prepareStream(request, sessions, bctx) {
|
|
|
90065
92133
|
});
|
|
90066
92134
|
audioUdp.on("message", (packet, rinfo) => {
|
|
90067
92135
|
handleIncomingAudioRtp(session, packet, rinfo.address, bctx).catch((err) => {
|
|
90068
|
-
bctx.ctx.logger.withTags({ deviceId: bctx.numericDeviceId }).debug("export-hap: incoming-audio handler error (dropped)", { meta: { error: errMsg$
|
|
92136
|
+
bctx.ctx.logger.withTags({ deviceId: bctx.numericDeviceId }).debug("export-hap: incoming-audio handler error (dropped)", { meta: { error: errMsg$11(err) } });
|
|
90069
92137
|
});
|
|
90070
92138
|
});
|
|
90071
92139
|
logLoopbackBuffer(tagLog, request.sessionID, "video", videoLoop.buffer);
|
|
@@ -90261,7 +92329,7 @@ function readControllerRtcp(session, leg, packet, log) {
|
|
|
90261
92329
|
} catch (err) {
|
|
90262
92330
|
drop(session, "inbound-rtcp-decrypt-failed");
|
|
90263
92331
|
storeReceiverReports(session, leg, recordUnreadableRtcp(tally));
|
|
90264
|
-
logUnreadableRtcp(session, leg, `decrypt: ${errMsg$
|
|
92332
|
+
logUnreadableRtcp(session, leg, `decrypt: ${errMsg$11(err)}`, log);
|
|
90265
92333
|
return;
|
|
90266
92334
|
}
|
|
90267
92335
|
const outcome = ingestDecryptedRtcp(plaintext, tally);
|
|
@@ -91027,7 +93095,7 @@ async function handleIncomingAudioRtp(session, packet, sourceAddress, bctx) {
|
|
|
91027
93095
|
session.upstreamRtpDecryptFailures += 1;
|
|
91028
93096
|
if (session.upstreamRtpDecryptFailures % 100 === 1) log.debug("export-hap: SRTP decrypt failed (rate-limited)", { meta: {
|
|
91029
93097
|
failures: session.upstreamRtpDecryptFailures,
|
|
91030
|
-
error: errMsg$
|
|
93098
|
+
error: errMsg$11(err)
|
|
91031
93099
|
} });
|
|
91032
93100
|
return;
|
|
91033
93101
|
}
|
|
@@ -91041,7 +93109,7 @@ async function handleIncomingAudioRtp(session, packet, sourceAddress, bctx) {
|
|
|
91041
93109
|
rtpPayloadType = parsedRtp.header.payloadType;
|
|
91042
93110
|
} catch (err) {
|
|
91043
93111
|
drop(session, "upstream-parse-failed");
|
|
91044
|
-
log.debug("export-hap: RTP parse failed after decrypt", { meta: { error: errMsg$
|
|
93112
|
+
log.debug("export-hap: RTP parse failed after decrypt", { meta: { error: errMsg$11(err) } });
|
|
91045
93113
|
return;
|
|
91046
93114
|
}
|
|
91047
93115
|
const negotiatedAudioPt = session.lastStartParams?.audioPt;
|
|
@@ -91065,7 +93133,7 @@ async function handleIncomingAudioRtp(session, packet, sourceAddress, bctx) {
|
|
|
91065
93133
|
if (session.intercomTalkSessionId === null) {
|
|
91066
93134
|
session.intercomTalkSessionId = "";
|
|
91067
93135
|
const opened = await openIntercomTalkSession(bctx).catch((err) => {
|
|
91068
|
-
log.warn("export-hap: intercom.startTalkSession failed", { meta: { error: errMsg$
|
|
93136
|
+
log.warn("export-hap: intercom.startTalkSession failed", { meta: { error: errMsg$11(err) } });
|
|
91069
93137
|
return null;
|
|
91070
93138
|
});
|
|
91071
93139
|
if (opened) {
|
|
@@ -91092,7 +93160,7 @@ async function handleIncomingAudioRtp(session, packet, sourceAddress, bctx) {
|
|
|
91092
93160
|
drop(session, "upstream-push-failed");
|
|
91093
93161
|
log.debug("export-hap: intercom.pushTalkAudio failed (will re-open on next frame)", { meta: {
|
|
91094
93162
|
sequenceNumber: session.intercomPcmSequence,
|
|
91095
|
-
error: errMsg$
|
|
93163
|
+
error: errMsg$11(err)
|
|
91096
93164
|
} });
|
|
91097
93165
|
session.intercomTalkSessionId = null;
|
|
91098
93166
|
}
|
|
@@ -91124,7 +93192,7 @@ async function closeIntercomTalkSession(session, bctx) {
|
|
|
91124
93192
|
} catch (err) {
|
|
91125
93193
|
log.debug("export-hap: intercom.endTalkSession failed (continuing)", { meta: {
|
|
91126
93194
|
sessionId,
|
|
91127
|
-
error: errMsg$
|
|
93195
|
+
error: errMsg$11(err)
|
|
91128
93196
|
} });
|
|
91129
93197
|
}
|
|
91130
93198
|
}
|
|
@@ -91149,7 +93217,7 @@ function closeSocket(udp) {
|
|
|
91149
93217
|
function randomSsrc() {
|
|
91150
93218
|
return Math.floor(Math.random() * 2147483646) + 1 | 0;
|
|
91151
93219
|
}
|
|
91152
|
-
function errMsg$
|
|
93220
|
+
function errMsg$11(err) {
|
|
91153
93221
|
return err instanceof Error ? err.message : String(err);
|
|
91154
93222
|
}
|
|
91155
93223
|
//#endregion
|
|
@@ -91233,14 +93301,14 @@ async function buildDoorbell(input) {
|
|
|
91233
93301
|
}
|
|
91234
93302
|
log.info("export-hap: doorbell SINGLE_PRESS pushed to HomeKit", { meta: { ...delivery } });
|
|
91235
93303
|
} catch (err) {
|
|
91236
|
-
log.warn("export-hap: ringDoorbell() failed", { meta: { error: errMsg$
|
|
93304
|
+
log.warn("export-hap: ringDoorbell() failed", { meta: { error: errMsg$10(err) } });
|
|
91237
93305
|
}
|
|
91238
93306
|
});
|
|
91239
93307
|
return { async dispose() {
|
|
91240
93308
|
unsubscribe();
|
|
91241
93309
|
} };
|
|
91242
93310
|
}
|
|
91243
|
-
function errMsg$
|
|
93311
|
+
function errMsg$10(err) {
|
|
91244
93312
|
return err instanceof Error ? err.message : String(err);
|
|
91245
93313
|
}
|
|
91246
93314
|
//#endregion
|
|
@@ -91280,13 +93348,15 @@ var RESET_DEBOUNCE_MS = 5e3;
|
|
|
91280
93348
|
*/
|
|
91281
93349
|
async function buildMotionSensor(bctx, existing = null) {
|
|
91282
93350
|
const { ctx, accessory, proxy, numericDeviceId, displayName } = bctx;
|
|
91283
|
-
const
|
|
93351
|
+
const label = hapServiceName([displayName], `Camera ${numericDeviceId}`);
|
|
93352
|
+
const motionService = existing ?? accessory.addService(import_dist.Service.MotionSensor, label);
|
|
93353
|
+
applyServiceLabel(motionService, label);
|
|
91284
93354
|
motionService.setCharacteristic(import_dist.Characteristic.MotionDetected, false);
|
|
91285
93355
|
try {
|
|
91286
93356
|
const detected = await proxy.motion?.isDetected({});
|
|
91287
93357
|
if (typeof detected === "boolean") motionService.updateCharacteristic(import_dist.Characteristic.MotionDetected, detected);
|
|
91288
93358
|
} catch (err) {
|
|
91289
|
-
ctx.logger.withTags({ deviceId: numericDeviceId }).debug("export-hap: initial motion hydrate failed (non-fatal)", { meta: { error: errMsg$
|
|
93359
|
+
ctx.logger.withTags({ deviceId: numericDeviceId }).debug("export-hap: initial motion hydrate failed (non-fatal)", { meta: { error: errMsg$9(err) } });
|
|
91290
93360
|
}
|
|
91291
93361
|
let resetTimer = null;
|
|
91292
93362
|
const armReset = () => {
|
|
@@ -91320,82 +93390,10 @@ async function buildMotionSensor(bctx, existing = null) {
|
|
|
91320
93390
|
}
|
|
91321
93391
|
} };
|
|
91322
93392
|
}
|
|
91323
|
-
function errMsg$
|
|
93393
|
+
function errMsg$9(err) {
|
|
91324
93394
|
return err instanceof Error ? err.message : String(err);
|
|
91325
93395
|
}
|
|
91326
93396
|
//#endregion
|
|
91327
|
-
//#region src/mappers/builders/service-label.ts
|
|
91328
|
-
/**
|
|
91329
|
-
* The ONE place a secondary service on the camera accessory gets its label.
|
|
91330
|
-
*
|
|
91331
|
-
* A "secondary service" here is a Switch or Lightbulb published alongside the
|
|
91332
|
-
* camera on the same accessory — the privacy switch, each accessory child
|
|
91333
|
-
* (siren, floodlight), each PTZ action. iOS Home renders these as their own
|
|
91334
|
-
* controls, and the operator has seen them as "Interruttore 1", "Interruttore
|
|
91335
|
-
* 2" through three separate rounds of fixes.
|
|
91336
|
-
*
|
|
91337
|
-
* ## Why `Name` alone cannot rename anything
|
|
91338
|
-
*
|
|
91339
|
-
* Two facts about hap-nodejs 2.1.7, both measured against the installed copy
|
|
91340
|
-
* rather than reasoned about:
|
|
91341
|
-
*
|
|
91342
|
-
* 1. `accessory.addService(Type, displayName, subtype)` ALREADY writes
|
|
91343
|
-
* `displayName` to `Characteristic.Name` (`Service` constructor). So every
|
|
91344
|
-
* round of this bug — including the one that moved the label onto
|
|
91345
|
-
* `ConfiguredName` — shipped with `Name` correctly set. "iOS had no name
|
|
91346
|
-
* to render" was never true.
|
|
91347
|
-
* 2. The mDNS configuration number (`c#`) is a sha1 over
|
|
91348
|
-
* `internalHAPRepresentation(false)`, which OMITS characteristic VALUES.
|
|
91349
|
-
* Changing the string in `Name` therefore does not bump `c#`, a paired
|
|
91350
|
-
* controller gets no signal to re-read `/accessories`, and the name it
|
|
91351
|
-
* cached at first enumeration stands forever.
|
|
91352
|
-
*
|
|
91353
|
-
* `Name` is also declared `pr` only — paired read, no write, no notify. It is
|
|
91354
|
-
* the seed a controller seeds its database from once; it is not a channel.
|
|
91355
|
-
*
|
|
91356
|
-
* ## Why `ConfiguredName`
|
|
91357
|
-
*
|
|
91358
|
-
* `ConfiguredName` (`000000E3`) is declared `pr | pw | ev` — the only name
|
|
91359
|
-
* characteristic a controller may write and may subscribe to. It is what iOS
|
|
91360
|
-
* 16+ reads for a service the user can rename, and adding it CHANGES the
|
|
91361
|
-
* accessory structure, so `c#` does bump and the controller re-reads.
|
|
91362
|
-
*
|
|
91363
|
-
* It was removed once because hap-nodejs logged
|
|
91364
|
-
*
|
|
91365
|
-
* ```
|
|
91366
|
-
* Characteristic not in required or optional characteristic section for
|
|
91367
|
-
* service Switch. Adding anyway.
|
|
91368
|
-
* ```
|
|
91369
|
-
*
|
|
91370
|
-
* That line is a WARNING, not a rejection: `Service.getCharacteristic` calls
|
|
91371
|
-
* `addCharacteristic` unconditionally and only then emits the warning. The
|
|
91372
|
-
* characteristic was always present and always published. hap-nodejs'
|
|
91373
|
-
* per-service optional lists simply predate `ConfiguredName` being valid on
|
|
91374
|
-
* any service.
|
|
91375
|
-
*
|
|
91376
|
-
* Registering it with {@link Service.addOptionalCharacteristic} first takes
|
|
91377
|
-
* the branch above the warning, so the accessory still builds with ZERO
|
|
91378
|
-
* characteristic warnings — which is what `service-naming.spec.ts` asserts.
|
|
91379
|
-
*
|
|
91380
|
-
* ## Scope
|
|
91381
|
-
*
|
|
91382
|
-
* Switch- and Lightbulb-shaped services only. `Service.MotionSensor` on a
|
|
91383
|
-
* camera accessory is not a separately named tile in iOS Home, so giving it a
|
|
91384
|
-
* writable name would be a guess, and this module does not guess.
|
|
91385
|
-
*/
|
|
91386
|
-
/**
|
|
91387
|
-
* Publish `name` as both the immutable `Name` and the controller-visible
|
|
91388
|
-
* `ConfiguredName` of `service`.
|
|
91389
|
-
*
|
|
91390
|
-
* `name` must already be HAP-valid — build it with `service-names.ts`, which
|
|
91391
|
-
* cannot return a string hap-nodejs' `checkName` would warn about.
|
|
91392
|
-
*/
|
|
91393
|
-
function applyServiceLabel(service, name) {
|
|
91394
|
-
service.setCharacteristic(import_dist.Characteristic.Name, name);
|
|
91395
|
-
if (!service.optionalCharacteristics.some((characteristic) => characteristic.UUID === import_dist.Characteristic.ConfiguredName.UUID)) service.addOptionalCharacteristic(import_dist.Characteristic.ConfiguredName);
|
|
91396
|
-
service.setCharacteristic(import_dist.Characteristic.ConfiguredName, name);
|
|
91397
|
-
}
|
|
91398
|
-
//#endregion
|
|
91399
93397
|
//#region src/mappers/builders/privacy-switch.ts
|
|
91400
93398
|
/**
|
|
91401
93399
|
* Privacy-mask switch builder — turns the camstack `privacy-mask` cap's
|
|
@@ -91423,7 +93421,7 @@ async function buildPrivacySwitch(bctx) {
|
|
|
91423
93421
|
const status = await proxy.privacyMask?.getStatus({});
|
|
91424
93422
|
if (status && typeof status.enabled === "boolean") service.updateCharacteristic(import_dist.Characteristic.On, status.enabled);
|
|
91425
93423
|
} catch (err) {
|
|
91426
|
-
log.debug("export-hap: privacy-mask getStatus hydrate failed (non-fatal)", { meta: { error: errMsg$
|
|
93424
|
+
log.debug("export-hap: privacy-mask getStatus hydrate failed (non-fatal)", { meta: { error: errMsg$8(err) } });
|
|
91427
93425
|
}
|
|
91428
93426
|
service.getCharacteristic(import_dist.Characteristic.On).onSet(async (value) => {
|
|
91429
93427
|
const enabled = value === true;
|
|
@@ -91432,7 +93430,7 @@ async function buildPrivacySwitch(bctx) {
|
|
|
91432
93430
|
} catch (err) {
|
|
91433
93431
|
log.warn("export-hap: privacy-mask setMask failed", { meta: {
|
|
91434
93432
|
enabled,
|
|
91435
|
-
error: errMsg$
|
|
93433
|
+
error: errMsg$8(err)
|
|
91436
93434
|
} });
|
|
91437
93435
|
}
|
|
91438
93436
|
});
|
|
@@ -91450,7 +93448,7 @@ async function buildPrivacySwitch(bctx) {
|
|
|
91450
93448
|
} catch {}
|
|
91451
93449
|
} };
|
|
91452
93450
|
}
|
|
91453
|
-
function errMsg$
|
|
93451
|
+
function errMsg$8(err) {
|
|
91454
93452
|
return err instanceof Error ? err.message : String(err);
|
|
91455
93453
|
}
|
|
91456
93454
|
//#endregion
|
|
@@ -91541,7 +93539,7 @@ async function buildPtz(bctx) {
|
|
|
91541
93539
|
} catch (err) {
|
|
91542
93540
|
log.warn("export-hap: ptz.goToPreset failed", { meta: {
|
|
91543
93541
|
presetId: preset.id,
|
|
91544
|
-
error: errMsg$
|
|
93542
|
+
error: errMsg$7(err)
|
|
91545
93543
|
} });
|
|
91546
93544
|
}
|
|
91547
93545
|
armReset(() => service.updateCharacteristic(import_dist.Characteristic.On, false), MOMENTARY_RESET_MS);
|
|
@@ -91559,14 +93557,14 @@ async function buildPtz(bctx) {
|
|
|
91559
93557
|
try {
|
|
91560
93558
|
await proxy.ptz?.stop({});
|
|
91561
93559
|
} catch (err) {
|
|
91562
|
-
log.debug("export-hap: ptz.stop failed (non-fatal)", { meta: { error: errMsg$
|
|
93560
|
+
log.debug("export-hap: ptz.stop failed (non-fatal)", { meta: { error: errMsg$7(err) } });
|
|
91563
93561
|
}
|
|
91564
93562
|
service.updateCharacteristic(import_dist.Characteristic.On, false);
|
|
91565
93563
|
}, options.ptzPulseMs);
|
|
91566
93564
|
} catch (err) {
|
|
91567
93565
|
log.warn("export-hap: ptz.continuousMove failed", { meta: {
|
|
91568
93566
|
dir: dir.label,
|
|
91569
|
-
error: errMsg$
|
|
93567
|
+
error: errMsg$7(err)
|
|
91570
93568
|
} });
|
|
91571
93569
|
service.updateCharacteristic(import_dist.Characteristic.On, false);
|
|
91572
93570
|
}
|
|
@@ -91589,7 +93587,7 @@ async function readPresets(bctx) {
|
|
|
91589
93587
|
name: p.name
|
|
91590
93588
|
}));
|
|
91591
93589
|
} catch (err) {
|
|
91592
|
-
ctx.logger.withTags({ deviceId: numericDeviceId }).debug("export-hap: ptz.getPresets failed (non-fatal)", { meta: { error: errMsg$
|
|
93590
|
+
ctx.logger.withTags({ deviceId: numericDeviceId }).debug("export-hap: ptz.getPresets failed (non-fatal)", { meta: { error: errMsg$7(err) } });
|
|
91593
93591
|
return [];
|
|
91594
93592
|
}
|
|
91595
93593
|
}
|
|
@@ -91604,7 +93602,7 @@ async function tryBuildAutotrack(bctx) {
|
|
|
91604
93602
|
const status = await proxy.ptzAutotrack.getStatus({});
|
|
91605
93603
|
if (status && typeof status.enabled === "boolean") service.updateCharacteristic(import_dist.Characteristic.On, status.enabled);
|
|
91606
93604
|
} catch (err) {
|
|
91607
|
-
log.debug("export-hap: ptzAutotrack.getStatus failed (non-fatal)", { meta: { error: errMsg$
|
|
93605
|
+
log.debug("export-hap: ptzAutotrack.getStatus failed (non-fatal)", { meta: { error: errMsg$7(err) } });
|
|
91608
93606
|
}
|
|
91609
93607
|
service.getCharacteristic(import_dist.Characteristic.On).onSet(async (value) => {
|
|
91610
93608
|
const enabled = value === true;
|
|
@@ -91613,13 +93611,13 @@ async function tryBuildAutotrack(bctx) {
|
|
|
91613
93611
|
} catch (err) {
|
|
91614
93612
|
log.warn("export-hap: ptzAutotrack.setEnabled failed", { meta: {
|
|
91615
93613
|
enabled,
|
|
91616
|
-
error: errMsg$
|
|
93614
|
+
error: errMsg$7(err)
|
|
91617
93615
|
} });
|
|
91618
93616
|
}
|
|
91619
93617
|
});
|
|
91620
93618
|
return { async dispose() {} };
|
|
91621
93619
|
}
|
|
91622
|
-
function errMsg$
|
|
93620
|
+
function errMsg$7(err) {
|
|
91623
93621
|
return err instanceof Error ? err.message : String(err);
|
|
91624
93622
|
}
|
|
91625
93623
|
//#endregion
|
|
@@ -92650,13 +94648,13 @@ async function buildChildSwitch(bctx, subtype, deviceType) {
|
|
|
92650
94648
|
const switchStatus = await proxy.switch?.getStatus({});
|
|
92651
94649
|
if (switchStatus && typeof switchStatus.on === "boolean") service.updateCharacteristic(import_dist.Characteristic.On, switchStatus.on);
|
|
92652
94650
|
} catch (err) {
|
|
92653
|
-
log.debug("export-hap: child switch.getStatus failed (non-fatal)", { meta: { error: errMsg$
|
|
94651
|
+
log.debug("export-hap: child switch.getStatus failed (non-fatal)", { meta: { error: errMsg$6(err) } });
|
|
92654
94652
|
}
|
|
92655
94653
|
if (useLightbulb) try {
|
|
92656
94654
|
const status = await proxy.brightness?.getStatus({});
|
|
92657
94655
|
if (status && typeof status.percentage === "number") service.updateCharacteristic(import_dist.Characteristic.Brightness, status.percentage);
|
|
92658
94656
|
} catch (err) {
|
|
92659
|
-
log.debug("export-hap: child brightness.getStatus failed (non-fatal)", { meta: { error: errMsg$
|
|
94657
|
+
log.debug("export-hap: child brightness.getStatus failed (non-fatal)", { meta: { error: errMsg$6(err) } });
|
|
92660
94658
|
}
|
|
92661
94659
|
if (hasSwitch) service.getCharacteristic(import_dist.Characteristic.On).onSet(async (value) => {
|
|
92662
94660
|
const on = value === true;
|
|
@@ -92665,7 +94663,7 @@ async function buildChildSwitch(bctx, subtype, deviceType) {
|
|
|
92665
94663
|
} catch (err) {
|
|
92666
94664
|
log.warn("export-hap: child switch.setState failed", { meta: {
|
|
92667
94665
|
on,
|
|
92668
|
-
error: errMsg$
|
|
94666
|
+
error: errMsg$6(err)
|
|
92669
94667
|
} });
|
|
92670
94668
|
}
|
|
92671
94669
|
});
|
|
@@ -92677,7 +94675,7 @@ async function buildChildSwitch(bctx, subtype, deviceType) {
|
|
|
92677
94675
|
} catch (err) {
|
|
92678
94676
|
log.warn("export-hap: child brightness.setBrightness failed", { meta: {
|
|
92679
94677
|
percentage,
|
|
92680
|
-
error: errMsg$
|
|
94678
|
+
error: errMsg$6(err)
|
|
92681
94679
|
} });
|
|
92682
94680
|
}
|
|
92683
94681
|
});
|
|
@@ -92700,7 +94698,7 @@ async function buildChildSwitch(bctx, subtype, deviceType) {
|
|
|
92700
94698
|
} catch {}
|
|
92701
94699
|
} };
|
|
92702
94700
|
}
|
|
92703
|
-
function errMsg$
|
|
94701
|
+
function errMsg$6(err) {
|
|
92704
94702
|
return err instanceof Error ? err.message : String(err);
|
|
92705
94703
|
}
|
|
92706
94704
|
//#endregion
|
|
@@ -92727,7 +94725,7 @@ async function buildChildServicesFor(input) {
|
|
|
92727
94725
|
for (const h of handles) try {
|
|
92728
94726
|
await h.dispose();
|
|
92729
94727
|
} catch (err) {
|
|
92730
|
-
ctx.logger.withTags({ deviceId: parentNumericId }).debug("export-hap: child service dispose failed (continuing)", { meta: { error: errMsg$
|
|
94728
|
+
ctx.logger.withTags({ deviceId: parentNumericId }).debug("export-hap: child service dispose failed (continuing)", { meta: { error: errMsg$5(err) } });
|
|
92731
94729
|
}
|
|
92732
94730
|
} };
|
|
92733
94731
|
}
|
|
@@ -92743,7 +94741,7 @@ async function listChildren(ctx, parentNumericId) {
|
|
|
92743
94741
|
features: Array.isArray(c.features) ? c.features.filter((f) => typeof f === "string") : []
|
|
92744
94742
|
}));
|
|
92745
94743
|
} catch (err) {
|
|
92746
|
-
ctx.logger.withTags({ deviceId: parentNumericId }).debug("export-hap: deviceManager.getChildren failed (non-fatal)", { meta: { error: errMsg$
|
|
94744
|
+
ctx.logger.withTags({ deviceId: parentNumericId }).debug("export-hap: deviceManager.getChildren failed (non-fatal)", { meta: { error: errMsg$5(err) } });
|
|
92747
94745
|
return [];
|
|
92748
94746
|
}
|
|
92749
94747
|
}
|
|
@@ -92759,10 +94757,76 @@ function asDeviceType(raw) {
|
|
|
92759
94757
|
for (const value of Object.values(DeviceType)) if (value === lower) return value;
|
|
92760
94758
|
return DeviceType.Generic;
|
|
92761
94759
|
}
|
|
92762
|
-
function errMsg$
|
|
94760
|
+
function errMsg$5(err) {
|
|
92763
94761
|
return err instanceof Error ? err.message : String(err);
|
|
92764
94762
|
}
|
|
92765
94763
|
//#endregion
|
|
94764
|
+
//#region src/mappers/kind.ts
|
|
94765
|
+
/**
|
|
94766
|
+
* Which orchestrator a device gets, which device types the picker offers, and
|
|
94767
|
+
* what a device's HomeKit accessory is called on the wire.
|
|
94768
|
+
*
|
|
94769
|
+
* Separate from `index.ts` (the factory registry) only so the orchestrators can
|
|
94770
|
+
* import the uuid convention without importing the registry that imports them.
|
|
94771
|
+
*/
|
|
94772
|
+
var SUPPORTED_MAPPER_KINDS = ["camera", "generic"];
|
|
94773
|
+
/**
|
|
94774
|
+
* The device types the Export picker offers, and the answer to the cap's
|
|
94775
|
+
* `listSupportedDeviceKinds`.
|
|
94776
|
+
*
|
|
94777
|
+
* A UI FILTER, nothing more. Whether a device actually exports anything is
|
|
94778
|
+
* decided by its capabilities (`rowsForCaps` in the capability table) — the
|
|
94779
|
+
* doctrine the HA exporter writes down, and the thing that confined the
|
|
94780
|
+
* previous exporter to cameras when it was ignored.
|
|
94781
|
+
*
|
|
94782
|
+
* Tier A: the types whose HomeKit services exist today and need no
|
|
94783
|
+
* feature-dependent shape. `cover`, `climate`, `fan`, `valve`, `humidifier`,
|
|
94784
|
+
* `water-heater`, `alarm-panel`, `button` and `media-player` are deliberately
|
|
94785
|
+
* absent — each needs a service whose semantics the table cannot yet honour,
|
|
94786
|
+
* and offering the tab before the mapping exists is how an operator gets an
|
|
94787
|
+
* accessory that pairs and does nothing.
|
|
94788
|
+
*/
|
|
94789
|
+
var HAP_EXPORTABLE_DEVICE_TYPES = [
|
|
94790
|
+
DeviceType.Camera,
|
|
94791
|
+
DeviceType.Light,
|
|
94792
|
+
DeviceType.Switch,
|
|
94793
|
+
DeviceType.Siren,
|
|
94794
|
+
DeviceType.Sensor,
|
|
94795
|
+
DeviceType.Lock,
|
|
94796
|
+
DeviceType.Presence,
|
|
94797
|
+
DeviceType.Generic
|
|
94798
|
+
];
|
|
94799
|
+
/**
|
|
94800
|
+
* Resolve the orchestrator for a device from its TYPE.
|
|
94801
|
+
*
|
|
94802
|
+
* `null` means "no Export tab": the picker must not offer a type no
|
|
94803
|
+
* orchestrator can build. An UNKNOWN type (the device-manager read failed) maps
|
|
94804
|
+
* to `camera` — that is what every entry persisted before this function existed
|
|
94805
|
+
* carries, and a transient API failure must never re-shape an exposed camera.
|
|
94806
|
+
*/
|
|
94807
|
+
function pickMapperKind(deviceType) {
|
|
94808
|
+
if (deviceType === null || deviceType === void 0 || deviceType.length === 0) return "camera";
|
|
94809
|
+
if (deviceType === DeviceType.Camera) return "camera";
|
|
94810
|
+
return HAP_EXPORTABLE_DEVICE_TYPES.find((type) => type === deviceType) === void 0 ? null : "generic";
|
|
94811
|
+
}
|
|
94812
|
+
/**
|
|
94813
|
+
* The deterministic HAP accessory UUID for a device.
|
|
94814
|
+
*
|
|
94815
|
+
* One function, because three call sites depend on the answer agreeing: the
|
|
94816
|
+
* orchestrator that builds the accessory, `unexposeDevice` (which wipes
|
|
94817
|
+
* hap-nodejs' pairing blobs by uuid, on a device whose mapper is already gone)
|
|
94818
|
+
* and the export sync state that records it.
|
|
94819
|
+
*
|
|
94820
|
+
* The camera namespace is FROZEN — every camera paired to date derives its MAC
|
|
94821
|
+
* from `sha256("camstack:hap:" + uuid)` of this exact string, and changing it
|
|
94822
|
+
* would make every paired camera a stranger. Generic devices get their own
|
|
94823
|
+
* namespace so a switch and a camera that happen to share a device id are not
|
|
94824
|
+
* the same HomeKit accessory.
|
|
94825
|
+
*/
|
|
94826
|
+
function accessoryUuidFor(kind, deviceId) {
|
|
94827
|
+
return import_dist.uuid.generate(kind === "camera" ? `camstack:camera:${deviceId}` : `camstack:device:${deviceId}`);
|
|
94828
|
+
}
|
|
94829
|
+
//#endregion
|
|
92766
94830
|
//#region src/mappers/camera-accessory.ts
|
|
92767
94831
|
/**
|
|
92768
94832
|
* Camera accessory orchestrator — given a camstack deviceId, builds one
|
|
@@ -92795,9 +94859,9 @@ async function buildCameraAccessory(input) {
|
|
|
92795
94859
|
const capNames = new Set(proxy.binding?.entries.map((e) => e.capName) ?? []);
|
|
92796
94860
|
const isDoorbell = capNames.has("doorbell");
|
|
92797
94861
|
const category = isDoorbell ? import_dist.Categories.VIDEO_DOORBELL : import_dist.Categories.IP_CAMERA;
|
|
92798
|
-
const accessory = new import_dist.Accessory(displayName,
|
|
94862
|
+
const accessory = new import_dist.Accessory(displayName, accessoryUuidFor("camera", numericId));
|
|
92799
94863
|
accessory.category = category;
|
|
92800
|
-
await populateAccessoryInfo(accessory, proxy, displayName);
|
|
94864
|
+
await populateAccessoryInfo(accessory, proxy, displayName, "Camera");
|
|
92801
94865
|
const bctx = {
|
|
92802
94866
|
ctx,
|
|
92803
94867
|
accessory,
|
|
@@ -92855,55 +94919,462 @@ async function buildCameraAccessory(input) {
|
|
|
92855
94919
|
for (const h of handles) try {
|
|
92856
94920
|
await h.dispose();
|
|
92857
94921
|
} catch (err) {
|
|
92858
|
-
log.debug("export-hap: builder dispose failed (continuing)", { meta: { error: errMsg$
|
|
94922
|
+
log.debug("export-hap: builder dispose failed (continuing)", { meta: { error: errMsg$4(err) } });
|
|
92859
94923
|
}
|
|
92860
94924
|
try {
|
|
92861
94925
|
await streams.dispose();
|
|
92862
94926
|
} catch (err) {
|
|
92863
|
-
log.debug("export-hap: streams dispose failed", { meta: { error: errMsg$
|
|
94927
|
+
log.debug("export-hap: streams dispose failed", { meta: { error: errMsg$4(err) } });
|
|
92864
94928
|
}
|
|
92865
94929
|
}
|
|
92866
94930
|
};
|
|
92867
94931
|
}
|
|
92868
|
-
|
|
92869
|
-
|
|
92870
|
-
|
|
94932
|
+
function errMsg$4(err) {
|
|
94933
|
+
return err instanceof Error ? err.message : String(err);
|
|
94934
|
+
}
|
|
94935
|
+
//#endregion
|
|
94936
|
+
//#region src/mappers/builders/generic/lock.ts
|
|
94937
|
+
/**
|
|
94938
|
+
* `lock-control` → `Service.LockMechanism`.
|
|
94939
|
+
*
|
|
94940
|
+
* Not a sensor row: HomeKit models a lock as two characteristics, a CURRENT
|
|
94941
|
+
* state the accessory owns and a TARGET state the controller writes, and the
|
|
94942
|
+
* cap's five states do not map onto either one alone.
|
|
94943
|
+
*
|
|
94944
|
+
* ```
|
|
94945
|
+
* cap state LockCurrentState LockTargetState
|
|
94946
|
+
* locked SECURED SECURED
|
|
94947
|
+
* unlocked UNSECURED UNSECURED
|
|
94948
|
+
* locking UNKNOWN SECURED (in flight — do not claim SECURED)
|
|
94949
|
+
* unlocking UNKNOWN UNSECURED
|
|
94950
|
+
* jammed JAMMED unchanged (the controller's intent stands)
|
|
94951
|
+
* ```
|
|
94952
|
+
*
|
|
94953
|
+
* Reporting SECURED while the bolt is still moving is the failure worth naming:
|
|
94954
|
+
* iOS renders the lock as closed, the operator walks away, and the motor stalls
|
|
94955
|
+
* behind them. `UNKNOWN` is the honest answer for an in-flight transition.
|
|
94956
|
+
*
|
|
94957
|
+
* `open` (the cap's third method, for locks with a latch/buzzer) has no HomeKit
|
|
94958
|
+
* counterpart on this service and is deliberately not wired — a Switch that
|
|
94959
|
+
* silently buzzes a door open would be a second knob nobody asked for.
|
|
94960
|
+
*/
|
|
94961
|
+
var SUBTYPE = "lock-control";
|
|
94962
|
+
var readLock = reader(LockControlStatusSchema.pick({ state: true }), (status) => {
|
|
94963
|
+
const current = status.state === "locked" ? import_dist.Characteristic.LockCurrentState.SECURED : status.state === "unlocked" ? import_dist.Characteristic.LockCurrentState.UNSECURED : status.state === "jammed" ? import_dist.Characteristic.LockCurrentState.JAMMED : import_dist.Characteristic.LockCurrentState.UNKNOWN;
|
|
94964
|
+
const target = status.state === "locked" || status.state === "locking" ? import_dist.Characteristic.LockTargetState.SECURED : status.state === "unlocked" || status.state === "unlocking" ? import_dist.Characteristic.LockTargetState.UNSECURED : null;
|
|
94965
|
+
return [{
|
|
94966
|
+
characteristic: import_dist.Characteristic.LockCurrentState,
|
|
94967
|
+
value: current
|
|
94968
|
+
}, ...target === null ? [] : [{
|
|
94969
|
+
characteristic: import_dist.Characteristic.LockTargetState,
|
|
94970
|
+
value: target
|
|
94971
|
+
}]];
|
|
94972
|
+
});
|
|
94973
|
+
async function buildLockMechanism(bctx) {
|
|
94974
|
+
const { ctx, accessory, proxy, numericDeviceId, displayName } = bctx;
|
|
94975
|
+
const log = ctx.logger.withTags({ deviceId: numericDeviceId });
|
|
94976
|
+
const label = hapServiceName([displayName], `Lock ${numericDeviceId}`);
|
|
94977
|
+
const service = accessory.addService(import_dist.Service.LockMechanism, label, SUBTYPE);
|
|
94978
|
+
applyServiceLabel(service, label);
|
|
94979
|
+
const apply = (status, source) => {
|
|
94980
|
+
const updates = readLock(status);
|
|
94981
|
+
if (updates.length === 0) {
|
|
94982
|
+
log.debug("export-hap: lock status did not match the cap schema — no update", { meta: { source } });
|
|
94983
|
+
return;
|
|
94984
|
+
}
|
|
94985
|
+
applyUpdates(service, updates);
|
|
94986
|
+
};
|
|
92871
94987
|
try {
|
|
92872
|
-
const
|
|
92873
|
-
|
|
92874
|
-
|
|
92875
|
-
|
|
92876
|
-
|
|
92877
|
-
|
|
92878
|
-
|
|
92879
|
-
|
|
94988
|
+
const status = await proxy.lockControl?.getStatus({});
|
|
94989
|
+
if (status !== void 0 && status !== null) apply(status, "getStatus");
|
|
94990
|
+
} catch (err) {
|
|
94991
|
+
log.debug("export-hap: lock getStatus hydrate failed (non-fatal)", { meta: { error: errMsg$3(err) } });
|
|
94992
|
+
}
|
|
94993
|
+
service.getCharacteristic(import_dist.Characteristic.LockTargetState).onSet(async (value) => {
|
|
94994
|
+
const secure = value === import_dist.Characteristic.LockTargetState.SECURED;
|
|
94995
|
+
try {
|
|
94996
|
+
if (secure) await proxy.lockControl?.lock({});
|
|
94997
|
+
else await proxy.lockControl?.unlock({});
|
|
94998
|
+
} catch (err) {
|
|
94999
|
+
log.warn("export-hap: lock command failed", { meta: {
|
|
95000
|
+
secure,
|
|
95001
|
+
error: errMsg$3(err)
|
|
95002
|
+
} });
|
|
95003
|
+
}
|
|
95004
|
+
});
|
|
95005
|
+
const unsubscribe = proxy.state.lockControl?.subscribe((value) => {
|
|
95006
|
+
if (value === void 0 || value === null) return;
|
|
95007
|
+
apply(value, "slice");
|
|
95008
|
+
}) ?? null;
|
|
95009
|
+
return { async dispose() {
|
|
95010
|
+
try {
|
|
95011
|
+
unsubscribe?.();
|
|
95012
|
+
} catch {}
|
|
95013
|
+
} };
|
|
92880
95014
|
}
|
|
92881
|
-
function
|
|
92882
|
-
return
|
|
95015
|
+
function errMsg$3(err) {
|
|
95016
|
+
return err instanceof Error ? err.message : String(err);
|
|
95017
|
+
}
|
|
95018
|
+
//#endregion
|
|
95019
|
+
//#region src/mappers/builders/generic/sensor-service.ts
|
|
95020
|
+
async function buildSensorService(input) {
|
|
95021
|
+
const { bctx, spec, name, subtype } = input;
|
|
95022
|
+
const { ctx, accessory, proxy, numericDeviceId } = bctx;
|
|
95023
|
+
const log = ctx.logger.withTags({ deviceId: numericDeviceId });
|
|
95024
|
+
const label = hapServiceName([name], spec.label);
|
|
95025
|
+
const service = spec.addService(accessory, label, subtype);
|
|
95026
|
+
applyServiceLabel(service, label);
|
|
95027
|
+
const apply = (status, source) => {
|
|
95028
|
+
const updates = spec.read(status);
|
|
95029
|
+
if (updates.length === 0) {
|
|
95030
|
+
log.debug("export-hap: sensor status did not match the cap schema — no update", { meta: {
|
|
95031
|
+
subtype,
|
|
95032
|
+
source
|
|
95033
|
+
} });
|
|
95034
|
+
return;
|
|
95035
|
+
}
|
|
95036
|
+
applyUpdates(service, updates);
|
|
95037
|
+
};
|
|
95038
|
+
try {
|
|
95039
|
+
const status = await spec.getStatus(proxy);
|
|
95040
|
+
if (status !== void 0 && status !== null) apply(status, "getStatus");
|
|
95041
|
+
} catch (err) {
|
|
95042
|
+
log.debug("export-hap: sensor getStatus hydrate failed (non-fatal)", { meta: {
|
|
95043
|
+
subtype,
|
|
95044
|
+
error: errMsg$2(err)
|
|
95045
|
+
} });
|
|
95046
|
+
}
|
|
95047
|
+
const unsubscribe = spec.subscribe(proxy, (value) => {
|
|
95048
|
+
if (value === void 0 || value === null) return;
|
|
95049
|
+
apply(value, "slice");
|
|
95050
|
+
});
|
|
95051
|
+
return { async dispose() {
|
|
95052
|
+
try {
|
|
95053
|
+
unsubscribe?.();
|
|
95054
|
+
} catch {}
|
|
95055
|
+
} };
|
|
95056
|
+
}
|
|
95057
|
+
function errMsg$2(err) {
|
|
95058
|
+
return err instanceof Error ? err.message : String(err);
|
|
95059
|
+
}
|
|
95060
|
+
//#endregion
|
|
95061
|
+
//#region src/mappers/builders/generic/cap-service-table.ts
|
|
95062
|
+
/**
|
|
95063
|
+
* The capability→HomeKit-service TABLE for non-camera devices.
|
|
95064
|
+
*
|
|
95065
|
+
* This is the whole coverage decision for the generic export path, and it is
|
|
95066
|
+
* deliberately a table rather than a `switch` on `DeviceType`. The doctrine is
|
|
95067
|
+
* the one the Home Assistant exporter already writes down: *coverage is decided
|
|
95068
|
+
* by a device's capabilities, not its type — restricting the picker by type is
|
|
95069
|
+
* what confined the previous exporter to cameras.* A `DeviceType` here is only
|
|
95070
|
+
* ever a UI filter (`HAP_EXPORTABLE_DEVICE_TYPES`) or an icon
|
|
95071
|
+
* (`ACCESSORY_CATEGORY_BY_TYPE`); it never decides whether something exports.
|
|
95072
|
+
*
|
|
95073
|
+
* The shape mirrors `CAP_ENTITY_MAP` in
|
|
95074
|
+
* `packages/addon-provider-homeassistant/src/ha-export/entity-catalog.ts` so
|
|
95075
|
+
* the two can be unified later — one row per capability, keyed by the cap name
|
|
95076
|
+
* exactly as it appears in `proxy.binding.entries[].capName`.
|
|
95077
|
+
*
|
|
95078
|
+
* ## Reading a status
|
|
95079
|
+
*
|
|
95080
|
+
* Every row parses the cap's OWN Zod status schema rather than duck-typing the
|
|
95081
|
+
* payload, and it `.pick()`s only the fields it reads: a provider that omits a
|
|
95082
|
+
* timestamp must not silence a sensor, and a provider that sends the wrong
|
|
95083
|
+
* shape must not write a garbage characteristic. A parse that fails yields no
|
|
95084
|
+
* updates, and the builder that owns the service logs the drop — silence reads
|
|
95085
|
+
* as "the sensor never changed".
|
|
95086
|
+
*
|
|
95087
|
+
* ## Tier
|
|
95088
|
+
*
|
|
95089
|
+
* Tier A only: the caps whose HomeKit service exists today and needs no
|
|
95090
|
+
* feature-dependent shape (`cover` needs `cover-positionable`, `climate-control`
|
|
95091
|
+
* needs the dual-setpoint split, `alarm-panel` must survive a refused `arm`).
|
|
95092
|
+
* Those are a separate task; adding a row here must never mean adding a service
|
|
95093
|
+
* whose semantics this file cannot fully honour.
|
|
95094
|
+
*/
|
|
95095
|
+
/** HomeKit's floor for `CurrentAmbientLightLevel`; 0 lux is out of range. */
|
|
95096
|
+
var MIN_LUX = 1e-4;
|
|
95097
|
+
var SENSOR_SPECS = {
|
|
95098
|
+
contact: {
|
|
95099
|
+
label: "Contact",
|
|
95100
|
+
addService: (accessory, name, subtype) => accessory.addService(import_dist.Service.ContactSensor, name, subtype),
|
|
95101
|
+
getStatus: (proxy) => proxy.contact?.getStatus({}),
|
|
95102
|
+
subscribe: (proxy, onValue) => proxy.state.contact?.subscribe(onValue) ?? null,
|
|
95103
|
+
read: reader(ContactStatusSchema.pick({ entryOpen: true }), (status) => [{
|
|
95104
|
+
characteristic: import_dist.Characteristic.ContactSensorState,
|
|
95105
|
+
value: status.entryOpen ? import_dist.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : import_dist.Characteristic.ContactSensorState.CONTACT_DETECTED
|
|
95106
|
+
}])
|
|
95107
|
+
},
|
|
95108
|
+
motion: {
|
|
95109
|
+
label: "Motion",
|
|
95110
|
+
addService: (accessory, name, subtype) => accessory.addService(import_dist.Service.MotionSensor, name, subtype),
|
|
95111
|
+
getStatus: (proxy) => proxy.motion?.getStatus({}),
|
|
95112
|
+
subscribe: (proxy, onValue) => proxy.state.motion?.subscribe(onValue) ?? null,
|
|
95113
|
+
read: reader(MotionStatusSchema.pick({ detected: true }), (status) => [{
|
|
95114
|
+
characteristic: import_dist.Characteristic.MotionDetected,
|
|
95115
|
+
value: status.detected
|
|
95116
|
+
}])
|
|
95117
|
+
},
|
|
95118
|
+
presence: {
|
|
95119
|
+
label: "Presence",
|
|
95120
|
+
addService: (accessory, name, subtype) => accessory.addService(import_dist.Service.OccupancySensor, name, subtype),
|
|
95121
|
+
getStatus: (proxy) => proxy.presence?.getStatus({}),
|
|
95122
|
+
subscribe: (proxy, onValue) => proxy.state.presence?.subscribe(onValue) ?? null,
|
|
95123
|
+
read: reader(PresenceStatusSchema.pick({ state: true }), (status) => [{
|
|
95124
|
+
characteristic: import_dist.Characteristic.OccupancyDetected,
|
|
95125
|
+
value: status.state === "home" ? import_dist.Characteristic.OccupancyDetected.OCCUPANCY_DETECTED : import_dist.Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED
|
|
95126
|
+
}])
|
|
95127
|
+
},
|
|
95128
|
+
smoke: {
|
|
95129
|
+
label: "Smoke",
|
|
95130
|
+
addService: (accessory, name, subtype) => accessory.addService(import_dist.Service.SmokeSensor, name, subtype),
|
|
95131
|
+
getStatus: (proxy) => proxy.smoke?.getStatus({}),
|
|
95132
|
+
subscribe: (proxy, onValue) => proxy.state.smoke?.subscribe(onValue) ?? null,
|
|
95133
|
+
read: reader(SmokeStatusSchema.pick({ detected: true }), (status) => [{
|
|
95134
|
+
characteristic: import_dist.Characteristic.SmokeDetected,
|
|
95135
|
+
value: status.detected ? import_dist.Characteristic.SmokeDetected.SMOKE_DETECTED : import_dist.Characteristic.SmokeDetected.SMOKE_NOT_DETECTED
|
|
95136
|
+
}])
|
|
95137
|
+
},
|
|
95138
|
+
"carbon-monoxide": {
|
|
95139
|
+
label: "Carbon monoxide",
|
|
95140
|
+
addService: (accessory, name, subtype) => accessory.addService(import_dist.Service.CarbonMonoxideSensor, name, subtype),
|
|
95141
|
+
getStatus: (proxy) => proxy.carbonMonoxide?.getStatus({}),
|
|
95142
|
+
subscribe: (proxy, onValue) => proxy.state.carbonMonoxide?.subscribe(onValue) ?? null,
|
|
95143
|
+
read: reader(CarbonMonoxideStatusSchema.pick({ detected: true }), (status) => [{
|
|
95144
|
+
characteristic: import_dist.Characteristic.CarbonMonoxideDetected,
|
|
95145
|
+
value: status.detected ? import_dist.Characteristic.CarbonMonoxideDetected.CO_LEVELS_ABNORMAL : import_dist.Characteristic.CarbonMonoxideDetected.CO_LEVELS_NORMAL
|
|
95146
|
+
}])
|
|
95147
|
+
},
|
|
95148
|
+
flood: {
|
|
95149
|
+
label: "Leak",
|
|
95150
|
+
addService: (accessory, name, subtype) => accessory.addService(import_dist.Service.LeakSensor, name, subtype),
|
|
95151
|
+
getStatus: (proxy) => proxy.flood?.getStatus({}),
|
|
95152
|
+
subscribe: (proxy, onValue) => proxy.state.flood?.subscribe(onValue) ?? null,
|
|
95153
|
+
read: reader(FloodStatusSchema.pick({ flooded: true }), (status) => [{
|
|
95154
|
+
characteristic: import_dist.Characteristic.LeakDetected,
|
|
95155
|
+
value: status.flooded ? import_dist.Characteristic.LeakDetected.LEAK_DETECTED : import_dist.Characteristic.LeakDetected.LEAK_NOT_DETECTED
|
|
95156
|
+
}])
|
|
95157
|
+
},
|
|
95158
|
+
"temperature-sensor": {
|
|
95159
|
+
label: "Temperature",
|
|
95160
|
+
addService: (accessory, name, subtype) => accessory.addService(import_dist.Service.TemperatureSensor, name, subtype),
|
|
95161
|
+
getStatus: (proxy) => proxy.temperatureSensor?.getStatus({}),
|
|
95162
|
+
subscribe: (proxy, onValue) => proxy.state.temperatureSensor?.subscribe(onValue) ?? null,
|
|
95163
|
+
read: reader(TemperatureSensorStatusSchema.pick({ celsius: true }), (status) => [{
|
|
95164
|
+
characteristic: import_dist.Characteristic.CurrentTemperature,
|
|
95165
|
+
value: status.celsius
|
|
95166
|
+
}])
|
|
95167
|
+
},
|
|
95168
|
+
"humidity-sensor": {
|
|
95169
|
+
label: "Humidity",
|
|
95170
|
+
addService: (accessory, name, subtype) => accessory.addService(import_dist.Service.HumiditySensor, name, subtype),
|
|
95171
|
+
getStatus: (proxy) => proxy.humiditySensor?.getStatus({}),
|
|
95172
|
+
subscribe: (proxy, onValue) => proxy.state.humiditySensor?.subscribe(onValue) ?? null,
|
|
95173
|
+
read: reader(HumiditySensorStatusSchema.pick({ percent: true }), (status) => [{
|
|
95174
|
+
characteristic: import_dist.Characteristic.CurrentRelativeHumidity,
|
|
95175
|
+
value: status.percent
|
|
95176
|
+
}])
|
|
95177
|
+
},
|
|
95178
|
+
"ambient-light-sensor": {
|
|
95179
|
+
label: "Light level",
|
|
95180
|
+
addService: (accessory, name, subtype) => accessory.addService(import_dist.Service.LightSensor, name, subtype),
|
|
95181
|
+
getStatus: (proxy) => proxy.ambientLightSensor?.getStatus({}),
|
|
95182
|
+
subscribe: (proxy, onValue) => proxy.state.ambientLightSensor?.subscribe(onValue) ?? null,
|
|
95183
|
+
read: reader(AmbientLightSensorStatusSchema.pick({ lux: true }), (status) => [{
|
|
95184
|
+
characteristic: import_dist.Characteristic.CurrentAmbientLightLevel,
|
|
95185
|
+
value: Math.max(MIN_LUX, status.lux)
|
|
95186
|
+
}])
|
|
95187
|
+
}
|
|
95188
|
+
};
|
|
95189
|
+
/**
|
|
95190
|
+
* The `battery` row reads like a sensor but writes three characteristics from
|
|
95191
|
+
* one status, and the camera path publishes the SAME service from the same cap.
|
|
95192
|
+
* The derivation therefore lives once, in `battery.ts`, and both callers read
|
|
95193
|
+
* it from there.
|
|
95194
|
+
*/
|
|
95195
|
+
var BATTERY_SPEC = {
|
|
95196
|
+
label: "Battery",
|
|
95197
|
+
addService: (accessory, name, subtype) => accessory.addService(import_dist.Service.Battery, name, subtype),
|
|
95198
|
+
getStatus: (proxy) => proxy.battery?.getStatus({}),
|
|
95199
|
+
subscribe: (proxy, onValue) => proxy.state.battery?.subscribe(onValue) ?? null,
|
|
95200
|
+
read: batteryCharacteristicUpdates
|
|
95201
|
+
};
|
|
95202
|
+
function sensorRow(capName, spec) {
|
|
95203
|
+
return {
|
|
95204
|
+
caps: [capName],
|
|
95205
|
+
label: spec.label,
|
|
95206
|
+
build: ({ bctx, name }) => buildSensorService({
|
|
95207
|
+
bctx,
|
|
95208
|
+
spec,
|
|
95209
|
+
name,
|
|
95210
|
+
subtype: capName
|
|
95211
|
+
})
|
|
95212
|
+
};
|
|
95213
|
+
}
|
|
95214
|
+
/**
|
|
95215
|
+
* THE table. Order matters only for one thing: the first row that matches a
|
|
95216
|
+
* device decides the accessory's category when the device's own type does not.
|
|
95217
|
+
*/
|
|
95218
|
+
var HAP_CAP_SERVICES = [
|
|
95219
|
+
{
|
|
95220
|
+
caps: ["switch", "brightness"],
|
|
95221
|
+
label: "Power",
|
|
95222
|
+
build: ({ bctx, name, deviceType }) => buildChildSwitch({
|
|
95223
|
+
...bctx,
|
|
95224
|
+
displayName: name
|
|
95225
|
+
}, "switch", deviceType)
|
|
95226
|
+
},
|
|
95227
|
+
{
|
|
95228
|
+
caps: ["lock-control"],
|
|
95229
|
+
label: "Lock",
|
|
95230
|
+
build: ({ bctx, name }) => buildLockMechanism({
|
|
95231
|
+
...bctx,
|
|
95232
|
+
displayName: name
|
|
95233
|
+
})
|
|
95234
|
+
},
|
|
95235
|
+
{
|
|
95236
|
+
caps: ["battery"],
|
|
95237
|
+
label: BATTERY_SPEC.label,
|
|
95238
|
+
build: ({ bctx, name }) => buildSensorService({
|
|
95239
|
+
bctx,
|
|
95240
|
+
spec: BATTERY_SPEC,
|
|
95241
|
+
name,
|
|
95242
|
+
subtype: "battery"
|
|
95243
|
+
})
|
|
95244
|
+
},
|
|
95245
|
+
...Object.entries(SENSOR_SPECS).map(([capName, spec]) => sensorRow(capName, spec))
|
|
95246
|
+
];
|
|
95247
|
+
/**
|
|
95248
|
+
* The rows a device's bound capabilities select, in table order.
|
|
95249
|
+
*
|
|
95250
|
+
* A row matches when ANY of its caps is bound: a lamp with `switch` but no
|
|
95251
|
+
* `brightness` is still a Lightbulb-shaped row, and the builder degrades on its
|
|
95252
|
+
* own.
|
|
95253
|
+
*/
|
|
95254
|
+
function rowsForCaps(capNames) {
|
|
95255
|
+
return HAP_CAP_SERVICES.filter((row) => row.caps.some((cap) => capNames.has(cap)));
|
|
95256
|
+
}
|
|
95257
|
+
/**
|
|
95258
|
+
* The HomeKit accessory category per camstack device type — the icon iOS Home
|
|
95259
|
+
* draws and nothing else. Absent = `OTHER`, which is a valid accessory that
|
|
95260
|
+
* simply gets the generic tile.
|
|
95261
|
+
*
|
|
95262
|
+
* This is NOT a coverage decision. `HAP_EXPORTABLE_DEVICE_TYPES` (in
|
|
95263
|
+
* `mappers/index.ts`) filters the picker; whether a device exports anything is
|
|
95264
|
+
* decided by `rowsForCaps`.
|
|
95265
|
+
*/
|
|
95266
|
+
var ACCESSORY_CATEGORY_BY_TYPE = {
|
|
95267
|
+
[DeviceType.Switch]: import_dist.Categories.SWITCH,
|
|
95268
|
+
[DeviceType.Light]: import_dist.Categories.LIGHTBULB,
|
|
95269
|
+
[DeviceType.Siren]: import_dist.Categories.SWITCH,
|
|
95270
|
+
[DeviceType.Lock]: import_dist.Categories.DOOR_LOCK,
|
|
95271
|
+
[DeviceType.Sensor]: import_dist.Categories.SENSOR,
|
|
95272
|
+
[DeviceType.Presence]: import_dist.Categories.SENSOR
|
|
95273
|
+
};
|
|
95274
|
+
function accessoryCategoryFor(deviceType) {
|
|
95275
|
+
return ACCESSORY_CATEGORY_BY_TYPE[deviceType] ?? import_dist.Categories.OTHER;
|
|
95276
|
+
}
|
|
95277
|
+
//#endregion
|
|
95278
|
+
//#region src/mappers/generic-accessory.ts
|
|
95279
|
+
/**
|
|
95280
|
+
* Generic accessory orchestrator — every camstack device that is NOT a camera.
|
|
95281
|
+
*
|
|
95282
|
+
* Same three steps as the camera orchestrator, and deliberately nothing more:
|
|
95283
|
+
* 1. resolve the typed `DeviceProxy`,
|
|
95284
|
+
* 2. select rows from the capability→service table with the device's BOUND
|
|
95285
|
+
* capabilities (never its type — see `builders/generic/cap-service-table.ts`),
|
|
95286
|
+
* 3. let each row add its service to one `Accessory`.
|
|
95287
|
+
*
|
|
95288
|
+
* It publishes standalone, like the camera path: one accessory, one mDNS
|
|
95289
|
+
* advertisement, one pairing, the shared setup code. This addon is not a
|
|
95290
|
+
* bridge — that decision predates this file and is not re-litigated here.
|
|
95291
|
+
*
|
|
95292
|
+
* ## It REFUSES rather than publishing an empty tile
|
|
95293
|
+
*
|
|
95294
|
+
* A device whose capabilities select no row throws. The alternative is an
|
|
95295
|
+
* accessory carrying nothing but `AccessoryInformation`: it pairs, it appears
|
|
95296
|
+
* in the Home app, it does nothing, and the operator has no way to tell that
|
|
95297
|
+
* from a broken integration. The Export tab is gated on the same question
|
|
95298
|
+
* (`hap-export.addon.ts`), so reaching this throw means the device changed
|
|
95299
|
+
* shape between the tab rendering and the toggle landing.
|
|
95300
|
+
*/
|
|
95301
|
+
async function buildGenericAccessory(input) {
|
|
95302
|
+
const { ctx, deviceId, displayName, options } = input;
|
|
95303
|
+
const numericId = Number.parseInt(deviceId, 10);
|
|
95304
|
+
if (!Number.isFinite(numericId)) throw new Error(`export-hap: cannot map device '${deviceId}' — id is not numeric`);
|
|
95305
|
+
const log = ctx.logger.withTags({ deviceId: numericId });
|
|
95306
|
+
const proxy = await ctx.fetchDevice(numericId);
|
|
95307
|
+
const capNames = new Set(proxy.binding?.entries.map((e) => e.capName) ?? []);
|
|
95308
|
+
const rows = rowsForCaps(capNames);
|
|
95309
|
+
if (rows.length === 0) throw new Error(`export-hap: device ${numericId} carries no capability HomeKit can export (bound: ${[...capNames].toSorted().join(", ") || "none"})`);
|
|
95310
|
+
const deviceType = await resolveDeviceType(proxy);
|
|
95311
|
+
const accessory = new import_dist.Accessory(displayName, accessoryUuidFor("generic", numericId));
|
|
95312
|
+
accessory.category = accessoryCategoryFor(deviceType);
|
|
95313
|
+
await populateAccessoryInfo(accessory, proxy, displayName, "Accessory");
|
|
95314
|
+
const bctx = {
|
|
95315
|
+
ctx,
|
|
95316
|
+
accessory,
|
|
95317
|
+
proxy,
|
|
95318
|
+
numericDeviceId: numericId,
|
|
95319
|
+
displayName,
|
|
95320
|
+
options
|
|
95321
|
+
};
|
|
95322
|
+
const handles = [];
|
|
95323
|
+
for (const row of rows) {
|
|
95324
|
+
const name = rows.length === 1 ? displayName : row.label;
|
|
95325
|
+
handles.push(await row.build({
|
|
95326
|
+
bctx,
|
|
95327
|
+
name,
|
|
95328
|
+
deviceType
|
|
95329
|
+
}));
|
|
95330
|
+
}
|
|
95331
|
+
log.info("export-hap: built generic accessory", { meta: {
|
|
95332
|
+
deviceType,
|
|
95333
|
+
services: rows.map((row) => row.caps[0]).join(",")
|
|
95334
|
+
} });
|
|
95335
|
+
return {
|
|
95336
|
+
accessory,
|
|
95337
|
+
accessories: [accessory],
|
|
95338
|
+
async dispose() {
|
|
95339
|
+
for (const handle of handles) try {
|
|
95340
|
+
await handle.dispose();
|
|
95341
|
+
} catch (err) {
|
|
95342
|
+
log.debug("export-hap: generic builder dispose failed (continuing)", { meta: { error: errMsg$1(err) } });
|
|
95343
|
+
}
|
|
95344
|
+
}
|
|
95345
|
+
};
|
|
95346
|
+
}
|
|
95347
|
+
/**
|
|
95348
|
+
* The device's own type, or `Generic`.
|
|
95349
|
+
*
|
|
95350
|
+
* Used ONLY where one capability has two HomeKit shapes — a siren's
|
|
95351
|
+
* `brightness` is alarm volume, not luminosity — and for the accessory's icon.
|
|
95352
|
+
* `Generic` is the safe reading: it is the shape `child-switch.ts` already
|
|
95353
|
+
* treats as "a lamp if it dims, a switch otherwise".
|
|
95354
|
+
*/
|
|
95355
|
+
async function resolveDeviceType(proxy) {
|
|
95356
|
+
try {
|
|
95357
|
+
const device = await proxy.deviceManager?.getDevice({});
|
|
95358
|
+
const raw = typeof device?.type === "string" ? device.type.toLowerCase() : "";
|
|
95359
|
+
return Object.values(DeviceType).find((value) => value === raw) ?? DeviceType.Generic;
|
|
95360
|
+
} catch {
|
|
95361
|
+
return DeviceType.Generic;
|
|
95362
|
+
}
|
|
92883
95363
|
}
|
|
92884
95364
|
function errMsg$1(err) {
|
|
92885
95365
|
return err instanceof Error ? err.message : String(err);
|
|
92886
95366
|
}
|
|
92887
95367
|
//#endregion
|
|
92888
95368
|
//#region src/mappers/index.ts
|
|
92889
|
-
var
|
|
92890
|
-
|
|
95369
|
+
var REGISTRY = {
|
|
95370
|
+
camera: buildCameraAccessory,
|
|
95371
|
+
generic: buildGenericAccessory
|
|
95372
|
+
};
|
|
92891
95373
|
function getMapperFactory(kind) {
|
|
92892
95374
|
const factory = REGISTRY[kind];
|
|
92893
95375
|
if (!factory) throw new Error(`export-hap: no mapper registered for kind '${kind}'`);
|
|
92894
95376
|
return factory;
|
|
92895
95377
|
}
|
|
92896
|
-
/**
|
|
92897
|
-
* Resolve the best-fit mapper kind. With Round 2, the operator just
|
|
92898
|
-
* picks a camera and the orchestrator does the rest — the single
|
|
92899
|
-
* `camera` kind is returned unconditionally. We keep the function
|
|
92900
|
-
* signature for backwards-compat with the addon's existing
|
|
92901
|
-
* `exposeDevice` flow and to leave room for future device types (NVR,
|
|
92902
|
-
* climate sensor, ...).
|
|
92903
|
-
*/
|
|
92904
|
-
function pickMapperKind(_capabilities) {
|
|
92905
|
-
return "camera";
|
|
92906
|
-
}
|
|
92907
95378
|
//#endregion
|
|
92908
95379
|
//#region src/mappers/builders/stream-hwaccel-memo.ts
|
|
92909
95380
|
/**
|
|
@@ -93041,8 +95512,8 @@ function syncStateToJson(map) {
|
|
|
93041
95512
|
//#endregion
|
|
93042
95513
|
//#region src/hap-export.addon.ts
|
|
93043
95514
|
/**
|
|
93044
|
-
* HomeKit (HAP) export addon — publishes
|
|
93045
|
-
*
|
|
95515
|
+
* HomeKit (HAP) export addon — publishes selected camstack devices as
|
|
95516
|
+
* standalone HomeKit accessories.
|
|
93046
95517
|
*
|
|
93047
95518
|
* Operator flow:
|
|
93048
95519
|
* 1. Install + enable the addon (hub-only, group `export-hap`).
|
|
@@ -93053,10 +95524,10 @@ function syncStateToJson(map) {
|
|
|
93053
95524
|
* 3. The setup URI (`X-HM://…`) is logged AND surfaced via the
|
|
93054
95525
|
* `getStatus` cap method so the wizard UI can render the QR.
|
|
93055
95526
|
* 4. Operator opens iOS Home → + → scan QR → enter pincode.
|
|
93056
|
-
* 5. Operator hits "Expose to HomeKit" on individual camstack
|
|
93057
|
-
*
|
|
93058
|
-
*
|
|
93059
|
-
*
|
|
95527
|
+
* 5. Operator hits "Expose to HomeKit" on individual camstack devices.
|
|
95528
|
+
* Each exposed device is published as its OWN accessory with its own
|
|
95529
|
+
* mDNS advertisement, sharing one setup code — cameras cannot be
|
|
95530
|
+
* bridged, so nothing is.
|
|
93060
95531
|
*
|
|
93061
95532
|
* Exception — the hap-nodejs library's own `HAPStorage` lives under
|
|
93062
95533
|
* `ctx.dataDir/hap-store/`. That directory is library-internal: the
|
|
@@ -93065,23 +95536,20 @@ function syncStateToJson(map) {
|
|
|
93065
95536
|
* I/O for addon-owned data") explicitly scopes itself to OUR own data;
|
|
93066
95537
|
* library-managed blobs are out of scope.
|
|
93067
95538
|
*
|
|
93068
|
-
*
|
|
93069
|
-
*
|
|
93070
|
-
*
|
|
93071
|
-
*
|
|
93072
|
-
*
|
|
93073
|
-
*
|
|
93074
|
-
*
|
|
93075
|
-
*
|
|
93076
|
-
*
|
|
93077
|
-
*
|
|
93078
|
-
*
|
|
93079
|
-
*
|
|
93080
|
-
*
|
|
93081
|
-
*
|
|
93082
|
-
* What's deferred to Round 3+: cam→browser audio Opus transcode,
|
|
93083
|
-
* intercom upload bridge, HomeKit Secure Video, recording, native
|
|
93084
|
-
* H.264 stream tap (currently uses RTSP + ffmpeg copy).
|
|
95539
|
+
* ## Two accessory shapes, one rule about coverage
|
|
95540
|
+
*
|
|
95541
|
+
* `exposeDevice({deviceId})` resolves the device's TYPE to a mapper kind
|
|
95542
|
+
* (`mappers/kind.ts`) and hands off:
|
|
95543
|
+
*
|
|
95544
|
+
* - a camera → `mappers/camera-accessory.ts`: streams, HKSV recording,
|
|
95545
|
+
* doorbell, motion, intercom, PTZ, battery, privacy and every accessory
|
|
95546
|
+
* child, all as services on one accessory;
|
|
95547
|
+
* - anything else → `mappers/generic-accessory.ts`, driven by the
|
|
95548
|
+
* capability→service table in `mappers/builders/generic/`.
|
|
95549
|
+
*
|
|
95550
|
+
* The type only picks the SHAPE. What a device publishes is decided by the
|
|
95551
|
+
* capabilities bound to it — restricting coverage by type is precisely what
|
|
95552
|
+
* confined this exporter to cameras for its first three rounds.
|
|
93085
95553
|
*/
|
|
93086
95554
|
var DEFAULT_DEVICE_SETTINGS = {
|
|
93087
95555
|
streamPreference: "auto",
|
|
@@ -93124,7 +95592,6 @@ var DEFAULT_CONFIG = {
|
|
|
93124
95592
|
fixedPin: "",
|
|
93125
95593
|
interfaceName: "",
|
|
93126
95594
|
ptzPulseMs: 400,
|
|
93127
|
-
hksvPreview: false,
|
|
93128
95595
|
identity: {
|
|
93129
95596
|
username: "",
|
|
93130
95597
|
pincode: "",
|
|
@@ -93240,7 +95707,7 @@ var ExportHapAddon = class extends BaseAddon {
|
|
|
93240
95707
|
...setup ? { setup } : {}
|
|
93241
95708
|
};
|
|
93242
95709
|
},
|
|
93243
|
-
listSupportedDeviceKinds: async () => [...
|
|
95710
|
+
listSupportedDeviceKinds: async () => [...HAP_EXPORTABLE_DEVICE_TYPES],
|
|
93244
95711
|
listExposedDevices: async () => Array.from(this.exposed.entries()).map(([deviceId, m]) => {
|
|
93245
95712
|
const entry = this.config.exposed.find((e) => e.deviceId === deviceId);
|
|
93246
95713
|
return {
|
|
@@ -93325,9 +95792,10 @@ var ExportHapAddon = class extends BaseAddon {
|
|
|
93325
95792
|
log.debug("export-hap: device already exposed — refreshing capabilities");
|
|
93326
95793
|
await this.detachMapper(deviceId);
|
|
93327
95794
|
}
|
|
93328
|
-
const
|
|
93329
|
-
|
|
93330
|
-
|
|
95795
|
+
const summary = await this.fetchDeviceSummary(numericId);
|
|
95796
|
+
const mapperKind = pickMapperKind(summary?.type);
|
|
95797
|
+
if (!mapperKind) throw new Error(`export-hap: device ${numericId} has type '${summary?.type ?? "unknown"}', which HomeKit export does not support`);
|
|
95798
|
+
const displayName = summary?.name ?? `Device ${deviceId}`;
|
|
93331
95799
|
const previous = this.config.exposed.find((e) => e.deviceId === deviceId);
|
|
93332
95800
|
const baseEntry = carryForward({
|
|
93333
95801
|
deviceId,
|
|
@@ -93357,10 +95825,11 @@ var ExportHapAddon = class extends BaseAddon {
|
|
|
93357
95825
|
async unexposeDevice(deviceId, options = {}) {
|
|
93358
95826
|
const numericId = Number.parseInt(deviceId, 10);
|
|
93359
95827
|
const log = this.ctx.logger.withTags({ deviceId: numericId });
|
|
95828
|
+
const mapperKind = this.findEntry(numericId)?.mapperKind ?? "camera";
|
|
93360
95829
|
await this.detachMapper(deviceId);
|
|
93361
95830
|
const next = this.config.exposed.filter((e) => e.deviceId !== deviceId);
|
|
93362
95831
|
if (next.length !== this.config.exposed.length) await this.updateGlobalSettings({ exposed: next });
|
|
93363
|
-
if (options.clearPairing !== false) clearPairingFiles(
|
|
95832
|
+
if (options.clearPairing !== false) clearPairingFiles(accessoryUuidFor(mapperKind, numericId), this.ctx.logger);
|
|
93364
95833
|
await this.forgetFingerprint(numericId);
|
|
93365
95834
|
log.info("export-hap: unexposed device");
|
|
93366
95835
|
}
|
|
@@ -93410,10 +95879,10 @@ var ExportHapAddon = class extends BaseAddon {
|
|
|
93410
95879
|
/** Pending rebuild timers per deviceId — used to debounce. Cleared
|
|
93411
95880
|
* on detach so stale timers can't republish a removed accessory. */
|
|
93412
95881
|
pendingRebuildTimers = /* @__PURE__ */ new Map();
|
|
93413
|
-
/** Deterministic HAP accessory UUID for a device
|
|
93414
|
-
*
|
|
95882
|
+
/** Deterministic HAP accessory UUID for a device — the same one the
|
|
95883
|
+
* orchestrator built it with (`mappers/kind.ts`). */
|
|
93415
95884
|
hapAccessoryUuid(deviceId) {
|
|
93416
|
-
return
|
|
95885
|
+
return accessoryUuidFor(this.findEntry(deviceId)?.mapperKind ?? "camera", deviceId);
|
|
93417
95886
|
}
|
|
93418
95887
|
/**
|
|
93419
95888
|
* Export fingerprint of a device from its PERSISTED features + type
|
|
@@ -93572,31 +96041,79 @@ var ExportHapAddon = class extends BaseAddon {
|
|
|
93572
96041
|
log.warn("export-hap: reconcile rebuild failed", { meta: { error: errMsg(err) } });
|
|
93573
96042
|
}
|
|
93574
96043
|
}
|
|
93575
|
-
|
|
93576
|
-
|
|
93577
|
-
|
|
96044
|
+
/**
|
|
96045
|
+
* The device's name and type, or `null` when the registry cannot answer.
|
|
96046
|
+
*
|
|
96047
|
+
* `null` is NOT "no such device" — it also covers a transient API failure,
|
|
96048
|
+
* and every caller treats it as "keep doing what was already being done"
|
|
96049
|
+
* rather than re-shaping an exposed accessory on incomplete information.
|
|
96050
|
+
*/
|
|
96051
|
+
async fetchDeviceSummary(deviceId) {
|
|
96052
|
+
if (!Number.isFinite(deviceId)) return null;
|
|
93578
96053
|
try {
|
|
93579
|
-
const device = await this.ctx.api.deviceManager?.getDevice.query({ deviceId
|
|
93580
|
-
if (device
|
|
96054
|
+
const device = await this.ctx.api.deviceManager?.getDevice.query({ deviceId });
|
|
96055
|
+
if (!device) return null;
|
|
96056
|
+
return {
|
|
96057
|
+
name: device.name,
|
|
96058
|
+
type: device.type
|
|
96059
|
+
};
|
|
93581
96060
|
} catch (err) {
|
|
93582
|
-
this.ctx.logger.withTags({ deviceId
|
|
96061
|
+
this.ctx.logger.withTags({ deviceId }).debug("export-hap: deviceManager.getDevice failed", { meta: { error: errMsg(err) } });
|
|
96062
|
+
return null;
|
|
96063
|
+
}
|
|
96064
|
+
}
|
|
96065
|
+
/**
|
|
96066
|
+
* Which accessory shape this device would get, or `null` for "no Export tab".
|
|
96067
|
+
*
|
|
96068
|
+
* Two questions, in this order, because they fail differently:
|
|
96069
|
+
* 1. the TYPE — a type with no orchestrator is refused outright;
|
|
96070
|
+
* 2. for a non-camera, the CAPABILITIES — a device carrying nothing the
|
|
96071
|
+
* table maps would publish an accessory that pairs and does nothing, and
|
|
96072
|
+
* the operator cannot tell that from a broken integration.
|
|
96073
|
+
*
|
|
96074
|
+
* A camera skips (2): the camera orchestrator has always published on type
|
|
96075
|
+
* alone, and adding a cap gate here would be a new way for an existing camera
|
|
96076
|
+
* to lose its Export tab. A registry that cannot answer at all also resolves
|
|
96077
|
+
* to `camera`, which is what every persisted entry predating this method
|
|
96078
|
+
* carries.
|
|
96079
|
+
*/
|
|
96080
|
+
async resolveExportKind(deviceId) {
|
|
96081
|
+
const summary = await this.fetchDeviceSummary(deviceId);
|
|
96082
|
+
const kind = pickMapperKind(summary?.type);
|
|
96083
|
+
if (kind === null) return null;
|
|
96084
|
+
if (kind === "camera") return "camera";
|
|
96085
|
+
try {
|
|
96086
|
+
const proxy = await this.ctx.fetchDevice(deviceId);
|
|
96087
|
+
const capNames = new Set(proxy.binding?.entries.map((entry) => entry.capName) ?? []);
|
|
96088
|
+
if (rowsForCaps(capNames).length > 0) return "generic";
|
|
96089
|
+
this.ctx.logger.withTags({ deviceId }).debug("export-hap: no Export tab — no mapped caps", { meta: {
|
|
96090
|
+
type: summary?.type,
|
|
96091
|
+
caps: [...capNames].toSorted().join(",")
|
|
96092
|
+
} });
|
|
96093
|
+
return null;
|
|
96094
|
+
} catch (err) {
|
|
96095
|
+
this.ctx.logger.withTags({ deviceId }).debug("export-hap: no Export tab — capability read failed", { meta: {
|
|
96096
|
+
type: summary?.type,
|
|
96097
|
+
error: errMsg(err)
|
|
96098
|
+
} });
|
|
96099
|
+
return null;
|
|
93583
96100
|
}
|
|
93584
|
-
return `Device ${deviceId}`;
|
|
93585
96101
|
}
|
|
93586
96102
|
globalSettingsSchema() {
|
|
93587
96103
|
return this.schema({ sections: [{
|
|
93588
96104
|
id: "export-hap",
|
|
93589
96105
|
title: "HomeKit Export",
|
|
93590
|
-
description: "Publishes
|
|
96106
|
+
description: "Publishes each exposed device as its own HomeKit accessory, sharing one setup code. Mark a device \"Expose to HomeKit\" on its settings page, then add it in the iOS Home app (+ → Add Accessory) and enter the setup code shown there.",
|
|
93591
96107
|
columns: 1,
|
|
93592
96108
|
fields: [
|
|
93593
96109
|
this.field({
|
|
93594
96110
|
type: "text",
|
|
93595
96111
|
key: "bridgeName",
|
|
93596
|
-
label: "
|
|
93597
|
-
description: "
|
|
96112
|
+
label: "Installation name",
|
|
96113
|
+
description: "Legacy. Every exposed device is published STANDALONE, under its own camstack device name — HomeKit cameras cannot be bridged, so nothing is bridged. This string only seeds the identity generated on first boot and is not shown in iOS Home.",
|
|
93598
96114
|
default: DEFAULT_CONFIG.bridgeName,
|
|
93599
|
-
requiresRestart: true
|
|
96115
|
+
requiresRestart: true,
|
|
96116
|
+
placement: { tab: "advanced" }
|
|
93600
96117
|
}),
|
|
93601
96118
|
this.field({
|
|
93602
96119
|
type: "number",
|
|
@@ -93632,21 +96149,13 @@ var ExportHapAddon = class extends BaseAddon {
|
|
|
93632
96149
|
description: "Duration of a single pan/tilt momentary command issued by the PTZ direction switches in Apple Home. Lower = finer steps; higher = bigger sweeps per tap. Defaults to 400ms.",
|
|
93633
96150
|
default: DEFAULT_CONFIG.ptzPulseMs,
|
|
93634
96151
|
placement: { tab: "advanced" }
|
|
93635
|
-
}),
|
|
93636
|
-
this.field({
|
|
93637
|
-
type: "boolean",
|
|
93638
|
-
key: "hksvPreview",
|
|
93639
|
-
label: "HKSV Developer Preview (experimental)",
|
|
93640
|
-
description: "Advertise Apple's HomeKit Secure Video Developer Preview services — HEVC streaming and the WebRTC stream-management surface — alongside the classic camera profile. Apple published this specification on 2026-06-03 and no shipping controller is known to negotiate it yet. Leave off unless you are testing against a preview build: advertising unknown services to a paired controller can disturb the classic path that works today.",
|
|
93641
|
-
default: DEFAULT_CONFIG.hksvPreview,
|
|
93642
|
-
requiresRestart: true,
|
|
93643
|
-
placement: { tab: "advanced" }
|
|
93644
96152
|
})
|
|
93645
96153
|
]
|
|
93646
96154
|
}] });
|
|
93647
96155
|
}
|
|
93648
96156
|
async buildDeviceSettingsContribution(deviceId) {
|
|
93649
|
-
|
|
96157
|
+
const kind = await this.resolveExportKind(deviceId);
|
|
96158
|
+
if (kind === null) return null;
|
|
93650
96159
|
const entry = this.findEntry(deviceId);
|
|
93651
96160
|
const settings = entry?.settings ?? DEFAULT_DEVICE_SETTINGS;
|
|
93652
96161
|
const enabled = entry !== null;
|
|
@@ -93663,6 +96172,32 @@ var ExportHapAddon = class extends BaseAddon {
|
|
|
93663
96172
|
} catch (err) {
|
|
93664
96173
|
this.ctx.logger.withTags({ deviceId }).debug("export-hap: setupURI failed for per-device contribution", { meta: { error: errMsg(err) } });
|
|
93665
96174
|
}
|
|
96175
|
+
const cameraFields = kind !== "camera" ? [] : [{
|
|
96176
|
+
type: "select",
|
|
96177
|
+
key: streamPreferenceKey,
|
|
96178
|
+
label: "Source stream (HomeKit)",
|
|
96179
|
+
description: "Which camstack profile slot HomeKit pulls. Auto = the broker picks the slot closest to 1080p at session start.",
|
|
96180
|
+
options: HAP_STREAM_PREFERENCE_OPTIONS,
|
|
96181
|
+
required: true,
|
|
96182
|
+
value: settings.streamPreference,
|
|
96183
|
+
showWhen: {
|
|
96184
|
+
field: enabledKey,
|
|
96185
|
+
equals: true
|
|
96186
|
+
},
|
|
96187
|
+
immediate: true
|
|
96188
|
+
}, {
|
|
96189
|
+
type: "boolean",
|
|
96190
|
+
key: hksvKey,
|
|
96191
|
+
label: "HomeKit recording (Secure Video)",
|
|
96192
|
+
description: "Offer “Stream and Allow Recording” in iOS Home. Requires iCloud+ and a home hub. Keeps a continuous 8s prebuffer for this camera (~0.7% of one CPU core, H.264 sources only).",
|
|
96193
|
+
style: "switch",
|
|
96194
|
+
value: resolveHksvRecording(settings),
|
|
96195
|
+
showWhen: {
|
|
96196
|
+
field: enabledKey,
|
|
96197
|
+
equals: true
|
|
96198
|
+
},
|
|
96199
|
+
immediate: true
|
|
96200
|
+
}];
|
|
93666
96201
|
return {
|
|
93667
96202
|
tabs: [{
|
|
93668
96203
|
id: "export",
|
|
@@ -93673,7 +96208,7 @@ var ExportHapAddon = class extends BaseAddon {
|
|
|
93673
96208
|
sections: [{
|
|
93674
96209
|
id: "export-hap",
|
|
93675
96210
|
title: "HomeKit Export",
|
|
93676
|
-
description: "
|
|
96211
|
+
description: kind === "camera" ? "Publish this camera as its own HomeKit accessory so iOS Home can pair with it." : "Publish this device as its own HomeKit accessory. Its capabilities decide which controls iOS Home shows.",
|
|
93677
96212
|
tab: "export",
|
|
93678
96213
|
columns: 1,
|
|
93679
96214
|
order: 10,
|
|
@@ -93689,7 +96224,7 @@ var ExportHapAddon = class extends BaseAddon {
|
|
|
93689
96224
|
type: "qr-code",
|
|
93690
96225
|
key: "__hap-pair-qr",
|
|
93691
96226
|
label: paired ? "Pairing QR (already paired)" : "Pairing QR",
|
|
93692
|
-
caption: paired ? `Already paired with at least one device. Scan from another iPhone / iPad to add it there too. Setup code: ${this.config.identity.pincode}.` : `Scan with the iOS Camera app to pair this
|
|
96227
|
+
caption: paired ? `Already paired with at least one device. Scan from another iPhone / iPad to add it there too. Setup code: ${this.config.identity.pincode}.` : `Scan with the iOS Camera app to pair this accessory in HomeKit. Setup code: ${this.config.identity.pincode}.`,
|
|
93693
96228
|
value: qrValue,
|
|
93694
96229
|
size: 192,
|
|
93695
96230
|
alt: `HomeKit pairing QR for ${name}`,
|
|
@@ -93702,38 +96237,12 @@ var ExportHapAddon = class extends BaseAddon {
|
|
|
93702
96237
|
type: "boolean",
|
|
93703
96238
|
key: enabledKey,
|
|
93704
96239
|
label: "Expose to HomeKit",
|
|
93705
|
-
description: "Toggle to publish this
|
|
96240
|
+
description: "Toggle to publish this device as a HomeKit accessory.",
|
|
93706
96241
|
style: "switch",
|
|
93707
96242
|
value: enabled,
|
|
93708
96243
|
immediate: true
|
|
93709
96244
|
},
|
|
93710
|
-
|
|
93711
|
-
type: "select",
|
|
93712
|
-
key: streamPreferenceKey,
|
|
93713
|
-
label: "Source stream (HomeKit)",
|
|
93714
|
-
description: "Which camstack profile slot HomeKit pulls. Auto = the broker picks the slot closest to 1080p at session start.",
|
|
93715
|
-
options: HAP_STREAM_PREFERENCE_OPTIONS,
|
|
93716
|
-
required: true,
|
|
93717
|
-
value: settings.streamPreference,
|
|
93718
|
-
showWhen: {
|
|
93719
|
-
field: enabledKey,
|
|
93720
|
-
equals: true
|
|
93721
|
-
},
|
|
93722
|
-
immediate: true
|
|
93723
|
-
},
|
|
93724
|
-
{
|
|
93725
|
-
type: "boolean",
|
|
93726
|
-
key: hksvKey,
|
|
93727
|
-
label: "HomeKit recording (Secure Video)",
|
|
93728
|
-
description: "Offer “Stream and Allow Recording” in iOS Home. Requires iCloud+ and a home hub. Keeps a continuous 8s prebuffer for this camera (~0.7% of one CPU core, H.264 sources only).",
|
|
93729
|
-
style: "switch",
|
|
93730
|
-
value: resolveHksvRecording(settings),
|
|
93731
|
-
showWhen: {
|
|
93732
|
-
field: enabledKey,
|
|
93733
|
-
equals: true
|
|
93734
|
-
},
|
|
93735
|
-
immediate: true
|
|
93736
|
-
}
|
|
96245
|
+
...cameraFields
|
|
93737
96246
|
]
|
|
93738
96247
|
}]
|
|
93739
96248
|
};
|
|
@@ -93799,23 +96308,6 @@ var ExportHapAddon = class extends BaseAddon {
|
|
|
93799
96308
|
}
|
|
93800
96309
|
return { success: true };
|
|
93801
96310
|
}
|
|
93802
|
-
/**
|
|
93803
|
-
* Camera-only gate — HomeKit export only knows how to mirror cameras
|
|
93804
|
-
* today. Mirrors the snapshot addon's source-side filter so the
|
|
93805
|
-
* device-details page doesn't render an "Export" tab on lights /
|
|
93806
|
-
* switches / sensors.
|
|
93807
|
-
*/
|
|
93808
|
-
async isCameraDevice(deviceId) {
|
|
93809
|
-
const api = this.ctx.api;
|
|
93810
|
-
if (!api.deviceManager) return true;
|
|
93811
|
-
try {
|
|
93812
|
-
const dev = await api.deviceManager.getDevice.query({ deviceId });
|
|
93813
|
-
if (!dev) return true;
|
|
93814
|
-
return dev.type === DeviceType.Camera;
|
|
93815
|
-
} catch {
|
|
93816
|
-
return true;
|
|
93817
|
-
}
|
|
93818
|
-
}
|
|
93819
96311
|
findEntry(deviceId) {
|
|
93820
96312
|
const id = String(deviceId);
|
|
93821
96313
|
return this.config.exposed.find((e) => e.deviceId === id) ?? null;
|