@camstack/addon-provider-enpal 0.1.16 → 0.1.17
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/CHANGELOG.md +6 -0
- package/dist/addon.js +1253 -516
- package/dist/addon.mjs +1253 -516
- package/package.json +2 -2
package/dist/addon.mjs
CHANGED
|
@@ -805,7 +805,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
805
805
|
return EventCategory;
|
|
806
806
|
}({});
|
|
807
807
|
//#endregion
|
|
808
|
-
//#region ../types/dist/composed-stable-id-
|
|
808
|
+
//#region ../types/dist/composed-stable-id-CPp3votu.mjs
|
|
809
809
|
/**
|
|
810
810
|
import { errMsg } from '@camstack/types'
|
|
811
811
|
* Extract a human-readable message from an unknown error value.
|
|
@@ -818,17 +818,18 @@ function errMsg(err) {
|
|
|
818
818
|
}
|
|
819
819
|
var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
820
820
|
DeviceType["Camera"] = "camera";
|
|
821
|
+
/** Legacy stored value, read only by the hub-to-integration migrations; no live device carries it. */
|
|
821
822
|
DeviceType["Hub"] = "hub";
|
|
822
823
|
DeviceType["Light"] = "light";
|
|
823
824
|
DeviceType["Siren"] = "siren";
|
|
824
825
|
DeviceType["Switch"] = "switch";
|
|
825
826
|
DeviceType["Sensor"] = "sensor";
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
* `climate-control`
|
|
829
|
-
*
|
|
830
|
-
*
|
|
831
|
-
*
|
|
827
|
+
/** Every climate device: an air conditioner, a room thermostat, a radiator
|
|
828
|
+
* valve, a floor-heating zone, a heat-pump circuit. They share one set of
|
|
829
|
+
* controls (`climate-control`), so they are one type; WHICH kind of climate
|
|
830
|
+
* device it is is its {@link DeviceRole} (`climate-*`), which the UI and the
|
|
831
|
+
* exports branch on. The retired `thermostat` type is read as this one
|
|
832
|
+
* (`canonicalStoredDeviceType`). */
|
|
832
833
|
DeviceType["Climate"] = "climate";
|
|
833
834
|
DeviceType["Button"] = "button";
|
|
834
835
|
/** Generic stateless event emitter — carries a device's EXACT declared
|
|
@@ -919,10 +920,6 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
|
919
920
|
/** Washing machine. A plug-built one is running while its power stays
|
|
920
921
|
* above a threshold. No program cap — that surface is the dishwasher's. */
|
|
921
922
|
DeviceType["Washer"] = "washer";
|
|
922
|
-
/** A dumb plug-in stove or heater: a switch (and, with a meter, a power
|
|
923
|
-
* reading) that only runs while the plug is on. Built from a smart plug;
|
|
924
|
-
* the `timed-run` cap switches it off again after a chosen time. */
|
|
925
|
-
DeviceType["Heater"] = "heater";
|
|
926
923
|
/** Tumble dryer. Same plug-built running surface as {@link Washer}. */
|
|
927
924
|
DeviceType["Dryer"] = "dryer";
|
|
928
925
|
/** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
|
|
@@ -1142,6 +1139,27 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
|
|
|
1142
1139
|
/** A smart plug: a `switch` whose source says it is an outlet (HA
|
|
1143
1140
|
* `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
|
|
1144
1141
|
DeviceRole["Outlet"] = "outlet";
|
|
1142
|
+
/** A dumb plug-in stove or heater behind a switch: it runs only while the
|
|
1143
|
+
* switch is on. Offered `timed-run`, which switches it off again after a
|
|
1144
|
+
* chosen time. */
|
|
1145
|
+
DeviceRole["SwitchHeater"] = "switch-heater";
|
|
1146
|
+
/** A plain fan behind a switch (a smart plug powering it): drawn as a fan,
|
|
1147
|
+
* with the switch's on/off only. Speed or oscillation appear only when the
|
|
1148
|
+
* device also has a fan cap (D1033). */
|
|
1149
|
+
DeviceRole["SwitchFan"] = "switch-fan";
|
|
1150
|
+
/** Air conditioner / split unit: cooling, fan speed, louvres. */
|
|
1151
|
+
DeviceRole["ClimateAc"] = "climate-ac";
|
|
1152
|
+
/** Room thermostat: a setpoint and a room temperature. The default. */
|
|
1153
|
+
DeviceRole["ClimateThermostat"] = "climate-thermostat";
|
|
1154
|
+
/** Floor-heating zone actuator. */
|
|
1155
|
+
DeviceRole["ClimateFloor"] = "climate-floor";
|
|
1156
|
+
/** Radiator valve (thermostatic radiator head). */
|
|
1157
|
+
DeviceRole["ClimateRadiator"] = "climate-radiator";
|
|
1158
|
+
/** A heater with its own thermostat (stove, convector). */
|
|
1159
|
+
DeviceRole["ClimateHeater"] = "climate-heater";
|
|
1160
|
+
/** One heating/cooling circuit of a heat pump: a water loop with comfort /
|
|
1161
|
+
* eco setpoints and a heat/cool mode, no room sensor. */
|
|
1162
|
+
DeviceRole["ClimateHeatPumpCircuit"] = "climate-heat-pump-circuit";
|
|
1145
1163
|
/** Generic motion / occupancy / moving event source. Distinct from
|
|
1146
1164
|
* the camera accessory PirSensor role: that one is a camera child;
|
|
1147
1165
|
* this is a standalone HA / 3rd-party motion sensor. */
|
|
@@ -6029,7 +6047,7 @@ var ZodIssueCode = {
|
|
|
6029
6047
|
var ZodFirstPartyTypeKind;
|
|
6030
6048
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
6031
6049
|
//#endregion
|
|
6032
|
-
//#region ../types/dist/sleep-
|
|
6050
|
+
//#region ../types/dist/sleep-D0roI_CZ.mjs
|
|
6033
6051
|
/**
|
|
6034
6052
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
6035
6053
|
* window to float samples (D455).
|
|
@@ -11806,6 +11824,50 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
11806
11824
|
value: false
|
|
11807
11825
|
},
|
|
11808
11826
|
args: { oscillating: false }
|
|
11827
|
+
},
|
|
11828
|
+
{
|
|
11829
|
+
id: "fan.horizontalOscillateOn",
|
|
11830
|
+
method: "setOscillationAxis",
|
|
11831
|
+
label: t("Horizontal oscillation on", "Oscillazione orizzontale attiva"),
|
|
11832
|
+
phrase: t("Start the horizontal oscillation of {device}", "Attiva l’oscillazione orizzontale di {device}"),
|
|
11833
|
+
icon: "move-horizontal",
|
|
11834
|
+
args: {
|
|
11835
|
+
axis: "horizontal",
|
|
11836
|
+
oscillating: true
|
|
11837
|
+
}
|
|
11838
|
+
},
|
|
11839
|
+
{
|
|
11840
|
+
id: "fan.horizontalOscillateOff",
|
|
11841
|
+
method: "setOscillationAxis",
|
|
11842
|
+
label: t("Horizontal oscillation off", "Oscillazione orizzontale spenta"),
|
|
11843
|
+
phrase: t("Stop the horizontal oscillation of {device}", "Ferma l’oscillazione orizzontale di {device}"),
|
|
11844
|
+
icon: "move-horizontal",
|
|
11845
|
+
args: {
|
|
11846
|
+
axis: "horizontal",
|
|
11847
|
+
oscillating: false
|
|
11848
|
+
}
|
|
11849
|
+
},
|
|
11850
|
+
{
|
|
11851
|
+
id: "fan.verticalOscillateOn",
|
|
11852
|
+
method: "setOscillationAxis",
|
|
11853
|
+
label: t("Vertical oscillation on", "Oscillazione verticale attiva"),
|
|
11854
|
+
phrase: t("Start the vertical oscillation of {device}", "Attiva l’oscillazione verticale di {device}"),
|
|
11855
|
+
icon: "move-vertical",
|
|
11856
|
+
args: {
|
|
11857
|
+
axis: "vertical",
|
|
11858
|
+
oscillating: true
|
|
11859
|
+
}
|
|
11860
|
+
},
|
|
11861
|
+
{
|
|
11862
|
+
id: "fan.verticalOscillateOff",
|
|
11863
|
+
method: "setOscillationAxis",
|
|
11864
|
+
label: t("Vertical oscillation off", "Oscillazione verticale spenta"),
|
|
11865
|
+
phrase: t("Stop the vertical oscillation of {device}", "Ferma l’oscillazione verticale di {device}"),
|
|
11866
|
+
icon: "move-vertical",
|
|
11867
|
+
args: {
|
|
11868
|
+
axis: "vertical",
|
|
11869
|
+
oscillating: false
|
|
11870
|
+
}
|
|
11809
11871
|
}
|
|
11810
11872
|
],
|
|
11811
11873
|
state: [{
|
|
@@ -13895,10 +13957,6 @@ var CAMERA_CAP_VOCABULARY = {
|
|
|
13895
13957
|
scope: "device",
|
|
13896
13958
|
noun: t("Admin link", "Link di amministrazione")
|
|
13897
13959
|
},
|
|
13898
|
-
"device-discovery": {
|
|
13899
|
-
scope: "device",
|
|
13900
|
-
noun: t("Discovery", "Rilevamento dispositivi")
|
|
13901
|
-
},
|
|
13902
13960
|
"device-extension": {
|
|
13903
13961
|
scope: "device",
|
|
13904
13962
|
noun: t("Device extension", "Estensione del dispositivo")
|
|
@@ -15356,7 +15414,6 @@ var CAP_NAMES = Object.freeze([
|
|
|
15356
15414
|
"device-admin-link",
|
|
15357
15415
|
"device-adoption",
|
|
15358
15416
|
"device-credentials",
|
|
15359
|
-
"device-discovery",
|
|
15360
15417
|
"device-export",
|
|
15361
15418
|
"device-extension",
|
|
15362
15419
|
"device-history",
|
|
@@ -15978,13 +16035,6 @@ Object.freeze({
|
|
|
15978
16035
|
"valueType": "string"
|
|
15979
16036
|
}
|
|
15980
16037
|
],
|
|
15981
|
-
"device-discovery": [{
|
|
15982
|
-
"path": "lastDiscoveryAt",
|
|
15983
|
-
"valueType": "number"
|
|
15984
|
-
}, {
|
|
15985
|
-
"path": "lastError",
|
|
15986
|
-
"valueType": "string"
|
|
15987
|
-
}],
|
|
15988
16038
|
"device-export": [
|
|
15989
16039
|
{
|
|
15990
16040
|
"path": "linkState",
|
|
@@ -16173,6 +16223,10 @@ Object.freeze({
|
|
|
16173
16223
|
"path": "lastEvent.eventType",
|
|
16174
16224
|
"valueType": "string"
|
|
16175
16225
|
},
|
|
16226
|
+
{
|
|
16227
|
+
"path": "lastEvent.sourceId",
|
|
16228
|
+
"valueType": "string"
|
|
16229
|
+
},
|
|
16176
16230
|
{
|
|
16177
16231
|
"path": "lastEvent.timestamp",
|
|
16178
16232
|
"valueType": "number"
|
|
@@ -17436,7 +17490,7 @@ Object.freeze({
|
|
|
17436
17490
|
}
|
|
17437
17491
|
});
|
|
17438
17492
|
//#endregion
|
|
17439
|
-
//#region ../types/dist/implemented-methods-
|
|
17493
|
+
//#region ../types/dist/implemented-methods-5YMDeE3f.mjs
|
|
17440
17494
|
/**
|
|
17441
17495
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
17442
17496
|
*
|
|
@@ -44097,6 +44151,41 @@ function macroOnly(macro) {
|
|
|
44097
44151
|
};
|
|
44098
44152
|
}
|
|
44099
44153
|
}
|
|
44154
|
+
var EventVocabularyKindSchema = _enum([
|
|
44155
|
+
"press",
|
|
44156
|
+
"long-press",
|
|
44157
|
+
"long-press-start",
|
|
44158
|
+
"release",
|
|
44159
|
+
"repeat",
|
|
44160
|
+
"double-press",
|
|
44161
|
+
"other"
|
|
44162
|
+
]);
|
|
44163
|
+
/** The part of the device an event comes from: a button, a channel. */
|
|
44164
|
+
var EventVocabularySourceSchema = object({
|
|
44165
|
+
id: string().min(1),
|
|
44166
|
+
label: string().min(1)
|
|
44167
|
+
});
|
|
44168
|
+
/** One scalar field of an event's `data`, by dotted path. */
|
|
44169
|
+
var EventVocabularyFieldSchema = object({
|
|
44170
|
+
path: string().min(1),
|
|
44171
|
+
kind: _enum([
|
|
44172
|
+
"string",
|
|
44173
|
+
"number",
|
|
44174
|
+
"boolean",
|
|
44175
|
+
"enum"
|
|
44176
|
+
]),
|
|
44177
|
+
enumValues: array(string()).optional(),
|
|
44178
|
+
label: string().min(1).optional()
|
|
44179
|
+
});
|
|
44180
|
+
var EventVocabularyEntrySchema = object({
|
|
44181
|
+
/** The native token, verbatim. */
|
|
44182
|
+
id: string().min(1),
|
|
44183
|
+
source: EventVocabularySourceSchema.optional(),
|
|
44184
|
+
label: string().min(1),
|
|
44185
|
+
description: string().optional(),
|
|
44186
|
+
kind: EventVocabularyKindSchema.optional(),
|
|
44187
|
+
payload: array(EventVocabularyFieldSchema).optional()
|
|
44188
|
+
});
|
|
44100
44189
|
Math.PI / 180;
|
|
44101
44190
|
/**
|
|
44102
44191
|
* WHEN something happens, as data (D983) — the one time vocabulary of the
|
|
@@ -44149,6 +44238,62 @@ var WeekdayKeySchema = _enum([
|
|
|
44149
44238
|
"sat",
|
|
44150
44239
|
"sun"
|
|
44151
44240
|
]);
|
|
44241
|
+
/**
|
|
44242
|
+
* Hold semantics — "for N seconds" on a leaf of the `when` tree (#498).
|
|
44243
|
+
*
|
|
44244
|
+
* ONE primitive, `for: { ms, mode, tolerance? }`, shared by `device-becomes`,
|
|
44245
|
+
* `device-is` and `expression`:
|
|
44246
|
+
*
|
|
44247
|
+
* - `held`: the condition stays true for `ms`;
|
|
44248
|
+
* - `stable`: the value does not move by more than `tolerance` (absolute, in
|
|
44249
|
+
* the field's base unit) for `ms` while the condition holds.
|
|
44250
|
+
*
|
|
44251
|
+
* {@link HoldCore} is the pure state machine (an input observation → a reading).
|
|
44252
|
+
* {@link HoldTracker} keys cores by a leaf path for the STATE leaves the tree
|
|
44253
|
+
* evaluator judges. {@link BecomesWatch} wraps a core with a timer for the
|
|
44254
|
+
* `device-becomes` EVENT leaf: the deadline is armed when the hold starts and
|
|
44255
|
+
* cancelled when it breaks, and the leaf fires ONCE per episode.
|
|
44256
|
+
*
|
|
44257
|
+
* Hold state is memory only. A hold that is already running when the watcher
|
|
44258
|
+
* first looks (a restart) is INHERITED: nobody knows when it began, so it never
|
|
44259
|
+
* fires for that episode and a state leaf reads it as unknown until the window
|
|
44260
|
+
* has elapsed from the moment the watcher started (D49: not-known is not false).
|
|
44261
|
+
*/
|
|
44262
|
+
var HOLD_MAX_MS = 864e5;
|
|
44263
|
+
var HoldModeSchema = _enum(["held", "stable"]);
|
|
44264
|
+
var HoldSpecSchema = object({
|
|
44265
|
+
/** How long the condition (or the value) must hold. */
|
|
44266
|
+
ms: number$1().int().min(1).max(HOLD_MAX_MS),
|
|
44267
|
+
mode: HoldModeSchema,
|
|
44268
|
+
/** `stable` only: the largest move of the value that still counts as unchanged (absolute, base unit). */
|
|
44269
|
+
tolerance: number$1().min(0).optional()
|
|
44270
|
+
});
|
|
44271
|
+
/** What a trace says about a hold. */
|
|
44272
|
+
var HoldTraceSchema = object({
|
|
44273
|
+
phase: _enum([
|
|
44274
|
+
"pending",
|
|
44275
|
+
"cancelled",
|
|
44276
|
+
"fired",
|
|
44277
|
+
"reseeded"
|
|
44278
|
+
]),
|
|
44279
|
+
ms: number$1().int(),
|
|
44280
|
+
mode: HoldModeSchema,
|
|
44281
|
+
/** When the hold started (ms epoch). */
|
|
44282
|
+
since: number$1().optional(),
|
|
44283
|
+
/** When it completes (ms epoch), for a pending hold. */
|
|
44284
|
+
deadline: number$1().optional()
|
|
44285
|
+
});
|
|
44286
|
+
/** The operators a `device-becomes` leaf may carry: a threshold or a range. */
|
|
44287
|
+
var BecomesOperatorSchema = _enum([
|
|
44288
|
+
"eq",
|
|
44289
|
+
"ne",
|
|
44290
|
+
"gt",
|
|
44291
|
+
"gte",
|
|
44292
|
+
"lt",
|
|
44293
|
+
"lte",
|
|
44294
|
+
"between",
|
|
44295
|
+
"outside"
|
|
44296
|
+
]);
|
|
44152
44297
|
/** How a state leaf compares a device field to a value. Derived from the
|
|
44153
44298
|
* field's `kind` in `deviceManager.getWireableFields`, never hand-maintained. */
|
|
44154
44299
|
var AutomationConditionOperatorSchema = _enum([
|
|
@@ -44162,6 +44307,13 @@ var AutomationConditionOperatorSchema = _enum([
|
|
|
44162
44307
|
"in"
|
|
44163
44308
|
]);
|
|
44164
44309
|
/**
|
|
44310
|
+
* The stable name of an event leaf. A path (`when.0.1`) moves when a row is
|
|
44311
|
+
* reordered; the id does not, so a `triggered-by` leaf keeps pointing at the
|
|
44312
|
+
* same row. Optional: a leaf nothing refers to needs none.
|
|
44313
|
+
*/
|
|
44314
|
+
var EVENT_LEAF_ID_RE = /^[A-Za-z0-9_-]{1,40}$/;
|
|
44315
|
+
var eventLeafId = { id: string().regex(EVENT_LEAF_ID_RE).optional() };
|
|
44316
|
+
/**
|
|
44165
44317
|
* D8 compliance, and it is the reason a "becomes" leaf is not merely an event
|
|
44166
44318
|
* subscription: it evaluates against the **state mirror**, which is reconciled,
|
|
44167
44319
|
* and an event only WAKES the evaluation. A dropped event therefore DELAYS a
|
|
@@ -44169,24 +44321,60 @@ var AutomationConditionOperatorSchema = _enum([
|
|
|
44169
44321
|
*/
|
|
44170
44322
|
var DeviceBecomesLeafSchema = object({
|
|
44171
44323
|
kind: literal("device-becomes"),
|
|
44324
|
+
...eventLeafId,
|
|
44172
44325
|
deviceId: number$1().int().nonnegative(),
|
|
44173
44326
|
cap: string().min(1),
|
|
44174
44327
|
fieldPath: string().min(1),
|
|
44175
|
-
/** Fire when the field takes this value. Omit to fire on any change. */
|
|
44328
|
+
/** Fire when the field takes this value. Omit (with no `operator`) to fire on any change. */
|
|
44176
44329
|
becomes: union([
|
|
44177
44330
|
string(),
|
|
44178
44331
|
number$1(),
|
|
44179
44332
|
boolean()
|
|
44180
44333
|
]).optional(),
|
|
44181
|
-
/**
|
|
44334
|
+
/**
|
|
44335
|
+
* A threshold or a range instead of an equality: "crosses above 3000", "enters
|
|
44336
|
+
* 18..24". The leaf fires on the TRANSITION into the predicate, never on a re-report.
|
|
44337
|
+
* `value` is in the field's base unit; `between` / `outside` take `[low, high]`.
|
|
44338
|
+
*/
|
|
44339
|
+
operator: BecomesOperatorSchema.optional(),
|
|
44340
|
+
value: union([
|
|
44341
|
+
number$1(),
|
|
44342
|
+
string(),
|
|
44343
|
+
boolean(),
|
|
44344
|
+
array(number$1()).length(2)
|
|
44345
|
+
]).optional(),
|
|
44346
|
+
/**
|
|
44347
|
+
* Hold: the transition (or, with no predicate, the new value) must stay for
|
|
44348
|
+
* `for.ms` before the leaf fires, ONCE per episode. See `hold.ts`.
|
|
44349
|
+
*/
|
|
44350
|
+
for: HoldSpecSchema.optional(),
|
|
44351
|
+
/** @deprecated every leaf fires on a transition now; read as always on. */
|
|
44182
44352
|
edge: boolean().optional(),
|
|
44183
|
-
/**
|
|
44184
|
-
forMs: number$1().int().min(0).max(
|
|
44353
|
+
/** @deprecated legacy `held` window — read through `holdOf` as `for: { ms, mode: 'held' }`. */
|
|
44354
|
+
forMs: number$1().int().min(0).max(HOLD_MAX_MS).optional(),
|
|
44185
44355
|
/** Collapse a burst into one occurrence. */
|
|
44186
44356
|
debounceMs: number$1().int().min(0).max(6e5).optional()
|
|
44357
|
+
}).superRefine((leaf, ctx) => {
|
|
44358
|
+
if (leaf.becomes !== void 0 && leaf.operator !== void 0) ctx.addIssue({
|
|
44359
|
+
code: "custom",
|
|
44360
|
+
message: "a leaf names `becomes` or `operator`, not both",
|
|
44361
|
+
path: ["operator"]
|
|
44362
|
+
});
|
|
44363
|
+
if (leaf.operator !== void 0 && leaf.value === void 0) ctx.addIssue({
|
|
44364
|
+
code: "custom",
|
|
44365
|
+
message: "an operator needs the value to compare to",
|
|
44366
|
+
path: ["value"]
|
|
44367
|
+
});
|
|
44368
|
+
const range = leaf.operator === "between" || leaf.operator === "outside";
|
|
44369
|
+
if (leaf.operator !== void 0 && range !== Array.isArray(leaf.value)) ctx.addIssue({
|
|
44370
|
+
code: "custom",
|
|
44371
|
+
message: range ? "a range takes [low, high]" : "only a range takes [low, high]",
|
|
44372
|
+
path: ["value"]
|
|
44373
|
+
});
|
|
44187
44374
|
});
|
|
44188
44375
|
var DeviceEventLeafSchema = object({
|
|
44189
44376
|
kind: literal("device-event"),
|
|
44377
|
+
...eventLeafId,
|
|
44190
44378
|
/** An `EventCategory` value. */
|
|
44191
44379
|
category: string().min(1),
|
|
44192
44380
|
deviceId: number$1().int().nonnegative().optional()
|
|
@@ -44199,18 +44387,51 @@ var DeviceEventLeafSchema = object({
|
|
|
44199
44387
|
*/
|
|
44200
44388
|
var ScheduleLeafSchema = object({
|
|
44201
44389
|
kind: literal("schedule"),
|
|
44390
|
+
...eventLeafId,
|
|
44202
44391
|
at: TimeSpecSchema.optional(),
|
|
44203
44392
|
days: array(WeekdayKeySchema).min(1).max(7).optional(),
|
|
44204
44393
|
/** @deprecated legacy cron — new schedules use `at`. */
|
|
44205
44394
|
cron: string().min(1).max(120).optional()
|
|
44206
44395
|
}).refine((leaf) => leaf.at === void 0 !== (leaf.cron === void 0), { message: "a schedule names exactly one of `at` (a time spec) and `cron` (legacy)" }).refine((leaf) => leaf.days === void 0 || leaf.at !== void 0, { message: "`days` goes with `at`; a cron expression carries its own days" });
|
|
44207
|
-
|
|
44396
|
+
/**
|
|
44397
|
+
* One payload predicate of a `device-fired` row: the value at `path` inside
|
|
44398
|
+
* the fire's `data` (dotted) equals `equals`. Compared strictly, no coercion.
|
|
44399
|
+
*/
|
|
44400
|
+
var DeviceFiredMatchSchema = object({
|
|
44401
|
+
path: string().min(1).max(120),
|
|
44402
|
+
equals: union([
|
|
44403
|
+
string(),
|
|
44404
|
+
number$1(),
|
|
44405
|
+
boolean()
|
|
44406
|
+
])
|
|
44407
|
+
});
|
|
44408
|
+
/**
|
|
44409
|
+
* "fires": one device of the `event-emitter` capability reported an event
|
|
44410
|
+
* (D1045). Every list is an OR, the lists together an AND; an absent list does
|
|
44411
|
+
* not narrow. `eventIds` are the device's own tokens, `sourceIds` the parts of
|
|
44412
|
+
* the device (a button), `kinds` the shared press kinds of its vocabulary.
|
|
44413
|
+
*/
|
|
44414
|
+
var DeviceFiredLeafSchema = object({
|
|
44415
|
+
kind: literal("device-fired"),
|
|
44416
|
+
...eventLeafId,
|
|
44417
|
+
deviceId: number$1().int().nonnegative(),
|
|
44418
|
+
eventIds: array(string().min(1)).min(1).max(64).optional(),
|
|
44419
|
+
sourceIds: array(string().min(1)).min(1).max(64).optional(),
|
|
44420
|
+
kinds: array(EventVocabularyKindSchema).min(1).optional(),
|
|
44421
|
+
match: array(DeviceFiredMatchSchema).min(1).max(8).optional()
|
|
44422
|
+
});
|
|
44423
|
+
var ManualLeafSchema = object({
|
|
44424
|
+
kind: literal("manual"),
|
|
44425
|
+
...eventLeafId
|
|
44426
|
+
});
|
|
44208
44427
|
var IntervalLeafSchema = object({
|
|
44209
44428
|
kind: literal("interval"),
|
|
44429
|
+
...eventLeafId,
|
|
44210
44430
|
minutes: number$1().int().min(1).max(1440)
|
|
44211
44431
|
});
|
|
44212
44432
|
var DetectionLeafSchema = object({
|
|
44213
44433
|
kind: literal("detection"),
|
|
44434
|
+
...eventLeafId,
|
|
44214
44435
|
/** One macro class each, optionally narrowed by a label; a track of any listed subject fires. */
|
|
44215
44436
|
subjects: array(DetectionSubjectSchema).min(1),
|
|
44216
44437
|
deviceIds: array(number$1().int().nonnegative()).min(1),
|
|
@@ -44221,6 +44442,7 @@ var DetectionLeafSchema = object({
|
|
|
44221
44442
|
});
|
|
44222
44443
|
var StationaryLeafSchema = object({
|
|
44223
44444
|
kind: literal("stationary"),
|
|
44445
|
+
...eventLeafId,
|
|
44224
44446
|
deviceIds: array(number$1().int().nonnegative()).min(1),
|
|
44225
44447
|
subjects: array(StationarySubjectSchema).min(1).optional(),
|
|
44226
44448
|
zones: array(string().min(1)).min(1).optional(),
|
|
@@ -44248,7 +44470,9 @@ var DeviceIsLeafSchema = object({
|
|
|
44248
44470
|
QuantityValueSchema
|
|
44249
44471
|
]),
|
|
44250
44472
|
/** The unit the person picked for `value` (display only; `value` is in the base unit). */
|
|
44251
|
-
unit: string().min(1).optional()
|
|
44473
|
+
unit: string().min(1).optional(),
|
|
44474
|
+
/** The comparison must hold (`held`) or the value stay put (`stable`) this long. */
|
|
44475
|
+
for: HoldSpecSchema.optional()
|
|
44252
44476
|
});
|
|
44253
44477
|
/**
|
|
44254
44478
|
* True when the camera currently has a PARKED object matching `subjects` in at
|
|
@@ -44264,6 +44488,41 @@ var StationaryPresentLeafSchema = object({
|
|
|
44264
44488
|
minCount: number$1().int().positive().optional()
|
|
44265
44489
|
});
|
|
44266
44490
|
/**
|
|
44491
|
+
* True while the site-local clock is inside a daily window and/or on one of
|
|
44492
|
+
* the named days. `days` names the local day a window STARTS on, so
|
|
44493
|
+
* 22:00-06:00 on weekdays is still inside at 01:00 Saturday. A one-date spec
|
|
44494
|
+
* (`kind: 'date'`) is refused: only a clock time or a sun event makes a window
|
|
44495
|
+
* that repeats every day.
|
|
44496
|
+
*/
|
|
44497
|
+
var TimeIsLeafSchema = object({
|
|
44498
|
+
kind: literal("time-is"),
|
|
44499
|
+
between: object({
|
|
44500
|
+
from: TimeSpecSchema,
|
|
44501
|
+
to: TimeSpecSchema
|
|
44502
|
+
}).optional(),
|
|
44503
|
+
days: array(WeekdayKeySchema).min(1).max(7).optional()
|
|
44504
|
+
}).superRefine((leaf, ctx) => {
|
|
44505
|
+
if (leaf.between === void 0 && leaf.days === void 0) ctx.addIssue({
|
|
44506
|
+
code: "custom",
|
|
44507
|
+
message: "a time-is leaf needs a between window, days, or both"
|
|
44508
|
+
});
|
|
44509
|
+
for (const end of ["from", "to"]) if (leaf.between?.[end].kind === "date") ctx.addIssue({
|
|
44510
|
+
code: "custom",
|
|
44511
|
+
message: "a one-date time cannot bound a daily window: use a clock time or a sun event",
|
|
44512
|
+
path: ["between", end]
|
|
44513
|
+
});
|
|
44514
|
+
});
|
|
44515
|
+
/**
|
|
44516
|
+
* True for the run that one of the named event leaves started. It reads the
|
|
44517
|
+
* run, not a device: in the tree it is judged against the leaf that fired, in
|
|
44518
|
+
* a step guard against the trigger the run carries. A run no event leaf
|
|
44519
|
+
* started (a state-only tree becoming true) is false.
|
|
44520
|
+
*/
|
|
44521
|
+
var TriggeredByLeafSchema = object({
|
|
44522
|
+
kind: literal("triggered-by"),
|
|
44523
|
+
leafIds: array(string().regex(EVENT_LEAF_ID_RE)).min(1).max(20)
|
|
44524
|
+
});
|
|
44525
|
+
/**
|
|
44267
44526
|
* The escape hatch: the bounded expression engine over named device bindings.
|
|
44268
44527
|
* Declared as a plain object so a discriminated union can read `kind`; the
|
|
44269
44528
|
* author-time validation is the SAME function `ExpressionSourceSchema` runs.
|
|
@@ -44271,7 +44530,9 @@ var StationaryPresentLeafSchema = object({
|
|
|
44271
44530
|
var ExpressionLeafSchema = object({
|
|
44272
44531
|
kind: literal("expression"),
|
|
44273
44532
|
expr: string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
|
|
44274
|
-
bindings: record(string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
|
|
44533
|
+
bindings: record(string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema),
|
|
44534
|
+
/** The expression must stay true this long. An expression has no single value, so only `held`. */
|
|
44535
|
+
for: HoldSpecSchema.optional()
|
|
44275
44536
|
}).superRefine((src, ctx) => {
|
|
44276
44537
|
const err = validateExpressionSource(src);
|
|
44277
44538
|
if (err !== null) ctx.addIssue({
|
|
@@ -44279,6 +44540,11 @@ var ExpressionLeafSchema = object({
|
|
|
44279
44540
|
message: err,
|
|
44280
44541
|
path: ["expr"]
|
|
44281
44542
|
});
|
|
44543
|
+
if (src.for?.mode === "stable") ctx.addIssue({
|
|
44544
|
+
code: "custom",
|
|
44545
|
+
message: "an expression is held, not stable: it has no single value to keep still",
|
|
44546
|
+
path: ["for", "mode"]
|
|
44547
|
+
});
|
|
44282
44548
|
});
|
|
44283
44549
|
var AutomationWhenSchema = lazy(() => discriminatedUnion("kind", [
|
|
44284
44550
|
object({
|
|
@@ -44295,6 +44561,7 @@ var AutomationWhenSchema = lazy(() => discriminatedUnion("kind", [
|
|
|
44295
44561
|
}),
|
|
44296
44562
|
DeviceBecomesLeafSchema,
|
|
44297
44563
|
DeviceEventLeafSchema,
|
|
44564
|
+
DeviceFiredLeafSchema,
|
|
44298
44565
|
ScheduleLeafSchema,
|
|
44299
44566
|
ManualLeafSchema,
|
|
44300
44567
|
IntervalLeafSchema,
|
|
@@ -44302,11 +44569,14 @@ var AutomationWhenSchema = lazy(() => discriminatedUnion("kind", [
|
|
|
44302
44569
|
StationaryLeafSchema,
|
|
44303
44570
|
DeviceIsLeafSchema,
|
|
44304
44571
|
StationaryPresentLeafSchema,
|
|
44305
|
-
ExpressionLeafSchema
|
|
44572
|
+
ExpressionLeafSchema,
|
|
44573
|
+
TimeIsLeafSchema,
|
|
44574
|
+
TriggeredByLeafSchema
|
|
44306
44575
|
]));
|
|
44307
44576
|
var EVENT_KINDS = new Set([
|
|
44308
44577
|
"device-becomes",
|
|
44309
44578
|
"device-event",
|
|
44579
|
+
"device-fired",
|
|
44310
44580
|
"schedule",
|
|
44311
44581
|
"manual",
|
|
44312
44582
|
"interval",
|
|
@@ -44356,6 +44626,28 @@ function hasEventLeaves(node) {
|
|
|
44356
44626
|
});
|
|
44357
44627
|
return found;
|
|
44358
44628
|
}
|
|
44629
|
+
/** The event leaves of the tree that carry an id, in tree order. */
|
|
44630
|
+
function identifiedEventLeaves(when) {
|
|
44631
|
+
const found = [];
|
|
44632
|
+
forEachLeaf(when, (leaf, path) => {
|
|
44633
|
+
if (isEventLeaf(leaf) && leaf.id !== void 0) found.push({
|
|
44634
|
+
id: leaf.id,
|
|
44635
|
+
path,
|
|
44636
|
+
leaf
|
|
44637
|
+
});
|
|
44638
|
+
});
|
|
44639
|
+
return found;
|
|
44640
|
+
}
|
|
44641
|
+
/** Does the tree hold a `triggered-by` leaf? */
|
|
44642
|
+
function hasTriggeredByLeaves(when) {
|
|
44643
|
+
let found = false;
|
|
44644
|
+
forEachLeaf(when, (leaf) => {
|
|
44645
|
+
if (leaf.kind === "triggered-by") found = true;
|
|
44646
|
+
});
|
|
44647
|
+
return found;
|
|
44648
|
+
}
|
|
44649
|
+
/** Why a `triggered-by` leaf is refused outside an automation: there is no trigger row to name. */
|
|
44650
|
+
var TRIGGERED_BY_NEEDS_AUTOMATION = "\"triggered by\" reads which trigger started an automation — an action or a scheduler has no trigger rows, so it could never be true here";
|
|
44359
44651
|
function hasEmptyContainer(node) {
|
|
44360
44652
|
if (isLeaf(node)) return false;
|
|
44361
44653
|
if (node.kind === "not") return hasEmptyContainer(node.child);
|
|
@@ -44365,6 +44657,7 @@ function hasEmptyContainer(node) {
|
|
|
44365
44657
|
var LEAF_WORDS = {
|
|
44366
44658
|
"device-becomes": "a device value that becomes",
|
|
44367
44659
|
"device-event": "a device event",
|
|
44660
|
+
"device-fired": "a device firing an event",
|
|
44368
44661
|
schedule: "a schedule",
|
|
44369
44662
|
manual: "a manual run",
|
|
44370
44663
|
interval: "an interval",
|
|
@@ -44414,6 +44707,11 @@ function validateWhenTree(when) {
|
|
|
44414
44707
|
if (depth > 4) return `the rules nest ${depth} levels deep — the limit is 4. "not (any of …)" is the same as "all of (not …)", which is one level shallower`;
|
|
44415
44708
|
const leaves = countWhenLeaves(when);
|
|
44416
44709
|
if (leaves > 32) return `the rules use ${leaves} rows — the limit is 32`;
|
|
44710
|
+
const seen = /* @__PURE__ */ new Set();
|
|
44711
|
+
for (const entry of identifiedEventLeaves(when)) {
|
|
44712
|
+
if (seen.has(entry.id)) return `two trigger rows share the id "${entry.id}" — an id names one row`;
|
|
44713
|
+
seen.add(entry.id);
|
|
44714
|
+
}
|
|
44417
44715
|
const need = eventNeed(when, "when", false);
|
|
44418
44716
|
if ("error" in need) return need.error;
|
|
44419
44717
|
const [first, second] = need.paths;
|
|
@@ -44625,9 +44923,12 @@ function convertLegacyWhen(half) {
|
|
|
44625
44923
|
* every caller — automations, Actions, and the notification centre, which runs
|
|
44626
44924
|
* only Actions (planning#473).
|
|
44627
44925
|
*/
|
|
44926
|
+
/** The longest one `wait` step lasts. It is cut by a restart of the run and does not survive a hub restart. */
|
|
44927
|
+
var WAIT_MAX_SECONDS = 3600;
|
|
44928
|
+
var WAIT_TOO_LONG = `a wait is at most one hour (${WAIT_MAX_SECONDS} s)`;
|
|
44628
44929
|
var AutomationWaitStepSchema = object({
|
|
44629
44930
|
kind: literal("wait"),
|
|
44630
|
-
seconds: number$1().min(0).max(
|
|
44931
|
+
seconds: number$1().min(0).max(WAIT_MAX_SECONDS, WAIT_TOO_LONG)
|
|
44631
44932
|
});
|
|
44632
44933
|
/** The legacy `deviceId` of a cap step: still READ, never written (D983). */
|
|
44633
44934
|
var LegacyStepDeviceSchema = union([
|
|
@@ -44697,28 +44998,227 @@ var elseNeedsIf = {
|
|
|
44697
44998
|
path: ["else"]
|
|
44698
44999
|
};
|
|
44699
45000
|
var GuardedWaitStepSchema = AutomationWaitStepSchema.extend(stepGuardShape).refine((step) => step.else === void 0 || step.if !== void 0, elseNeedsIf);
|
|
44700
|
-
|
|
45001
|
+
/**
|
|
45002
|
+
* A step of a STANDALONE list (an action, a scheduler point, a preset): the
|
|
45003
|
+
* same two step kinds an automation runs, minus a guard that reads the
|
|
45004
|
+
* automation's trigger, which such a list does not have.
|
|
45005
|
+
*/
|
|
45006
|
+
var ActionStepSchema = discriminatedUnion("kind", [GuardedWaitStepSchema, AutomationCapStepBaseSchema.extend(stepGuardShape).refine((step) => step.target !== void 0 || step.deviceId !== void 0, {
|
|
45007
|
+
message: "a cap step needs a target",
|
|
45008
|
+
path: ["target"]
|
|
45009
|
+
}).refine((step) => step.target?.kind !== "domain" || step.actionId !== void 0, {
|
|
45010
|
+
message: "a domain target needs the vocabulary action it applies to (actionId)",
|
|
45011
|
+
path: ["actionId"]
|
|
45012
|
+
}).refine((step) => step.else === void 0 || step.if !== void 0, elseNeedsIf)]).superRefine((step, ctx) => {
|
|
45013
|
+
if (step.if !== void 0 && hasTriggeredByLeaves(step.if)) ctx.addIssue({
|
|
45014
|
+
code: "custom",
|
|
45015
|
+
message: TRIGGERED_BY_NEEDS_AUTOMATION,
|
|
45016
|
+
path: ["if"]
|
|
45017
|
+
});
|
|
45018
|
+
});
|
|
45019
|
+
/**
|
|
45020
|
+
* Flow control of an AUTOMATION's list (#500). A standalone list (an action, a
|
|
45021
|
+
* scheduler point, a preset) stays `wait` and `cap`: it has no run of its own
|
|
45022
|
+
* to end and no variable bag to write.
|
|
45023
|
+
*/
|
|
45024
|
+
var continueOnErrorShape = {
|
|
45025
|
+
/** A failure of this step is traced `failed-continued` and the next step runs. */
|
|
45026
|
+
continueOnError: literal(true).optional() };
|
|
45027
|
+
/** An automation's cap step: the shared guarded cap step, plus `continueOnError`. */
|
|
45028
|
+
var AutomationGuardedCapStepSchema = AutomationCapStepBaseSchema.extend({
|
|
45029
|
+
...stepGuardShape,
|
|
45030
|
+
...continueOnErrorShape
|
|
45031
|
+
}).refine((step) => step.target !== void 0 || step.deviceId !== void 0, {
|
|
44701
45032
|
message: "a cap step needs a target",
|
|
44702
45033
|
path: ["target"]
|
|
44703
45034
|
}).refine((step) => step.target?.kind !== "domain" || step.actionId !== void 0, {
|
|
44704
45035
|
message: "a domain target needs the vocabulary action it applies to (actionId)",
|
|
44705
45036
|
path: ["actionId"]
|
|
44706
45037
|
}).refine((step) => step.else === void 0 || step.if !== void 0, elseNeedsIf);
|
|
44707
|
-
|
|
45038
|
+
/** End the run here, on purpose. `reason` may carry `{{vars.x}}` slots. */
|
|
45039
|
+
var AutomationStopStepSchema = object({
|
|
45040
|
+
kind: literal("stop"),
|
|
45041
|
+
reason: string().min(1, "a stop needs a reason: it is what the run trace shows").max(200),
|
|
45042
|
+
/** `ok`: the run ends as a success. `failed`: it ends as a failure, with the reason. */
|
|
45043
|
+
outcome: _enum(["ok", "failed"]),
|
|
45044
|
+
/** Stop only when this reads true; false or unknown does not stop. */
|
|
45045
|
+
if: AutomationWhenSchema.optional()
|
|
45046
|
+
});
|
|
45047
|
+
var SET_VAR_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]{0,39}$/;
|
|
45048
|
+
/** Names the run itself writes and reads (`leaf` is what `triggered-by` judges): a step may not overwrite them. */
|
|
45049
|
+
var RESERVED_RUN_VARS = [
|
|
45050
|
+
"kind",
|
|
45051
|
+
"leaf",
|
|
45052
|
+
"at"
|
|
45053
|
+
];
|
|
45054
|
+
/**
|
|
45055
|
+
* Write variables into the run's bag, for the steps after it. SUBSTITUTION
|
|
45056
|
+
* only, never evaluation: a text value may carry `{{vars.x}}` slots.
|
|
45057
|
+
*/
|
|
45058
|
+
var AutomationSetStepSchema = object({
|
|
45059
|
+
kind: literal("set"),
|
|
45060
|
+
vars: record(string(), union([
|
|
45061
|
+
string().max(500),
|
|
45062
|
+
number$1(),
|
|
45063
|
+
boolean()
|
|
45064
|
+
])).refine((vars) => Object.keys(vars).every((name) => SET_VAR_NAME_RE.test(name)), { message: "a variable name is letters, digits and _, not starting with a digit (max 40)" }).refine((vars) => Object.keys(vars).length >= 1, { message: "set needs at least one variable" }).refine((vars) => Object.keys(vars).length <= 20, { message: `set holds at most 20 variables` }).refine((vars) => Object.keys(vars).every((name) => !RESERVED_RUN_VARS.includes(name)), { message: `the names ${RESERVED_RUN_VARS.join(", ")} belong to the run and cannot be set` }),
|
|
45065
|
+
/** Set only when this reads true; false or unknown leaves the bag as it is. */
|
|
45066
|
+
if: AutomationWhenSchema.optional()
|
|
45067
|
+
});
|
|
45068
|
+
var AutomationRunActionStepSchema = object({
|
|
45069
|
+
kind: literal("action"),
|
|
45070
|
+
/** An entry of the `actions` surface (`internal:<uuid>` or `<addonId>:<localId>`). */
|
|
45071
|
+
actionId: string().min(1),
|
|
45072
|
+
/** Values for the action's declared variables; the others take their default. */
|
|
45073
|
+
variables: record(string(), union([
|
|
45074
|
+
string(),
|
|
45075
|
+
number$1(),
|
|
45076
|
+
boolean()
|
|
45077
|
+
])).optional(),
|
|
45078
|
+
...continueOnErrorShape
|
|
45079
|
+
});
|
|
45080
|
+
/**
|
|
45081
|
+
* A step INSIDE a branch of a `choose`: a flow step with no condition of its
|
|
45082
|
+
* own (the branch is the condition) and no block inside it. Strict, so an `if`
|
|
45083
|
+
* written on such a step is refused rather than dropped.
|
|
45084
|
+
*/
|
|
45085
|
+
/** The longest a `wait-until` may wait: six hours. */
|
|
45086
|
+
var WAIT_UNTIL_TIMEOUT_MAX_S = 21600;
|
|
45087
|
+
/**
|
|
45088
|
+
* Pause the run until a condition is true (#500). The timeout is mandatory: a
|
|
45089
|
+
* run never waits without an end. `onTimeout` says what a wait that ran out
|
|
45090
|
+
* does: `continue` goes on to the next step, `stop` ends the run as a failure.
|
|
45091
|
+
* A condition that cannot be read is not "true": the wait goes on (D49).
|
|
45092
|
+
*/
|
|
45093
|
+
var AutomationWaitUntilStepSchema = object({
|
|
45094
|
+
kind: literal("wait-until"),
|
|
45095
|
+
when: AutomationWhenSchema,
|
|
45096
|
+
timeoutS: number$1().int().min(1, "a wait needs a timeout of at least one second").max(WAIT_UNTIL_TIMEOUT_MAX_S, `a wait for a condition is at most six hours (${WAIT_UNTIL_TIMEOUT_MAX_S} s)`),
|
|
45097
|
+
onTimeout: _enum(["continue", "stop"])
|
|
45098
|
+
}).strict();
|
|
45099
|
+
/** The step kinds that hold steps of their own, or pause on a condition: they nest two levels at most. */
|
|
45100
|
+
var BLOCK_STEP_KINDS = [
|
|
45101
|
+
"choose",
|
|
45102
|
+
"repeat",
|
|
45103
|
+
"parallel",
|
|
45104
|
+
"wait-until"
|
|
45105
|
+
];
|
|
45106
|
+
var NEST_TOO_DEEP = `steps nest two levels at most: a step inside a nested choose or repeat can be a plain step only`;
|
|
45107
|
+
function kindOf(input) {
|
|
45108
|
+
return typeof input === "object" && input !== null ? Reflect.get(input, "kind") : void 0;
|
|
45109
|
+
}
|
|
45110
|
+
/** Where the nesting ends, a block step is refused in words rather than as an unknown kind. */
|
|
45111
|
+
function nestTooDeep(issue) {
|
|
45112
|
+
const kind = kindOf(issue.input);
|
|
45113
|
+
return typeof kind === "string" && BLOCK_STEP_KINDS.includes(kind) ? NEST_TOO_DEEP : void 0;
|
|
45114
|
+
}
|
|
45115
|
+
var plainStepSchemas = [
|
|
45116
|
+
AutomationWaitStepSchema.strict(),
|
|
45117
|
+
AutomationCapStepBaseSchema.extend(continueOnErrorShape).strict().refine((step) => step.target !== void 0 || step.deviceId !== void 0, {
|
|
45118
|
+
message: "a cap step needs a target",
|
|
45119
|
+
path: ["target"]
|
|
45120
|
+
}).refine((step) => step.target?.kind !== "domain" || step.actionId !== void 0, {
|
|
45121
|
+
message: "a domain target needs the vocabulary action it applies to (actionId)",
|
|
45122
|
+
path: ["actionId"]
|
|
45123
|
+
}),
|
|
45124
|
+
AutomationStopStepSchema.omit({ if: true }).strict(),
|
|
45125
|
+
AutomationSetStepSchema.omit({ if: true }).strict(),
|
|
45126
|
+
AutomationRunActionStepSchema.strict()
|
|
45127
|
+
];
|
|
45128
|
+
/**
|
|
45129
|
+
* A plain step: a flow step with no condition of its own and no step inside
|
|
45130
|
+
* it. The second (last) level of nesting holds these only.
|
|
45131
|
+
*/
|
|
45132
|
+
var AutomationBranchStepSchema = discriminatedUnion("kind", plainStepSchemas, { error: nestTooDeep });
|
|
45133
|
+
/** A step that can run beside others: it writes no variable and does not end the run. */
|
|
45134
|
+
var AutomationParallelMemberSchema = AutomationBranchStepSchema.refine((step) => step.kind === "cap" || step.kind === "action" || step.kind === "wait", "a parallel holds device commands, actions and waits only: a set or a stop beside other steps has no order");
|
|
45135
|
+
/**
|
|
45136
|
+
* Several steps at once (#500): all are started together and all are awaited.
|
|
45137
|
+
* A failure fails the run once the others finished, as in a sequence; a member
|
|
45138
|
+
* marked "continue if this fails" does not.
|
|
45139
|
+
*/
|
|
45140
|
+
var AutomationParallelStepSchema = object({
|
|
45141
|
+
kind: literal("parallel"),
|
|
45142
|
+
steps: array(AutomationParallelMemberSchema).min(2, "a parallel needs at least two steps").max(5, `a parallel takes at most 5 steps`)
|
|
45143
|
+
}).strict();
|
|
45144
|
+
var REPEAT_NEEDS_MAX = "a repeat on a condition needs a maximum number of iterations";
|
|
45145
|
+
/** A loop on a condition: the bound is mandatory, so a run never loops without an end. */
|
|
45146
|
+
var repeatConditionShape = {
|
|
45147
|
+
when: AutomationWhenSchema,
|
|
45148
|
+
maxIterations: number$1({ error: REPEAT_NEEDS_MAX }).int().min(1, REPEAT_NEEDS_MAX).max(50, `a repeat on a condition runs at most 50 times`),
|
|
45149
|
+
/** A pause between two iterations, never after the last. */
|
|
45150
|
+
waitS: number$1().int().min(1).max(WAIT_MAX_SECONDS, WAIT_TOO_LONG).optional()
|
|
45151
|
+
};
|
|
45152
|
+
/** What decides how many times a `repeat` runs. */
|
|
45153
|
+
var AutomationRepeatLoopSchema = discriminatedUnion("by", [
|
|
45154
|
+
object({
|
|
45155
|
+
by: literal("count"),
|
|
45156
|
+
count: number$1().int().min(1).max(20, `a repeat runs at most 20 times`)
|
|
45157
|
+
}).strict(),
|
|
45158
|
+
object({
|
|
45159
|
+
by: literal("while"),
|
|
45160
|
+
...repeatConditionShape
|
|
45161
|
+
}).strict(),
|
|
45162
|
+
object({
|
|
45163
|
+
by: literal("until"),
|
|
45164
|
+
...repeatConditionShape
|
|
45165
|
+
}).strict(),
|
|
45166
|
+
object({
|
|
45167
|
+
by: literal("forEach"),
|
|
45168
|
+
target: object({
|
|
45169
|
+
kind: literal("devices"),
|
|
45170
|
+
ids: array(number$1().int()).min(1, "a repeat over devices needs at least one device").max(50, `a repeat over devices takes at most 50 devices`)
|
|
45171
|
+
}).strict()
|
|
45172
|
+
}).strict()
|
|
45173
|
+
]);
|
|
45174
|
+
/**
|
|
45175
|
+
* The same steps several times (#500). Every form is bounded at save, so a
|
|
45176
|
+
* run cannot loop without an end. A failure or a `stop` in the steps ends the
|
|
45177
|
+
* loop and the run, as in a sequence.
|
|
45178
|
+
*/
|
|
45179
|
+
function repeatSchemaOf(step) {
|
|
45180
|
+
return object({
|
|
45181
|
+
kind: literal("repeat"),
|
|
45182
|
+
loop: AutomationRepeatLoopSchema,
|
|
45183
|
+
steps: array(step).min(1, "a repeat needs at least one step").max(10, `a repeat takes at most 10 steps`)
|
|
45184
|
+
}).strict();
|
|
45185
|
+
}
|
|
45186
|
+
/**
|
|
45187
|
+
* One step, several ways (#500): the branches are read in order and the FIRST
|
|
45188
|
+
* whose condition is true runs; `else` runs when every branch is false. A
|
|
45189
|
+
* branch that cannot be read ends the choice: nothing runs, not the branches
|
|
45190
|
+
* after it and not the else (D49). The #472 `if` / `else` of a wait or cap
|
|
45191
|
+
* step is the one-branch case and stays as it is stored.
|
|
45192
|
+
*/
|
|
45193
|
+
function chooseSchemaOf(step) {
|
|
45194
|
+
const steps = array(step).max(10);
|
|
45195
|
+
return object({
|
|
45196
|
+
kind: literal("choose"),
|
|
45197
|
+
branches: array(object({
|
|
45198
|
+
if: AutomationWhenSchema,
|
|
45199
|
+
steps: steps.min(1, "a branch needs at least one step")
|
|
45200
|
+
})).min(1, "a choose needs at least one branch").max(6, `a choose takes at most 6 branches`),
|
|
45201
|
+
else: steps.optional()
|
|
45202
|
+
});
|
|
45203
|
+
}
|
|
45204
|
+
var AutomationBodyStepSchema = discriminatedUnion("kind", [
|
|
45205
|
+
...plainStepSchemas,
|
|
45206
|
+
AutomationWaitUntilStepSchema,
|
|
45207
|
+
AutomationParallelStepSchema,
|
|
45208
|
+
chooseSchemaOf(AutomationBranchStepSchema),
|
|
45209
|
+
repeatSchemaOf(AutomationBranchStepSchema)
|
|
45210
|
+
]);
|
|
45211
|
+
var AutomationRepeatStepSchema = repeatSchemaOf(AutomationBodyStepSchema);
|
|
44708
45212
|
var AutomationActionSchema = discriminatedUnion("kind", [
|
|
44709
45213
|
GuardedWaitStepSchema,
|
|
44710
|
-
|
|
44711
|
-
|
|
44712
|
-
|
|
44713
|
-
|
|
44714
|
-
|
|
44715
|
-
|
|
44716
|
-
|
|
44717
|
-
|
|
44718
|
-
number$1(),
|
|
44719
|
-
boolean()
|
|
44720
|
-
])).optional()
|
|
44721
|
-
}),
|
|
45214
|
+
AutomationGuardedCapStepSchema,
|
|
45215
|
+
AutomationStopStepSchema,
|
|
45216
|
+
AutomationSetStepSchema,
|
|
45217
|
+
chooseSchemaOf(AutomationBodyStepSchema),
|
|
45218
|
+
AutomationWaitUntilStepSchema,
|
|
45219
|
+
AutomationParallelStepSchema,
|
|
45220
|
+
AutomationRepeatStepSchema,
|
|
45221
|
+
AutomationRunActionStepSchema,
|
|
44722
45222
|
object({
|
|
44723
45223
|
kind: literal("code"),
|
|
44724
45224
|
/** Compiled into the automation's OWN block by esbuild — not a third
|
|
@@ -44781,11 +45281,6 @@ function convertLegacyRecipe(legacy) {
|
|
|
44781
45281
|
...rest
|
|
44782
45282
|
};
|
|
44783
45283
|
}
|
|
44784
|
-
/**
|
|
44785
|
-
* Where recipes are PARSED (the core-blocks cap, the MCP tool, the literal a
|
|
44786
|
-
* saved block carries): the new shape as it is, or the old shape converted to
|
|
44787
|
-
* it. The output type is always the new one.
|
|
44788
|
-
*/
|
|
44789
45284
|
var AutomationRecipeSchema = union([CurrentAutomationRecipeSchema, LegacyAutomationRecipeSchema.transform(convertLegacyRecipe)]);
|
|
44790
45285
|
var DETECTION_DESCRIPTIONS = {
|
|
44791
45286
|
deviceId: "the camera that saw it",
|
|
@@ -44801,6 +45296,21 @@ var STATIONARY_DESCRIPTIONS = {
|
|
|
44801
45296
|
phase: "appeared or departed",
|
|
44802
45297
|
entryId: "the stopped object"
|
|
44803
45298
|
};
|
|
45299
|
+
var BECOMES_DESCRIPTIONS = {
|
|
45300
|
+
deviceId: "the device that changed",
|
|
45301
|
+
from: "the value before the change, empty when it is not known",
|
|
45302
|
+
to: "the value it changed to"
|
|
45303
|
+
};
|
|
45304
|
+
/**
|
|
45305
|
+
* A fire's payload is offered too, as `data.<path>` (`{{vars.data.channel}}`):
|
|
45306
|
+
* those names depend on the device, so they are not in this list.
|
|
45307
|
+
*/
|
|
45308
|
+
var FIRED_DESCRIPTIONS = {
|
|
45309
|
+
deviceId: "the device that fired",
|
|
45310
|
+
eventType: "the event, as the device names it",
|
|
45311
|
+
sourceId: "the part of the device that fired (a button), empty when it names none",
|
|
45312
|
+
eventKind: "the kind of press (press, long-press …), empty when the device declares none"
|
|
45313
|
+
};
|
|
44804
45314
|
function describe$1(descriptions) {
|
|
44805
45315
|
return Object.entries(descriptions).map(([name, description]) => ({
|
|
44806
45316
|
name,
|
|
@@ -44809,8 +45319,74 @@ function describe$1(descriptions) {
|
|
|
44809
45319
|
}
|
|
44810
45320
|
describe$1(DETECTION_DESCRIPTIONS);
|
|
44811
45321
|
describe$1(STATIONARY_DESCRIPTIONS);
|
|
45322
|
+
describe$1(FIRED_DESCRIPTIONS).map((v) => v.name === "deviceId" ? {
|
|
45323
|
+
...v,
|
|
45324
|
+
hintKey: "firedDeviceId"
|
|
45325
|
+
} : v);
|
|
45326
|
+
describe$1(BECOMES_DESCRIPTIONS).map((v) => v.name === "deviceId" ? {
|
|
45327
|
+
...v,
|
|
45328
|
+
hintKey: "becomesDeviceId"
|
|
45329
|
+
} : v);
|
|
44812
45330
|
new Set(Object.values(COCO_TO_MACRO.mapping));
|
|
44813
45331
|
/**
|
|
45332
|
+
* Generic stateless event emitter. Installed on a `DeviceType.EventEmitter`
|
|
45333
|
+
* device. Carries the device's EXACT declared event vocabulary verbatim
|
|
45334
|
+
* (NO normalization). Two shapes:
|
|
45335
|
+
* - structured source (HA `event.*` entity) → `vocabulary` is the fixed
|
|
45336
|
+
* declared set (e.g. press_short, press_long, …).
|
|
45337
|
+
* - generic/legacy source (HA bus events, e.g. zha_event) → `vocabulary`
|
|
45338
|
+
* is [] (open) and `lastEvent.data` carries the complex payload.
|
|
45339
|
+
* `vocabulary` (D1044) names each event for a person and types it for a
|
|
45340
|
+
* rule; `eventTypes` is its list of native tokens, kept for older readers.
|
|
45341
|
+
* A fire names the part of the device it came from in `sourceId` when the
|
|
45342
|
+
* vocabulary declares sources (a remote's buttons).
|
|
45343
|
+
* `seq` is monotonic so two identical events still produce distinct slice writes.
|
|
45344
|
+
*/
|
|
45345
|
+
var EventFireSchema = object({
|
|
45346
|
+
deviceId: number$1(),
|
|
45347
|
+
eventType: string(),
|
|
45348
|
+
sourceId: string().optional(),
|
|
45349
|
+
data: record(string(), unknown()).nullable(),
|
|
45350
|
+
timestamp: number$1().meta(readingMeta("none")),
|
|
45351
|
+
seq: number$1().meta(readingMeta("count"))
|
|
45352
|
+
});
|
|
45353
|
+
var EventEmitterStatusSchema = object({
|
|
45354
|
+
eventTypes: array(string()),
|
|
45355
|
+
/**
|
|
45356
|
+
* Required of a provider by the type. A status written before the field
|
|
45357
|
+
* existed still parses, as an empty vocabulary: read it through
|
|
45358
|
+
* `readEventVocabulary`, which then answers with the plain tokens.
|
|
45359
|
+
*/
|
|
45360
|
+
vocabulary: array(EventVocabularyEntrySchema).default([]),
|
|
45361
|
+
lastEvent: EventFireSchema.nullable(),
|
|
45362
|
+
eventCountSinceStart: number$1().meta(readingMeta("count"))
|
|
45363
|
+
});
|
|
45364
|
+
var eventEmitterCapability = {
|
|
45365
|
+
name: "event-emitter",
|
|
45366
|
+
scope: "device",
|
|
45367
|
+
deviceNative: true,
|
|
45368
|
+
mode: "singleton",
|
|
45369
|
+
deviceTypes: [DeviceType.EventEmitter],
|
|
45370
|
+
methods: {},
|
|
45371
|
+
events: { onEvent: { data: EventFireSchema } },
|
|
45372
|
+
status: {
|
|
45373
|
+
schema: EventEmitterStatusSchema,
|
|
45374
|
+
kind: "push"
|
|
45375
|
+
},
|
|
45376
|
+
runtimeState: EventEmitterStatusSchema,
|
|
45377
|
+
/**
|
|
45378
|
+
* Runtime-state durability: **session** — `eventCountSinceStart` names its own scope.
|
|
45379
|
+
*
|
|
45380
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
45381
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
45382
|
+
*/
|
|
45383
|
+
durability: "session",
|
|
45384
|
+
/**
|
|
45385
|
+
* History (D850): none — `eventCountSinceStart` resets with the addon; events live in ledgers.
|
|
45386
|
+
*/
|
|
45387
|
+
history: "none"
|
|
45388
|
+
};
|
|
45389
|
+
/**
|
|
44814
45390
|
* Schedulers (D983) — an entity that performs actions over the day.
|
|
44815
45391
|
*
|
|
44816
45392
|
* A scheduler has profiles; a profile has weekdays and a timeline of POINTS.
|
|
@@ -44833,6 +45409,13 @@ var SchedulerStateTreeSchema = AutomationWhenSchema.superRefine((when, ctx) => {
|
|
|
44833
45409
|
});
|
|
44834
45410
|
return;
|
|
44835
45411
|
}
|
|
45412
|
+
if (hasTriggeredByLeaves(when)) {
|
|
45413
|
+
ctx.addIssue({
|
|
45414
|
+
code: "custom",
|
|
45415
|
+
message: TRIGGERED_BY_NEEDS_AUTOMATION
|
|
45416
|
+
});
|
|
45417
|
+
return;
|
|
45418
|
+
}
|
|
44836
45419
|
const problem = validateWhenTree(when);
|
|
44837
45420
|
if (problem !== null) ctx.addIssue({
|
|
44838
45421
|
code: "custom",
|
|
@@ -44854,12 +45437,16 @@ var HoldOverrideSchema = discriminatedUnion("mode", [object({ mode: literal("res
|
|
|
44854
45437
|
})]);
|
|
44855
45438
|
/** A missed edge up to this late still runs. */
|
|
44856
45439
|
var DEFAULT_MAX_LATE_MS = 10 * 6e4;
|
|
45440
|
+
var SchedulerStepSchema = ActionStepSchema.refine((step) => step.kind !== "wait" || step.seconds <= 300, {
|
|
45441
|
+
message: `a scheduler point waits at most five minutes (300 s); put a longer wait in an action`,
|
|
45442
|
+
path: ["seconds"]
|
|
45443
|
+
});
|
|
44857
45444
|
var SchedulerPointSchema = object({
|
|
44858
45445
|
id: string().min(1).max(64),
|
|
44859
45446
|
at: SchedulerTimeSpecSchema,
|
|
44860
45447
|
mode: SchedulerPointModeSchema,
|
|
44861
45448
|
/** The actions of an `edge` or `hold`; a `release` has none. */
|
|
44862
|
-
steps: array(
|
|
45449
|
+
steps: array(SchedulerStepSchema).max(20).default([]),
|
|
44863
45450
|
/** Checked when the point runs (an edge) or is applied (a hold). */
|
|
44864
45451
|
conditions: SchedulerStateTreeSchema.optional(),
|
|
44865
45452
|
/** Hold only; absent means `respect`. */
|
|
@@ -45038,7 +45625,9 @@ var AutomationTriggerTraceSchema = object({
|
|
|
45038
45625
|
/** Why an event was rejected ("class person not in [dog]") or what fired. */
|
|
45039
45626
|
reason: string(),
|
|
45040
45627
|
/** deviceId, className, trackId, score … — only what the trigger exposes. */
|
|
45041
|
-
vars: record(string(), TraceValueSchema)
|
|
45628
|
+
vars: record(string(), TraceValueSchema),
|
|
45629
|
+
/** A hold (`for`) this occurrence went through: pending, cancelled, fired or re-seeded. */
|
|
45630
|
+
hold: HoldTraceSchema.optional()
|
|
45042
45631
|
});
|
|
45043
45632
|
var AutomationConditionTraceNodeSchema = object({
|
|
45044
45633
|
/** `when` for the root, `when.<i>` for its i-th child, … (`conditions…` on an old trace). */
|
|
@@ -45051,8 +45640,11 @@ var AutomationConditionTraceNodeSchema = object({
|
|
|
45051
45640
|
"device-is",
|
|
45052
45641
|
"expression",
|
|
45053
45642
|
"stationary-present",
|
|
45643
|
+
"time-is",
|
|
45644
|
+
"triggered-by",
|
|
45054
45645
|
"device-becomes",
|
|
45055
45646
|
"device-event",
|
|
45647
|
+
"device-fired",
|
|
45056
45648
|
"schedule",
|
|
45057
45649
|
"interval",
|
|
45058
45650
|
"manual",
|
|
@@ -45123,18 +45715,46 @@ var StepTraceBaseSchema = object({
|
|
|
45123
45715
|
]).optional(),
|
|
45124
45716
|
/** Why the condition read what it read, in the evaluator's own words. */
|
|
45125
45717
|
conditionReason: string().optional(),
|
|
45126
|
-
/** `empty`: the target resolved to no device, so nothing was done — never silently `ok`. `skipped`: its `if` was false (or unknown) and nothing ran in its place. */
|
|
45718
|
+
/** `empty`: the target resolved to no device, so nothing was done — never silently `ok`. `skipped`: its `if` was false (or unknown) and nothing ran in its place. `failed-continued`: it failed and was marked to be passed over (#500). */
|
|
45127
45719
|
outcome: _enum([
|
|
45128
45720
|
"ok",
|
|
45129
45721
|
"error",
|
|
45130
45722
|
"not-run",
|
|
45131
45723
|
"empty",
|
|
45132
|
-
"skipped"
|
|
45724
|
+
"skipped",
|
|
45725
|
+
"failed-continued",
|
|
45726
|
+
"timed-out"
|
|
45133
45727
|
]),
|
|
45134
45728
|
message: string().optional()
|
|
45135
45729
|
});
|
|
45730
|
+
/** What a block step adds to its row: which list ran, what each branch read, how many rounds. */
|
|
45731
|
+
var blockTraceShape = {
|
|
45732
|
+
/** A `choose` step (#500): the list that ran, `branches.<n>` or `else`. Absent when none did. */
|
|
45733
|
+
branch: string().optional(),
|
|
45734
|
+
/** A `choose` step: what each branch read, in order, up to the one that decided. */
|
|
45735
|
+
branchChecks: array(object({
|
|
45736
|
+
condition: _enum([
|
|
45737
|
+
"true",
|
|
45738
|
+
"false",
|
|
45739
|
+
"unknown"
|
|
45740
|
+
]),
|
|
45741
|
+
conditionReason: string().optional()
|
|
45742
|
+
})).optional(),
|
|
45743
|
+
/** A `repeat` step (#500): how many times its steps ran. */
|
|
45744
|
+
iterations: number$1().int().optional()
|
|
45745
|
+
};
|
|
45746
|
+
/** A step one level down: it may be a block step itself, whose own steps are plain rows (the nesting ends there). */
|
|
45747
|
+
var NestedStepTraceSchema = StepTraceBaseSchema.extend({
|
|
45748
|
+
...blockTraceShape,
|
|
45749
|
+
steps: array(StepTraceBaseSchema).optional()
|
|
45750
|
+
});
|
|
45136
45751
|
/** A step trace; the `else` steps that ran are one level of the same shape (the nesting is bounded at one). */
|
|
45137
|
-
var AutomationStepTraceSchema = StepTraceBaseSchema.extend({
|
|
45752
|
+
var AutomationStepTraceSchema = StepTraceBaseSchema.extend({
|
|
45753
|
+
else: array(StepTraceBaseSchema).optional(),
|
|
45754
|
+
...blockTraceShape,
|
|
45755
|
+
/** A `choose` step: the steps of the list that ran. A `parallel`: its members. A `repeat`: the LAST iteration. */
|
|
45756
|
+
steps: array(NestedStepTraceSchema).optional()
|
|
45757
|
+
});
|
|
45138
45758
|
var AutomationRunTraceSchema = object({
|
|
45139
45759
|
runId: string().min(1),
|
|
45140
45760
|
startedAt: number$1(),
|
|
@@ -45384,7 +46004,7 @@ object({
|
|
|
45384
46004
|
* is irrelevant; the array is treated as a set by consumers. */
|
|
45385
46005
|
hiddenChildIds: array(number$1()).readonly()
|
|
45386
46006
|
});
|
|
45387
|
-
DeviceType.Camera, DeviceType.
|
|
46007
|
+
DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Sensor, DeviceType.Climate, DeviceType.Cover, DeviceType.Lock, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Generic, method(object({
|
|
45388
46008
|
deviceId: number$1().int().nonnegative(),
|
|
45389
46009
|
childDeviceId: number$1().int().nonnegative(),
|
|
45390
46010
|
hidden: boolean()
|
|
@@ -45541,7 +46161,9 @@ var AddonHttpRouteSchema = object({
|
|
|
45541
46161
|
]).optional(),
|
|
45542
46162
|
description: string().optional(),
|
|
45543
46163
|
/** The hub limits this route per client address before the handler runs. */
|
|
45544
|
-
rateLimit: _enum(["auth"]).optional()
|
|
46164
|
+
rateLimit: _enum(["auth"]).optional(),
|
|
46165
|
+
/** Served once per integration instance; the handler reads `instanceId`. */
|
|
46166
|
+
perInstance: boolean().optional()
|
|
45545
46167
|
});
|
|
45546
46168
|
/**
|
|
45547
46169
|
* Cross-process route invocation envelope. The hub captures the
|
|
@@ -45558,6 +46180,8 @@ var InvokeRequestSchema = object({
|
|
|
45558
46180
|
query: record(string(), string()),
|
|
45559
46181
|
body: unknown(),
|
|
45560
46182
|
headers: record(string(), string()),
|
|
46183
|
+
/** The instance the hub resolved for a `perInstance` route. */
|
|
46184
|
+
instanceId: string().optional(),
|
|
45561
46185
|
user: object({
|
|
45562
46186
|
id: string(),
|
|
45563
46187
|
username: string(),
|
|
@@ -47682,7 +48306,7 @@ var BATTERY_DEVICE_TYPES = [
|
|
|
47682
48306
|
DeviceType.Switch,
|
|
47683
48307
|
DeviceType.PetFeeder,
|
|
47684
48308
|
DeviceType.Container,
|
|
47685
|
-
DeviceType.
|
|
48309
|
+
DeviceType.Climate,
|
|
47686
48310
|
DeviceType.EventEmitter
|
|
47687
48311
|
];
|
|
47688
48312
|
/** Generic Customize target for the percentage reading (D936). */
|
|
@@ -49445,7 +50069,14 @@ var climateControlCapability = {
|
|
|
49445
50069
|
scope: "device",
|
|
49446
50070
|
deviceNative: true,
|
|
49447
50071
|
mode: "singleton",
|
|
49448
|
-
deviceTypes: [DeviceType.
|
|
50072
|
+
deviceTypes: [DeviceType.Climate],
|
|
50073
|
+
power: {
|
|
50074
|
+
kind: "mode",
|
|
50075
|
+
field: "mode",
|
|
50076
|
+
offValue: "off",
|
|
50077
|
+
set: "setMode",
|
|
50078
|
+
lastField: "lastActiveMode"
|
|
50079
|
+
},
|
|
49449
50080
|
methods: {
|
|
49450
50081
|
setMode: method(object({
|
|
49451
50082
|
deviceId: number$1().int().nonnegative(),
|
|
@@ -49830,6 +50461,7 @@ var CompositionProblemSchema = object({
|
|
|
49830
50461
|
"nested-field",
|
|
49831
50462
|
"missing-field",
|
|
49832
50463
|
"kind-mismatch",
|
|
50464
|
+
"source-kind-mismatch",
|
|
49833
50465
|
"may-be-null",
|
|
49834
50466
|
"source-missing",
|
|
49835
50467
|
"source-unreadable",
|
|
@@ -49851,7 +50483,8 @@ var CompositionProblemSchema = object({
|
|
|
49851
50483
|
"command-method-unknown",
|
|
49852
50484
|
"command-source-unknown",
|
|
49853
50485
|
"command-input-unknown",
|
|
49854
|
-
"command-input-missing"
|
|
50486
|
+
"command-input-missing",
|
|
50487
|
+
"power-source-is-actuator"
|
|
49855
50488
|
]),
|
|
49856
50489
|
path: string(),
|
|
49857
50490
|
message: string()
|
|
@@ -50889,6 +51522,13 @@ var mediaPlayerCapability = {
|
|
|
50889
51522
|
deviceNative: true,
|
|
50890
51523
|
mode: "singleton",
|
|
50891
51524
|
deviceTypes: [DeviceType.MediaPlayer],
|
|
51525
|
+
power: {
|
|
51526
|
+
kind: "methods",
|
|
51527
|
+
field: "state",
|
|
51528
|
+
offValues: ["off", "standby"],
|
|
51529
|
+
on: "turnOn",
|
|
51530
|
+
off: "turnOff"
|
|
51531
|
+
},
|
|
50892
51532
|
methods: {
|
|
50893
51533
|
turnOn: optionalMethod(deviceOnly, _void(), {
|
|
50894
51534
|
kind: "mutation",
|
|
@@ -51019,7 +51659,7 @@ var mediaPlayerCapability = {
|
|
|
51019
51659
|
isMuted: { kind: "boolean" }
|
|
51020
51660
|
} }
|
|
51021
51661
|
};
|
|
51022
|
-
DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.
|
|
51662
|
+
DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Climate, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway, DeviceType.HeatPump, DeviceType.Camera, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Fan, DeviceType.Lock, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Gateway, DeviceType.Camera, DeviceType.Sensor, DeviceType.MediaPlayer, DeviceType.Vacuum, DeviceType.Generic, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.PetFountain, DeviceType.Fan, DeviceType.Humidifier, DeviceType.Climate, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.PetFeeder, DeviceType.MediaPlayer;
|
|
51023
51663
|
var NUMBER$1 = {
|
|
51024
51664
|
kind: "fixed",
|
|
51025
51665
|
result: ["number"]
|
|
@@ -51102,7 +51742,7 @@ function numeric$1(input) {
|
|
|
51102
51742
|
}, ...alsoFrom === void 0 ? [] : [alsoFrom.accept]]
|
|
51103
51743
|
};
|
|
51104
51744
|
}
|
|
51105
|
-
var CLIMATE_DEVICES = [DeviceType.
|
|
51745
|
+
var CLIMATE_DEVICES = [DeviceType.Climate];
|
|
51106
51746
|
function binary(input) {
|
|
51107
51747
|
return {
|
|
51108
51748
|
...input,
|
|
@@ -51424,8 +52064,8 @@ var GROUP_TEMPLATES = [
|
|
|
51424
52064
|
actuator({
|
|
51425
52065
|
id: "climate",
|
|
51426
52066
|
summary: "Running when at least one thermostat or air conditioner is not off, or only when all of them are. Target temperature and fan are offered only when every device has them. A device that reports nothing is left out, never read as off.",
|
|
51427
|
-
deviceType: DeviceType.
|
|
51428
|
-
memberDeviceTypes: [DeviceType.
|
|
52067
|
+
deviceType: DeviceType.Climate,
|
|
52068
|
+
memberDeviceTypes: [DeviceType.Climate],
|
|
51429
52069
|
outputCap: "group-active",
|
|
51430
52070
|
outputField: "active",
|
|
51431
52071
|
member: {
|
|
@@ -51468,6 +52108,118 @@ function actuator(input) {
|
|
|
51468
52108
|
new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
|
|
51469
52109
|
DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
|
|
51470
52110
|
DeviceType.Camera;
|
|
52111
|
+
var PowerSourceRefusalReasonSchema = _enum([
|
|
52112
|
+
"no-power-source",
|
|
52113
|
+
"switch-missing",
|
|
52114
|
+
"switch-offline",
|
|
52115
|
+
"switch-unknown",
|
|
52116
|
+
"restart-in-progress",
|
|
52117
|
+
"switch-call-failed",
|
|
52118
|
+
"switch-is-actuator"
|
|
52119
|
+
]);
|
|
52120
|
+
var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
|
|
52121
|
+
accepted: literal(false),
|
|
52122
|
+
reason: PowerSourceRefusalReasonSchema,
|
|
52123
|
+
/** One line for the operator, naming the switch. */
|
|
52124
|
+
message: string()
|
|
52125
|
+
})]);
|
|
52126
|
+
var PowerSourceInfoSchema = object({
|
|
52127
|
+
/** False when the device has no power source linked; the rest is then empty. */
|
|
52128
|
+
linked: boolean(),
|
|
52129
|
+
switchDeviceId: number$1().nullable(),
|
|
52130
|
+
switchName: string().nullable(),
|
|
52131
|
+
/** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
|
|
52132
|
+
switchOn: boolean().nullable(),
|
|
52133
|
+
switchOnline: boolean().nullable(),
|
|
52134
|
+
restartInProgress: boolean(),
|
|
52135
|
+
offSeconds: number$1().int()
|
|
52136
|
+
});
|
|
52137
|
+
/** The composed slice: the switch state the link mirrors, plus the stored off time. */
|
|
52138
|
+
var PowerSourceStateSchema = object({
|
|
52139
|
+
/** Null while the switch has not reported: unknown is never `false` (D393). */
|
|
52140
|
+
switchOn: boolean().nullable(),
|
|
52141
|
+
offSeconds: number$1().int(),
|
|
52142
|
+
lastFetchedAt: number$1()
|
|
52143
|
+
});
|
|
52144
|
+
var POWER_SOURCE_DEVICE_TYPES = [
|
|
52145
|
+
DeviceType.Gateway,
|
|
52146
|
+
DeviceType.Camera,
|
|
52147
|
+
DeviceType.Sensor,
|
|
52148
|
+
DeviceType.MediaPlayer,
|
|
52149
|
+
DeviceType.Vacuum,
|
|
52150
|
+
DeviceType.Generic,
|
|
52151
|
+
DeviceType.Washer,
|
|
52152
|
+
DeviceType.Dryer,
|
|
52153
|
+
DeviceType.Dishwasher,
|
|
52154
|
+
DeviceType.PetFountain,
|
|
52155
|
+
DeviceType.Fan,
|
|
52156
|
+
DeviceType.Humidifier,
|
|
52157
|
+
DeviceType.Climate,
|
|
52158
|
+
DeviceType.WaterHeater
|
|
52159
|
+
];
|
|
52160
|
+
var powerSourceCustomize = {
|
|
52161
|
+
version: 1,
|
|
52162
|
+
targets: [{
|
|
52163
|
+
id: "switch",
|
|
52164
|
+
cardinality: "scalar",
|
|
52165
|
+
vocabulary: {
|
|
52166
|
+
cap: "power-source",
|
|
52167
|
+
stateField: "switchOn"
|
|
52168
|
+
},
|
|
52169
|
+
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
52170
|
+
apply: "add",
|
|
52171
|
+
path: "switchOn",
|
|
52172
|
+
sources: [{
|
|
52173
|
+
kind: "switch",
|
|
52174
|
+
transforms: [{ kind: "identity" }]
|
|
52175
|
+
}],
|
|
52176
|
+
companions: [{
|
|
52177
|
+
kind: "constant",
|
|
52178
|
+
path: "offSeconds",
|
|
52179
|
+
value: 10,
|
|
52180
|
+
when: "add",
|
|
52181
|
+
formerValues: [0]
|
|
52182
|
+
}, {
|
|
52183
|
+
kind: "clock",
|
|
52184
|
+
path: "lastFetchedAt",
|
|
52185
|
+
when: "always"
|
|
52186
|
+
}],
|
|
52187
|
+
sourceReport: { status: "assignment-status" }
|
|
52188
|
+
}]
|
|
52189
|
+
};
|
|
52190
|
+
var deviceInput$2 = object({ deviceId: number$1().int().nonnegative() });
|
|
52191
|
+
var powerSourceCapability = {
|
|
52192
|
+
name: "power-source",
|
|
52193
|
+
scope: "device",
|
|
52194
|
+
mode: "singleton",
|
|
52195
|
+
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
52196
|
+
methods: {
|
|
52197
|
+
getStatus: systemMethod(deviceInput$2, PowerSourceInfoSchema, { auth: "protected" }),
|
|
52198
|
+
powerOn: systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
52199
|
+
kind: "mutation",
|
|
52200
|
+
auth: "protected"
|
|
52201
|
+
}),
|
|
52202
|
+
powerOff: systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
52203
|
+
kind: "mutation",
|
|
52204
|
+
auth: "protected"
|
|
52205
|
+
}),
|
|
52206
|
+
forceRestart: systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
52207
|
+
kind: "mutation",
|
|
52208
|
+
auth: "protected"
|
|
52209
|
+
})
|
|
52210
|
+
},
|
|
52211
|
+
runtimeState: PowerSourceStateSchema,
|
|
52212
|
+
customize: powerSourceCustomize,
|
|
52213
|
+
/**
|
|
52214
|
+
* Runtime-state durability: **session** — mirrored from the linked switch on
|
|
52215
|
+
* its next report; a restored value would outlive the reading it names.
|
|
52216
|
+
*
|
|
52217
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
52218
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
52219
|
+
*/
|
|
52220
|
+
durability: "session",
|
|
52221
|
+
history: "none"
|
|
52222
|
+
};
|
|
51471
52223
|
`${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
|
|
51472
52224
|
/**
|
|
51473
52225
|
* What a composition may BUILD on a device type (G2, D676): the caps a new
|
|
@@ -52512,27 +53264,35 @@ var deviceAdminLinkCapability = {
|
|
|
52512
53264
|
getAdminLink: method(object({ deviceId: number$1().int().nonnegative() }), DeviceAdminLinkSchema.nullable(), { auth: "admin" }) }
|
|
52513
53265
|
};
|
|
52514
53266
|
/**
|
|
52515
|
-
* device-
|
|
52516
|
-
*
|
|
52517
|
-
*
|
|
52518
|
-
*
|
|
52519
|
-
*
|
|
52520
|
-
*
|
|
52521
|
-
*
|
|
52522
|
-
*
|
|
52523
|
-
*
|
|
52524
|
-
*
|
|
52525
|
-
*
|
|
52526
|
-
*
|
|
52527
|
-
*
|
|
52528
|
-
*
|
|
52529
|
-
*
|
|
52530
|
-
*
|
|
52531
|
-
*
|
|
52532
|
-
|
|
52533
|
-
|
|
52534
|
-
*
|
|
52535
|
-
*
|
|
53267
|
+
* `device-adoption` — generic discovery + adoption surface,
|
|
53268
|
+
* system-scoped collection (one provider per integration addon), routed
|
|
53269
|
+
* by explicit `{ addonId }` on direct `deviceAdoption.*` calls — the same
|
|
53270
|
+
* pin model as `connection-test`. The admin UI keeps using
|
|
53271
|
+
* `deviceManager.adoption*` (which already threads `addonId`). The de-HA-ified
|
|
53272
|
+
* successor to `ha-discovery`:
|
|
53273
|
+
* keyed by `integrationId` (the addon resolves integrationId → broker
|
|
53274
|
+
* internally) rather than a connection-specific `brokerId`, and it
|
|
53275
|
+
* OWNS the shared `DiscoveredChildDevice` candidate type
|
|
53276
|
+
* (declared below) so a single shared adoption modal renders any integration's
|
|
53277
|
+
* candidate tree.
|
|
53278
|
+
*
|
|
53279
|
+
* UI flow (Integrations page → integration detail):
|
|
53280
|
+
* 1. `listCandidates({integrationId, page, pageSize, filter})` →
|
|
53281
|
+
* paginated candidate list, each row collapsed.
|
|
53282
|
+
* 2. User expands a row → `getCandidate({integrationId,
|
|
53283
|
+
* childNativeId})` returns the single candidate (with its nested
|
|
53284
|
+
* `children`) for the accordion render.
|
|
53285
|
+
* 3. User clicks "Adopt" on one or more candidates →
|
|
53286
|
+
* `adopt({integrationId, childNativeIds, perCandidate?})`
|
|
53287
|
+
* materialises one parent CamStack device per candidate plus its
|
|
53288
|
+
* accessory children. `perCandidate[childNativeId]` carries an
|
|
53289
|
+
* optional display-name override + a `hiddenChildIds` pre-hide set
|
|
53290
|
+
* (the parent's `accessories.hiddenChildIds` gates UI visibility).
|
|
53291
|
+
* 4. User clicks "Release" → `release({integrationId, camDeviceId})`
|
|
53292
|
+
* removes the parent + every child from the kernel registry.
|
|
53293
|
+
*/
|
|
53294
|
+
/**
|
|
53295
|
+
* Status enum for a discovered device. Generic enough for
|
|
52536
53296
|
* battery cams, awake cams, and unreachable cams alike.
|
|
52537
53297
|
*/
|
|
52538
53298
|
var DiscoveredChildStatusSchema = _enum([
|
|
@@ -52549,12 +53309,12 @@ var DiscoveredChildDeviceSchema = lazy(() => object({
|
|
|
52549
53309
|
/** DeviceType the child should be created with on adopt. */
|
|
52550
53310
|
type: _enum(DeviceType),
|
|
52551
53311
|
status: DiscoveredChildStatusSchema,
|
|
52552
|
-
/** Free-form integration-specific metadata surfaced in the
|
|
53312
|
+
/** Free-form integration-specific metadata surfaced in the adoption modal. */
|
|
52553
53313
|
metadata: object({
|
|
52554
53314
|
model: string().optional(),
|
|
52555
53315
|
serialNumber: string().optional(),
|
|
52556
53316
|
uid: string().optional(),
|
|
52557
|
-
/** Reolink: 0-based channel index inside the parent NVR
|
|
53317
|
+
/** Reolink: 0-based channel index inside the parent NVR. */
|
|
52558
53318
|
rtspChannel: number$1().int().nonnegative().optional(),
|
|
52559
53319
|
isBattery: boolean().optional(),
|
|
52560
53320
|
isDoorbell: boolean().optional(),
|
|
@@ -52570,137 +53330,24 @@ var DiscoveredChildDeviceSchema = lazy(() => object({
|
|
|
52570
53330
|
}).default({}),
|
|
52571
53331
|
/**
|
|
52572
53332
|
* `true` when the framework already created a child device for this
|
|
52573
|
-
* `childNativeId` under the current parent. The
|
|
53333
|
+
* `childNativeId` under the current parent. The adoption modal uses it to
|
|
52574
53334
|
* gate the Add/Remove button and surface the existing `deviceId`.
|
|
52575
53335
|
*/
|
|
52576
53336
|
alreadyAdopted: boolean(),
|
|
52577
53337
|
/** When `alreadyAdopted=true`, the framework-assigned child device id. */
|
|
52578
53338
|
adoptedDeviceId: number$1().int().nonnegative().nullable(),
|
|
52579
|
-
/** Capability names the candidate advertises — integrations populate,
|
|
53339
|
+
/** Capability names the candidate advertises — integrations populate, flat enumerations omit. */
|
|
52580
53340
|
capabilities: array(string()).readonly().optional(),
|
|
52581
|
-
/** Feature flags the candidate advertises — integrations populate,
|
|
53341
|
+
/** Feature flags the candidate advertises — integrations populate, flat enumerations omit. */
|
|
52582
53342
|
features: array(string()).readonly().optional(),
|
|
52583
53343
|
/** Upstream-system identity + rendering hints for the candidate. */
|
|
52584
53344
|
sourceInfo: SourceInfoSchema.optional(),
|
|
52585
|
-
/** Nested entity-children for accordion preview — integrations populate,
|
|
53345
|
+
/** Nested entity-children for accordion preview — integrations populate, flat enumerations omit. */
|
|
52586
53346
|
children: array(DiscoveredChildDeviceSchema).readonly().optional(),
|
|
52587
53347
|
/** `childNativeId` of the gateway candidate in the same list this device sits behind. */
|
|
52588
53348
|
parentStableId: string().optional()
|
|
52589
53349
|
}));
|
|
52590
53350
|
/**
|
|
52591
|
-
* Status block — populated by the integration after each
|
|
52592
|
-
* `refreshDiscovery` (or push-driven update). The runtime-state slice
|
|
52593
|
-
* is what the panel reads via the per-device proxy; `getStatus`
|
|
52594
|
-
* returns the same payload via the codegen'd auto-injection.
|
|
52595
|
-
*/
|
|
52596
|
-
var DeviceDiscoveryStatusSchema = object({
|
|
52597
|
-
discovered: array(DiscoveredChildDeviceSchema),
|
|
52598
|
-
/** Wall-clock ms of the last successful enumeration. */
|
|
52599
|
-
lastDiscoveryAt: number$1().int().nonnegative().nullable(),
|
|
52600
|
-
/** Last error surfaced from the source (rendered as a banner). */
|
|
52601
|
-
lastError: string().nullable()
|
|
52602
|
-
});
|
|
52603
|
-
var deviceDiscoveryCapability = {
|
|
52604
|
-
name: "device-discovery",
|
|
52605
|
-
scope: "device",
|
|
52606
|
-
deviceNative: true,
|
|
52607
|
-
mode: "singleton",
|
|
52608
|
-
deviceTypes: [DeviceType.Hub],
|
|
52609
|
-
status: {
|
|
52610
|
-
schema: DeviceDiscoveryStatusSchema,
|
|
52611
|
-
kind: "poll"
|
|
52612
|
-
},
|
|
52613
|
-
runtimeState: DeviceDiscoveryStatusSchema.extend({ lastFetchedAt: number$1().int().nonnegative() }),
|
|
52614
|
-
/**
|
|
52615
|
-
* Runtime-state durability: **session** — 5.7 KB of scan output on the largest device, fully re-derivable by re-scanning.
|
|
52616
|
-
*
|
|
52617
|
-
* See `RuntimeStateDurability`. Enforced by
|
|
52618
|
-
* `scripts/check-runtime-state-durability.ts`.
|
|
52619
|
-
*/
|
|
52620
|
-
durability: "session",
|
|
52621
|
-
/**
|
|
52622
|
-
* History (D850): none — inventory.
|
|
52623
|
-
*/
|
|
52624
|
-
history: "none",
|
|
52625
|
-
methods: {
|
|
52626
|
-
/**
|
|
52627
|
-
* Snapshot of the current `discovered` list. Returns the
|
|
52628
|
-
* runtime-state cache — call `refreshDiscovery` first if a
|
|
52629
|
-
* fresh round-trip to the source is required.
|
|
52630
|
-
*/
|
|
52631
|
-
listDiscovered: method(object({ deviceId: number$1().int().nonnegative() }), array(DiscoveredChildDeviceSchema).readonly()),
|
|
52632
|
-
/**
|
|
52633
|
-
* Force the integration to re-enumerate and update the
|
|
52634
|
-
* runtime-state slice. Returns the freshly-enumerated list (also
|
|
52635
|
-
* available via `listDiscovered` post-call).
|
|
52636
|
-
*/
|
|
52637
|
-
refreshDiscovery: method(object({ deviceId: number$1().int().nonnegative() }), array(DiscoveredChildDeviceSchema).readonly(), {
|
|
52638
|
-
kind: "mutation",
|
|
52639
|
-
auth: "admin"
|
|
52640
|
-
}),
|
|
52641
|
-
/**
|
|
52642
|
-
* Promote a discovered entry to a real child device. The framework
|
|
52643
|
-
* creates the child via `kernel.devices.create()` with
|
|
52644
|
-
* `parentDeviceId = parent.id` and seeds the child's config from
|
|
52645
|
-
* `childInitialConfig` (driver-defined; usually carries channel +
|
|
52646
|
-
* uid + parent reference). Returns the kernel-assigned numeric id.
|
|
52647
|
-
*/
|
|
52648
|
-
adoptDevice: method(object({
|
|
52649
|
-
deviceId: number$1().int().nonnegative(),
|
|
52650
|
-
childNativeId: string(),
|
|
52651
|
-
/** Optional override for the child's display name. */
|
|
52652
|
-
name: string().optional()
|
|
52653
|
-
}), object({
|
|
52654
|
-
deviceId: number$1().int().nonnegative(),
|
|
52655
|
-
stableId: string()
|
|
52656
|
-
}), {
|
|
52657
|
-
kind: "mutation",
|
|
52658
|
-
auth: "admin"
|
|
52659
|
-
}),
|
|
52660
|
-
/**
|
|
52661
|
-
* Inverse of `adoptDevice`: removes the child device from the
|
|
52662
|
-
* kernel registry. The discovered entry remains in the
|
|
52663
|
-
* enumeration (status updates resume) so the operator can re-adopt
|
|
52664
|
-
* it later without a fresh refresh.
|
|
52665
|
-
*/
|
|
52666
|
-
releaseDevice: method(object({
|
|
52667
|
-
deviceId: number$1().int().nonnegative(),
|
|
52668
|
-
childDeviceId: number$1().int().nonnegative()
|
|
52669
|
-
}), _void(), {
|
|
52670
|
-
kind: "mutation",
|
|
52671
|
-
auth: "admin"
|
|
52672
|
-
})
|
|
52673
|
-
}
|
|
52674
|
-
};
|
|
52675
|
-
/**
|
|
52676
|
-
* `device-adoption` — generic discovery + adoption surface,
|
|
52677
|
-
* system-scoped collection (one provider per integration addon), routed
|
|
52678
|
-
* by explicit `{ addonId }` on direct `deviceAdoption.*` calls — the same
|
|
52679
|
-
* pin model as `connection-test`. The admin UI keeps using
|
|
52680
|
-
* `deviceManager.adoption*` (which already threads `addonId`). The de-HA-ified
|
|
52681
|
-
* successor to `ha-discovery`:
|
|
52682
|
-
* keyed by `integrationId` (the addon resolves integrationId → broker
|
|
52683
|
-
* internally) rather than a connection-specific `brokerId`, and it
|
|
52684
|
-
* REUSES the shared `DiscoveredChildDevice` candidate type from
|
|
52685
|
-
* `device-discovery.cap.ts` so a single shared adoption panel renders
|
|
52686
|
-
* any integration's candidate tree.
|
|
52687
|
-
*
|
|
52688
|
-
* UI flow (Integrations page → integration detail):
|
|
52689
|
-
* 1. `listCandidates({integrationId, page, pageSize, filter})` →
|
|
52690
|
-
* paginated candidate list, each row collapsed.
|
|
52691
|
-
* 2. User expands a row → `getCandidate({integrationId,
|
|
52692
|
-
* childNativeId})` returns the single candidate (with its nested
|
|
52693
|
-
* `children`) for the accordion render.
|
|
52694
|
-
* 3. User clicks "Adopt" on one or more candidates →
|
|
52695
|
-
* `adopt({integrationId, childNativeIds, perCandidate?})`
|
|
52696
|
-
* materialises one parent CamStack device per candidate plus its
|
|
52697
|
-
* accessory children. `perCandidate[childNativeId]` carries an
|
|
52698
|
-
* optional display-name override + a `hiddenChildIds` pre-hide set
|
|
52699
|
-
* (the parent's `accessories.hiddenChildIds` gates UI visibility).
|
|
52700
|
-
* 4. User clicks "Release" → `release({integrationId, camDeviceId})`
|
|
52701
|
-
* removes the parent + every child from the kernel registry.
|
|
52702
|
-
*/
|
|
52703
|
-
/**
|
|
52704
53351
|
* Provider-declared discovery *granularity* filter (the keystone of the
|
|
52705
53352
|
* single-entity-import design). A provider advertises which discovery
|
|
52706
53353
|
* granularities it supports via `listCandidateFilters`; the operator picks
|
|
@@ -54128,8 +54775,14 @@ method(object({
|
|
|
54128
54775
|
"string",
|
|
54129
54776
|
"number",
|
|
54130
54777
|
"boolean",
|
|
54131
|
-
"enum"
|
|
54778
|
+
"enum",
|
|
54779
|
+
"union"
|
|
54132
54780
|
]),
|
|
54781
|
+
unionMembers: array(_enum([
|
|
54782
|
+
"string",
|
|
54783
|
+
"number",
|
|
54784
|
+
"boolean"
|
|
54785
|
+
])).optional(),
|
|
54133
54786
|
enumValues: array(string()).optional(),
|
|
54134
54787
|
item: boolean().optional(),
|
|
54135
54788
|
quantity: ReadingQuantitySchema.optional()
|
|
@@ -56221,53 +56874,6 @@ var enumSensorCapability = {
|
|
|
56221
56874
|
* whether persisting is worth a SQLite commit. */
|
|
56222
56875
|
volatileStateFields: ["lastFetchedAt", "timestamp"]
|
|
56223
56876
|
};
|
|
56224
|
-
/**
|
|
56225
|
-
* Generic stateless event emitter. Installed on a `DeviceType.EventEmitter`
|
|
56226
|
-
* device. Carries the device's EXACT declared event vocabulary verbatim
|
|
56227
|
-
* (NO normalization). Two shapes:
|
|
56228
|
-
* - structured source (HA `event.*` entity) → `eventTypes` is the fixed
|
|
56229
|
-
* declared vocabulary (e.g. ['press_short','press_long',...]).
|
|
56230
|
-
* - generic/legacy source (HA bus events, e.g. zha_event) → `eventTypes`
|
|
56231
|
-
* is [] (open) and `lastEvent.data` carries the complex payload.
|
|
56232
|
-
* `seq` is monotonic so two identical events still produce distinct slice writes.
|
|
56233
|
-
*/
|
|
56234
|
-
var EventFireSchema = object({
|
|
56235
|
-
deviceId: number$1(),
|
|
56236
|
-
eventType: string(),
|
|
56237
|
-
data: record(string(), unknown()).nullable(),
|
|
56238
|
-
timestamp: number$1().meta(readingMeta("none")),
|
|
56239
|
-
seq: number$1().meta(readingMeta("count"))
|
|
56240
|
-
});
|
|
56241
|
-
var EventEmitterStatusSchema = object({
|
|
56242
|
-
eventTypes: array(string()),
|
|
56243
|
-
lastEvent: EventFireSchema.nullable(),
|
|
56244
|
-
eventCountSinceStart: number$1().meta(readingMeta("count"))
|
|
56245
|
-
});
|
|
56246
|
-
var eventEmitterCapability = {
|
|
56247
|
-
name: "event-emitter",
|
|
56248
|
-
scope: "device",
|
|
56249
|
-
deviceNative: true,
|
|
56250
|
-
mode: "singleton",
|
|
56251
|
-
deviceTypes: [DeviceType.EventEmitter],
|
|
56252
|
-
methods: {},
|
|
56253
|
-
events: { onEvent: { data: EventFireSchema } },
|
|
56254
|
-
status: {
|
|
56255
|
-
schema: EventEmitterStatusSchema,
|
|
56256
|
-
kind: "push"
|
|
56257
|
-
},
|
|
56258
|
-
runtimeState: EventEmitterStatusSchema,
|
|
56259
|
-
/**
|
|
56260
|
-
* Runtime-state durability: **session** — `eventCountSinceStart` names its own scope.
|
|
56261
|
-
*
|
|
56262
|
-
* See `RuntimeStateDurability`. Enforced by
|
|
56263
|
-
* `scripts/check-runtime-state-durability.ts`.
|
|
56264
|
-
*/
|
|
56265
|
-
durability: "session",
|
|
56266
|
-
/**
|
|
56267
|
-
* History (D850): none — `eventCountSinceStart` resets with the addon; events live in ledgers.
|
|
56268
|
-
*/
|
|
56269
|
-
history: "none"
|
|
56270
|
-
};
|
|
56271
56877
|
var IdentitySchema = object({
|
|
56272
56878
|
id: string(),
|
|
56273
56879
|
name: string(),
|
|
@@ -56757,8 +57363,85 @@ method(_void(), array(FailureContributionSchema).readonly());
|
|
|
56757
57363
|
* - `FanPreset` — preset selector
|
|
56758
57364
|
* - `FanDirection` — direction toggle (ceiling fans)
|
|
56759
57365
|
* - `FanOscillating` — oscillation toggle
|
|
57366
|
+
*
|
|
57367
|
+
* Per-axis oscillation (`oscillationAxes`, `setOscillationAxis`, D1034): an air
|
|
57368
|
+
* circulator sweeps horizontally and vertically, each axis with its own
|
|
57369
|
+
* switch, fixed angle, sweep bounds and limits. A provider may report them
|
|
57370
|
+
* natively; otherwise the OPERATOR links sibling devices to each field in
|
|
57371
|
+
* Customize (a `replace` over the fields listed in `overridable` — nothing is
|
|
57372
|
+
* linked or inferred by itself), and a write to a linked field is forwarded to
|
|
57373
|
+
* its source. `oscillating` stays the single summary flag: when axes exist it
|
|
57374
|
+
* means "any axis oscillates" (`anyAxisOscillating`).
|
|
56760
57375
|
*/
|
|
56761
57376
|
var FanDirectionSchema = _enum(["forward", "reverse"]);
|
|
57377
|
+
var FAN_OSCILLATION_AXES = ["horizontal", "vertical"];
|
|
57378
|
+
var FanOscillationAxisNameSchema = _enum(FAN_OSCILLATION_AXES);
|
|
57379
|
+
var FanOscillationAxisFieldSchema = _enum([
|
|
57380
|
+
"oscillating",
|
|
57381
|
+
"angle",
|
|
57382
|
+
"sweepFrom",
|
|
57383
|
+
"sweepTo"
|
|
57384
|
+
]);
|
|
57385
|
+
/** The limits of one axis, read from the source that serves its angle. */
|
|
57386
|
+
var FAN_OSCILLATION_AXIS_LIMITS = [
|
|
57387
|
+
"min",
|
|
57388
|
+
"max",
|
|
57389
|
+
"step"
|
|
57390
|
+
];
|
|
57391
|
+
/**
|
|
57392
|
+
* One oscillation axis. Every reading the device has not reported is `null`,
|
|
57393
|
+
* never `0` (D393): `0°` is a real position. Each leaf defaults to `null`, so
|
|
57394
|
+
* a slice that names only the leaves it knows (a Customize link of one field)
|
|
57395
|
+
* is still a whole axis.
|
|
57396
|
+
*/
|
|
57397
|
+
var FanOscillationAxisSchema = object({
|
|
57398
|
+
/** Whether the axis sweeps. */
|
|
57399
|
+
oscillating: boolean().nullable().default(null),
|
|
57400
|
+
/** The fixed position (degrees) the axis holds when it does not sweep. */
|
|
57401
|
+
angle: number$1().nullable().default(null),
|
|
57402
|
+
/** The sweep bounds (degrees): left → right, bottom → top. */
|
|
57403
|
+
sweepFrom: number$1().nullable().default(null),
|
|
57404
|
+
sweepTo: number$1().nullable().default(null),
|
|
57405
|
+
/** The angles the axis accepts. Null when the device states none. */
|
|
57406
|
+
min: number$1().nullable().default(null),
|
|
57407
|
+
max: number$1().nullable().default(null),
|
|
57408
|
+
step: number$1().nullable().default(null)
|
|
57409
|
+
});
|
|
57410
|
+
/** Both axes; an axis the device does not have is `null`. */
|
|
57411
|
+
var FanOscillationAxesSchema = object({
|
|
57412
|
+
horizontal: FanOscillationAxisSchema.nullable().default(null),
|
|
57413
|
+
vertical: FanOscillationAxisSchema.nullable().default(null)
|
|
57414
|
+
});
|
|
57415
|
+
var FanOscillationAxisRefusalSchema = _enum([
|
|
57416
|
+
"nothing-to-set",
|
|
57417
|
+
"axis-not-linked",
|
|
57418
|
+
"out-of-range",
|
|
57419
|
+
"source-gone",
|
|
57420
|
+
"source-unknown",
|
|
57421
|
+
"source-call-failed"
|
|
57422
|
+
]);
|
|
57423
|
+
var FanOscillationAxisResultSchema = discriminatedUnion("status", [object({ status: literal("applied") }), object({
|
|
57424
|
+
status: literal("refused"),
|
|
57425
|
+
reason: FanOscillationAxisRefusalSchema,
|
|
57426
|
+
/** The field at fault, when one is. */
|
|
57427
|
+
field: FanOscillationAxisFieldSchema.optional(),
|
|
57428
|
+
/** One line for the operator, naming the axis, the field and the source. */
|
|
57429
|
+
detail: string(),
|
|
57430
|
+
/** Fields that DID land before the refusal (writes run in order, stop at the first failure). */
|
|
57431
|
+
applied: array(FanOscillationAxisFieldSchema)
|
|
57432
|
+
})]);
|
|
57433
|
+
var SetOscillationAxisInputSchema = object({
|
|
57434
|
+
deviceId: number$1().int().nonnegative(),
|
|
57435
|
+
axis: FanOscillationAxisNameSchema,
|
|
57436
|
+
oscillating: boolean().optional(),
|
|
57437
|
+
angle: number$1().optional(),
|
|
57438
|
+
sweepFrom: number$1().optional(),
|
|
57439
|
+
sweepTo: number$1().optional()
|
|
57440
|
+
});
|
|
57441
|
+
/** The state path of one axis field: `oscillationAxes.<axis>.<field>`. */
|
|
57442
|
+
function fanAxisPath(axis, field) {
|
|
57443
|
+
return `oscillationAxes.${axis}.${field}`;
|
|
57444
|
+
}
|
|
56762
57445
|
var FanControlStatusSchema = object({
|
|
56763
57446
|
/** Active speed as 0..100 inclusive. Null when the device has no
|
|
56764
57447
|
* speed surface (single-speed fan). */
|
|
@@ -56777,9 +57460,102 @@ var FanControlStatusSchema = object({
|
|
|
56777
57460
|
/** Oscillation toggle. Null when the device has no oscillation
|
|
56778
57461
|
* surface. */
|
|
56779
57462
|
oscillating: boolean().nullable(),
|
|
57463
|
+
/**
|
|
57464
|
+
* Per-axis oscillation (air circulators). Absent when the device has no
|
|
57465
|
+
* per-axis surface and nothing is linked in Customize.
|
|
57466
|
+
*/
|
|
57467
|
+
oscillationAxes: FanOscillationAxesSchema.optional(),
|
|
56780
57468
|
/** Ms epoch when the slice was last updated. */
|
|
56781
57469
|
lastChangedAt: number$1()
|
|
56782
57470
|
});
|
|
57471
|
+
var FAN_CUSTOMIZE_DEVICE_TYPES = [DeviceType.Fan];
|
|
57472
|
+
/** The Customize section the axis targets are listed under. */
|
|
57473
|
+
var AXES_SECTION = "oscillation-axes";
|
|
57474
|
+
function axisOscillatingTarget(axis) {
|
|
57475
|
+
return {
|
|
57476
|
+
id: `${axis}-oscillating`,
|
|
57477
|
+
cardinality: "scalar",
|
|
57478
|
+
vocabulary: {
|
|
57479
|
+
cap: "fan-control",
|
|
57480
|
+
stateField: "oscillating"
|
|
57481
|
+
},
|
|
57482
|
+
deviceTypes: FAN_CUSTOMIZE_DEVICE_TYPES,
|
|
57483
|
+
apply: "replace",
|
|
57484
|
+
section: AXES_SECTION,
|
|
57485
|
+
path: fanAxisPath(axis, "oscillating"),
|
|
57486
|
+
sources: [{
|
|
57487
|
+
kind: "switch",
|
|
57488
|
+
transforms: [{ kind: "identity" }]
|
|
57489
|
+
}],
|
|
57490
|
+
sourceReport: { status: "assignment-status" }
|
|
57491
|
+
};
|
|
57492
|
+
}
|
|
57493
|
+
/**
|
|
57494
|
+
* A numeric axis field. The `angle` link also carries the axis limits, read
|
|
57495
|
+
* from the SAME linked control's own range (`min`/`max`/`step`), so the limits
|
|
57496
|
+
* are the source's, never guessed.
|
|
57497
|
+
*/
|
|
57498
|
+
function axisNumericTarget(axis, field) {
|
|
57499
|
+
const base = {
|
|
57500
|
+
id: `${axis}-${field}`,
|
|
57501
|
+
cardinality: "scalar",
|
|
57502
|
+
vocabulary: { cap: "fan-control" },
|
|
57503
|
+
deviceTypes: FAN_CUSTOMIZE_DEVICE_TYPES,
|
|
57504
|
+
apply: "replace",
|
|
57505
|
+
section: AXES_SECTION,
|
|
57506
|
+
path: fanAxisPath(axis, field),
|
|
57507
|
+
sources: [{
|
|
57508
|
+
kind: "numeric",
|
|
57509
|
+
transforms: [{ kind: "identity" }]
|
|
57510
|
+
}],
|
|
57511
|
+
sourceReport: { status: "assignment-status" }
|
|
57512
|
+
};
|
|
57513
|
+
if (field !== "angle") return base;
|
|
57514
|
+
return {
|
|
57515
|
+
...base,
|
|
57516
|
+
companions: FAN_OSCILLATION_AXIS_LIMITS.map((limit) => ({
|
|
57517
|
+
kind: "source-field",
|
|
57518
|
+
path: fanAxisPath(axis, limit),
|
|
57519
|
+
field: limit
|
|
57520
|
+
}))
|
|
57521
|
+
};
|
|
57522
|
+
}
|
|
57523
|
+
var AXIS_TARGETS = FAN_OSCILLATION_AXES.flatMap((axis) => [
|
|
57524
|
+
axisOscillatingTarget(axis),
|
|
57525
|
+
axisNumericTarget(axis, "angle"),
|
|
57526
|
+
axisNumericTarget(axis, "sweepFrom"),
|
|
57527
|
+
axisNumericTarget(axis, "sweepTo")
|
|
57528
|
+
]);
|
|
57529
|
+
/** A select (an enum control) the operator links to the fan's `preset`. */
|
|
57530
|
+
var PRESET_TARGET = {
|
|
57531
|
+
id: "preset",
|
|
57532
|
+
cardinality: "scalar",
|
|
57533
|
+
vocabulary: { cap: "fan-control" },
|
|
57534
|
+
deviceTypes: FAN_CUSTOMIZE_DEVICE_TYPES,
|
|
57535
|
+
apply: "replace",
|
|
57536
|
+
path: "preset",
|
|
57537
|
+
sources: [{
|
|
57538
|
+
kind: "field",
|
|
57539
|
+
valueKinds: ["string"],
|
|
57540
|
+
transforms: [{ kind: "identity" }]
|
|
57541
|
+
}],
|
|
57542
|
+
sourceReport: { status: "assignment-status" }
|
|
57543
|
+
};
|
|
57544
|
+
var FAN_CONTROL_TARGETS = [...AXIS_TARGETS, PRESET_TARGET];
|
|
57545
|
+
/**
|
|
57546
|
+
* Customize (D1034): the fan already serves `fan-control` natively, so every
|
|
57547
|
+
* target is a `replace` over a field listed in `overridable` — the axis leaves
|
|
57548
|
+
* and `preset`, nothing else. Nothing is pre-linked: the operator picks each
|
|
57549
|
+
* source (D356).
|
|
57550
|
+
*/
|
|
57551
|
+
var fanControlCustomize = {
|
|
57552
|
+
version: 1,
|
|
57553
|
+
overridable: FAN_CONTROL_TARGETS.map((target) => ({
|
|
57554
|
+
path: target.path,
|
|
57555
|
+
by: target.id
|
|
57556
|
+
})),
|
|
57557
|
+
targets: FAN_CONTROL_TARGETS
|
|
57558
|
+
};
|
|
56783
57559
|
var fanControlCapability = {
|
|
56784
57560
|
name: "fan-control",
|
|
56785
57561
|
scope: "device",
|
|
@@ -56814,6 +57590,18 @@ var fanControlCapability = {
|
|
|
56814
57590
|
}), _void(), {
|
|
56815
57591
|
kind: "mutation",
|
|
56816
57592
|
auth: "protected"
|
|
57593
|
+
}),
|
|
57594
|
+
/**
|
|
57595
|
+
* Sets one oscillation axis: its switch, fixed angle and/or sweep bounds.
|
|
57596
|
+
* Fields left out are untouched. A field linked in Customize is forwarded
|
|
57597
|
+
* to its linked source (D1034); otherwise the native provider serves it;
|
|
57598
|
+
* neither → `axis-not-linked`. Ranges are checked against the axis limits
|
|
57599
|
+
* before anything is written; writes run in order and stop at the first
|
|
57600
|
+
* failure, which the result names.
|
|
57601
|
+
*/
|
|
57602
|
+
setOscillationAxis: optionalMethod(SetOscillationAxisInputSchema, FanOscillationAxisResultSchema, {
|
|
57603
|
+
kind: "mutation",
|
|
57604
|
+
auth: "protected"
|
|
56817
57605
|
})
|
|
56818
57606
|
},
|
|
56819
57607
|
status: {
|
|
@@ -56825,6 +57613,7 @@ var fanControlCapability = {
|
|
|
56825
57613
|
* sliders read `speed` for live updates.
|
|
56826
57614
|
*/
|
|
56827
57615
|
runtimeState: FanControlStatusSchema,
|
|
57616
|
+
customize: fanControlCustomize,
|
|
56828
57617
|
/**
|
|
56829
57618
|
* Runtime-state durability: **session** — as `brightness`.
|
|
56830
57619
|
*
|
|
@@ -56844,16 +57633,33 @@ var fanControlCapability = {
|
|
|
56844
57633
|
oscillating: { kind: "boolean" }
|
|
56845
57634
|
} }
|
|
56846
57635
|
};
|
|
57636
|
+
/** One axis as a preset wants it; an absent field is left as it is. */
|
|
57637
|
+
var FanPresetAxisSchema = object({
|
|
57638
|
+
oscillating: boolean().optional(),
|
|
57639
|
+
angle: number$1().optional(),
|
|
57640
|
+
sweepFrom: number$1().optional(),
|
|
57641
|
+
sweepTo: number$1().optional()
|
|
57642
|
+
});
|
|
57643
|
+
/** The fan-control state a preset sets; an absent field is left as it is. */
|
|
57644
|
+
var FanPresetStateSchema = object({
|
|
57645
|
+
speed: number$1().min(0).max(100).optional(),
|
|
57646
|
+
/** The fan's own preset mode (or the linked select's option). */
|
|
57647
|
+
preset: string().min(1).optional(),
|
|
57648
|
+
direction: FanDirectionSchema.optional(),
|
|
57649
|
+
oscillationAxes: object({
|
|
57650
|
+
horizontal: FanPresetAxisSchema.optional(),
|
|
57651
|
+
vertical: FanPresetAxisSchema.optional()
|
|
57652
|
+
}).optional()
|
|
57653
|
+
});
|
|
56847
57654
|
var FanPresetSchema = object({
|
|
56848
57655
|
/** Stable key; never reused after a delete within one device's list. */
|
|
56849
57656
|
id: string().min(1).max(64),
|
|
56850
57657
|
/** What the operator and a voice assistant call it. */
|
|
56851
57658
|
name: string().trim().min(1).max(40),
|
|
56852
|
-
|
|
57659
|
+
state: FanPresetStateSchema
|
|
56853
57660
|
});
|
|
56854
57661
|
var FanPresetsRefusalSchema = _enum([
|
|
56855
|
-
"
|
|
56856
|
-
"bad-target",
|
|
57662
|
+
"empty-state",
|
|
56857
57663
|
"duplicate-name",
|
|
56858
57664
|
"duplicate-id",
|
|
56859
57665
|
"too-many",
|
|
@@ -56865,72 +57671,55 @@ var FanPresetsSetResultSchema = discriminatedUnion("status", [object({
|
|
|
56865
57671
|
}), object({
|
|
56866
57672
|
status: literal("refused"),
|
|
56867
57673
|
reason: FanPresetsRefusalSchema,
|
|
56868
|
-
/** Names the preset
|
|
57674
|
+
/** Names the preset at fault. */
|
|
56869
57675
|
detail: string()
|
|
56870
57676
|
})]);
|
|
56871
|
-
var
|
|
56872
|
-
|
|
57677
|
+
var FanPresetFieldResultSchema = object({
|
|
57678
|
+
/** The state path: `speed`, `preset`, `direction`, `oscillationAxes.horizontal.angle`. */
|
|
57679
|
+
field: string(),
|
|
56873
57680
|
outcome: _enum([
|
|
56874
57681
|
"applied",
|
|
56875
57682
|
"failed",
|
|
56876
57683
|
"not-run",
|
|
56877
57684
|
"refused"
|
|
56878
57685
|
]),
|
|
56879
|
-
deviceId: number$1().int().nonnegative().optional(),
|
|
56880
|
-
cap: string().optional(),
|
|
56881
|
-
method: string().optional(),
|
|
56882
57686
|
/** The cause, for every outcome but `applied`. */
|
|
56883
57687
|
message: string().optional()
|
|
56884
57688
|
});
|
|
56885
57689
|
var FanPresetApplyResultSchema = object({
|
|
56886
|
-
/** `applied`: every
|
|
57690
|
+
/** `applied`: every field landed. `partial`: some did, one did not. `refused`: nothing ran. */
|
|
56887
57691
|
status: _enum([
|
|
56888
57692
|
"applied",
|
|
56889
57693
|
"partial",
|
|
56890
57694
|
"refused"
|
|
56891
57695
|
]),
|
|
56892
|
-
|
|
57696
|
+
fields: array(FanPresetFieldResultSchema)
|
|
56893
57697
|
});
|
|
56894
57698
|
var FanCaptureFieldSchema = _enum([
|
|
56895
57699
|
"speed",
|
|
56896
|
-
"oscillating",
|
|
56897
57700
|
"direction",
|
|
56898
|
-
"preset"
|
|
57701
|
+
"preset",
|
|
57702
|
+
"oscillationAxes"
|
|
56899
57703
|
]);
|
|
56900
|
-
var FanCaptureOmissionSchema = object({
|
|
56901
|
-
/** A fan field (`speed`) or `step <n>` for a sibling step. */
|
|
56902
|
-
what: string(),
|
|
56903
|
-
/** Why no value was taken: unknown reading (never stored as 0, D393), or a step that cannot be read. */
|
|
56904
|
-
reason: string()
|
|
56905
|
-
});
|
|
56906
|
-
var FanCaptureItemSchema = object({
|
|
56907
|
-
group: _enum([
|
|
56908
|
-
"fan",
|
|
56909
|
-
"oscillation",
|
|
56910
|
-
"angles"
|
|
56911
|
-
]),
|
|
56912
|
-
/** What the step sets, in the operator's words (a device name, a fan field). */
|
|
56913
|
-
label: string()
|
|
56914
|
-
});
|
|
56915
57704
|
var FanPresetCaptureResultSchema = object({
|
|
56916
|
-
/** The
|
|
56917
|
-
|
|
56918
|
-
|
|
56919
|
-
|
|
56920
|
-
|
|
57705
|
+
/** The fan's CURRENT fan-control state, as a preset; NOT saved. */
|
|
57706
|
+
state: FanPresetStateSchema,
|
|
57707
|
+
omitted: array(object({
|
|
57708
|
+
/** A state path (`speed`, `oscillationAxes.vertical.angle`). */
|
|
57709
|
+
what: string(),
|
|
57710
|
+
/** Why no value was taken: an unknown reading is never stored as 0 (D393). */
|
|
57711
|
+
reason: string()
|
|
57712
|
+
}))
|
|
56921
57713
|
});
|
|
56922
|
-
var deviceInput$
|
|
57714
|
+
var deviceInput$1 = object({ deviceId: number$1().int().nonnegative() });
|
|
56923
57715
|
DeviceType.Fan;
|
|
56924
|
-
method(deviceInput$
|
|
57716
|
+
method(deviceInput$1, array(FanPresetSchema), { auth: "protected" }), method(deviceInput$1.extend({ presets: array(FanPresetSchema).max(12) }), FanPresetsSetResultSchema, {
|
|
56925
57717
|
kind: "mutation",
|
|
56926
57718
|
auth: "admin"
|
|
56927
|
-
}), method(deviceInput$
|
|
57719
|
+
}), method(deviceInput$1.extend({ presetId: string().min(1) }), FanPresetApplyResultSchema, {
|
|
56928
57720
|
kind: "mutation",
|
|
56929
57721
|
auth: "protected"
|
|
56930
|
-
}), method(deviceInput$
|
|
56931
|
-
fields: array(FanCaptureFieldSchema).optional(),
|
|
56932
|
-
steps: array(ActionStepSchema).max(20).default([])
|
|
56933
|
-
}), FanPresetCaptureResultSchema, { auth: "protected" });
|
|
57722
|
+
}), method(deviceInput$1.extend({ fields: array(FanCaptureFieldSchema).optional() }), FanPresetCaptureResultSchema, { auth: "protected" });
|
|
56934
57723
|
/**
|
|
56935
57724
|
* Per-device feature/identity probe slice. Holds the runtime-resolved
|
|
56936
57725
|
* truth about what a device CAN do — which the kernel uses to:
|
|
@@ -57142,8 +57931,7 @@ var groupActiveCapability = {
|
|
|
57142
57931
|
DeviceType.Valve,
|
|
57143
57932
|
DeviceType.Lock,
|
|
57144
57933
|
DeviceType.MediaPlayer,
|
|
57145
|
-
DeviceType.Climate
|
|
57146
|
-
DeviceType.Thermostat
|
|
57934
|
+
DeviceType.Climate
|
|
57147
57935
|
],
|
|
57148
57936
|
methods: {},
|
|
57149
57937
|
status: {
|
|
@@ -57185,7 +57973,6 @@ var groupCommandsCapability = {
|
|
|
57185
57973
|
DeviceType.Valve,
|
|
57186
57974
|
DeviceType.Lock,
|
|
57187
57975
|
DeviceType.MediaPlayer,
|
|
57188
|
-
DeviceType.Thermostat,
|
|
57189
57976
|
DeviceType.Climate
|
|
57190
57977
|
],
|
|
57191
57978
|
methods: {},
|
|
@@ -57241,7 +58028,6 @@ var groupStatusCapability = {
|
|
|
57241
58028
|
DeviceType.Valve,
|
|
57242
58029
|
DeviceType.Lock,
|
|
57243
58030
|
DeviceType.MediaPlayer,
|
|
57244
|
-
DeviceType.Thermostat,
|
|
57245
58031
|
DeviceType.Climate
|
|
57246
58032
|
],
|
|
57247
58033
|
methods: {},
|
|
@@ -57570,7 +58356,7 @@ var heatingValveCapability = {
|
|
|
57570
58356
|
scope: "device",
|
|
57571
58357
|
deviceNative: true,
|
|
57572
58358
|
mode: "singleton",
|
|
57573
|
-
deviceTypes: [DeviceType.
|
|
58359
|
+
deviceTypes: [DeviceType.Climate],
|
|
57574
58360
|
methods: {},
|
|
57575
58361
|
status: {
|
|
57576
58362
|
schema: HeatingValveStatusSchema,
|
|
@@ -57600,13 +58386,18 @@ var heatingValveCapability = {
|
|
|
57600
58386
|
* retried in a loop - D49); a future one is re-armed.
|
|
57601
58387
|
*
|
|
57602
58388
|
* What a device may be run for, and for how long at most, is per device type
|
|
57603
|
-
* ({@link
|
|
58389
|
+
* and role ({@link timedRunLimitOf}): the maximum is a safety bound, not a preference,
|
|
57604
58390
|
* so a request above it is REFUSED with a typed reason, never clamped.
|
|
57605
58391
|
*/
|
|
57606
58392
|
var TimedRunUnitSchema = _enum(["s", "min"]);
|
|
57607
58393
|
_enum(["switch", "pet-fountain"]);
|
|
57608
|
-
|
|
57609
|
-
|
|
58394
|
+
/**
|
|
58395
|
+
* The device types `timed-run` may be offered on. A `switch` is narrowed by
|
|
58396
|
+
* role ({@link TIMED_RUN_DEVICE_ROLES}): only a plug-in heater runs on a timer.
|
|
58397
|
+
*/
|
|
58398
|
+
var TIMED_RUN_DEVICE_TYPES = [DeviceType.Switch, DeviceType.PetFountain];
|
|
58399
|
+
/** The roles that make a device of these types timed-run-able (D1033). */
|
|
58400
|
+
var TIMED_RUN_DEVICE_ROLES = { [DeviceType.Switch]: [DeviceRole.SwitchHeater] };
|
|
57610
58401
|
/** One duration, as the one-tap run asks for it. */
|
|
57611
58402
|
var TimedRunQuickRunSchema = object({
|
|
57612
58403
|
duration: number$1().positive().finite(),
|
|
@@ -57656,6 +58447,7 @@ var timedRunCapability = {
|
|
|
57656
58447
|
kind: "wrapper",
|
|
57657
58448
|
defaultActive: true,
|
|
57658
58449
|
deviceTypes: TIMED_RUN_DEVICE_TYPES,
|
|
58450
|
+
deviceRoles: TIMED_RUN_DEVICE_ROLES,
|
|
57659
58451
|
deviceConfig: { ui: {
|
|
57660
58452
|
kind: "derived-form",
|
|
57661
58453
|
builderId: "timed-run",
|
|
@@ -57816,8 +58608,8 @@ var HeroSceneSchema = object({
|
|
|
57816
58608
|
/** 0..1: volume, cover position, cycle progress. */
|
|
57817
58609
|
level: number$1().min(0).max(1).nullable()
|
|
57818
58610
|
});
|
|
57819
|
-
DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.
|
|
57820
|
-
DeviceRole.Siren, DeviceRole.Floodlight, DeviceRole.Spotlight, DeviceRole.PirSensor, DeviceRole.Chime, DeviceRole.Autotrack, DeviceRole.Nightvision, DeviceRole.PrivacyMask, DeviceRole.Doorbell, DeviceRole.CoverBlind, DeviceRole.CoverShutter, DeviceRole.CoverAwning, DeviceRole.CoverCurtain, DeviceRole.CoverGarage, DeviceRole.CoverGate, DeviceRole.CoverDoor, DeviceRole.CoverWindow, DeviceRole.BinaryHelper, DeviceRole.Outlet, DeviceRole.MotionSensor, DeviceRole.ContactSensor, DeviceRole.LeakSensor, DeviceRole.SmokeSensor, DeviceRole.COSensor, DeviceRole.GasSensor, DeviceRole.TamperSensor, DeviceRole.VibrationSensor, DeviceRole.ConnectivitySensor, DeviceRole.SoundSensor, DeviceRole.BinarySensor, DeviceRole.TemperatureSensor, DeviceRole.HumiditySensor, DeviceRole.AmbientLightSensor, DeviceRole.PressureSensor, DeviceRole.WindSensor, DeviceRole.RainSensor, DeviceRole.UvSensor, DeviceRole.SolarRadiationSensor, DeviceRole.SoilMoistureSensor, DeviceRole.PowerSensor, DeviceRole.EnergySensor, DeviceRole.VoltageSensor, DeviceRole.CurrentSensor, DeviceRole.AirQualitySensor, DeviceRole.BatterySensor, DeviceRole.NumericSensor, DeviceRole.EnumSensor, DeviceRole.DateTimeSensor, DeviceRole.GenericSensor, DeviceRole.NumericControl, DeviceRole.SelectControl, DeviceRole.TextControl, DeviceRole.DateTimeControl, DeviceRole.MobilePushNotifier, DeviceRole.MessagingNotifier, DeviceRole.EmailNotifier, DeviceRole.GenericNotifier, DeviceRole.IrTransmitter;
|
|
58611
|
+
DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
|
|
58612
|
+
DeviceRole.Siren, DeviceRole.Floodlight, DeviceRole.Spotlight, DeviceRole.PirSensor, DeviceRole.Chime, DeviceRole.Autotrack, DeviceRole.Nightvision, DeviceRole.PrivacyMask, DeviceRole.Doorbell, DeviceRole.CoverBlind, DeviceRole.CoverShutter, DeviceRole.CoverAwning, DeviceRole.CoverCurtain, DeviceRole.CoverGarage, DeviceRole.CoverGate, DeviceRole.CoverDoor, DeviceRole.CoverWindow, DeviceRole.BinaryHelper, DeviceRole.Outlet, DeviceRole.SwitchHeater, DeviceRole.SwitchFan, DeviceRole.ClimateAc, DeviceRole.ClimateThermostat, DeviceRole.ClimateFloor, DeviceRole.ClimateRadiator, DeviceRole.ClimateHeater, DeviceRole.ClimateHeatPumpCircuit, DeviceRole.MotionSensor, DeviceRole.ContactSensor, DeviceRole.LeakSensor, DeviceRole.SmokeSensor, DeviceRole.COSensor, DeviceRole.GasSensor, DeviceRole.TamperSensor, DeviceRole.VibrationSensor, DeviceRole.ConnectivitySensor, DeviceRole.SoundSensor, DeviceRole.BinarySensor, DeviceRole.TemperatureSensor, DeviceRole.HumiditySensor, DeviceRole.AmbientLightSensor, DeviceRole.PressureSensor, DeviceRole.WindSensor, DeviceRole.RainSensor, DeviceRole.UvSensor, DeviceRole.SolarRadiationSensor, DeviceRole.SoilMoistureSensor, DeviceRole.PowerSensor, DeviceRole.EnergySensor, DeviceRole.VoltageSensor, DeviceRole.CurrentSensor, DeviceRole.AirQualitySensor, DeviceRole.BatterySensor, DeviceRole.NumericSensor, DeviceRole.EnumSensor, DeviceRole.DateTimeSensor, DeviceRole.GenericSensor, DeviceRole.NumericControl, DeviceRole.SelectControl, DeviceRole.TextControl, DeviceRole.DateTimeControl, DeviceRole.MobilePushNotifier, DeviceRole.MessagingNotifier, DeviceRole.EmailNotifier, DeviceRole.GenericNotifier, DeviceRole.IrTransmitter;
|
|
57821
58613
|
/** A string in both languages, as the cap vocabulary writes it (D920): no `t()` in the model. */
|
|
57822
58614
|
var HeroTextSchema = object({
|
|
57823
58615
|
en: string(),
|
|
@@ -57883,13 +58675,11 @@ var HeroConfirmSchema = _enum([
|
|
|
57883
58675
|
"press"
|
|
57884
58676
|
]);
|
|
57885
58677
|
var HeroActionSchema = object({
|
|
57886
|
-
/** Unique within the model; the `gated` list and the specs name it. */
|
|
57887
58678
|
id: string(),
|
|
57888
58679
|
label: HeroTextSchema,
|
|
57889
58680
|
icon: IconKeySchema,
|
|
57890
58681
|
call: HeroCallSchema,
|
|
57891
58682
|
emphasis: _enum(["primary", "normal"]),
|
|
57892
|
-
/** Ask before running it. */
|
|
57893
58683
|
confirm: HeroConfirmSchema.nullable()
|
|
57894
58684
|
});
|
|
57895
58685
|
var HeroControlSchema = discriminatedUnion("kind", [
|
|
@@ -57972,9 +58762,16 @@ var HeroBadgeSchema = object({
|
|
|
57972
58762
|
value: number$1().nullable(),
|
|
57973
58763
|
label: HeroTextSchema
|
|
57974
58764
|
});
|
|
57975
|
-
var HeroRemoteSlotSchema = object({
|
|
57976
|
-
/** Saved commands; `null` = not known yet (the button still shows). */
|
|
57977
|
-
commands: number$1().int().nullable()
|
|
58765
|
+
var HeroRemoteSlotSchema = object({
|
|
58766
|
+
/** Saved commands; `null` = not known yet (the button still shows). */
|
|
58767
|
+
commands: number$1().int().nullable(),
|
|
58768
|
+
/**
|
|
58769
|
+
* The control a list row draws as the "open the remote" icon. Recognised by its id
|
|
58770
|
+
* ({@link REMOTE_OPEN_ACTION_ID}); it changes no state, so a client opens the remote
|
|
58771
|
+
* instead of running `call`. Absent on a model from an older server.
|
|
58772
|
+
*/
|
|
58773
|
+
open: HeroActionSchema.optional()
|
|
58774
|
+
});
|
|
57978
58775
|
var HeroConsumablesSlotSchema = object({
|
|
57979
58776
|
items: array(object({
|
|
57980
58777
|
key: string(),
|
|
@@ -58142,6 +58939,8 @@ var HeroModelSchema = object({
|
|
|
58142
58939
|
* the FIRST row that has any (a later row's control is never promoted). When
|
|
58143
58940
|
* the model lists that part in `gated`, the row has no inline control: the
|
|
58144
58941
|
* projection never falls through to another control the caller could use.
|
|
58942
|
+
* - a climate or thermostat row's primary is its `climate.mode` control (no power
|
|
58943
|
+
* toggle: "off" is a mode); when the model gates it the rule above applies as before;
|
|
58145
58944
|
* - a timed run, when the model carries the slot, sits beside that primary:
|
|
58146
58945
|
* Run while idle, Stop while armed. Gating the slot hides the control.
|
|
58147
58946
|
* - the status line is the head (what plays, else the state) plus at most
|
|
@@ -58149,6 +58948,13 @@ var HeroModelSchema = object({
|
|
|
58149
58948
|
* time left on an armed timed run;
|
|
58150
58949
|
* - the trailing fact is the first of power, battery, consumables.
|
|
58151
58950
|
*
|
|
58951
|
+
* - `extras` are the further inline controls of the types that carry more than one
|
|
58952
|
+
* ({@link ROW_EXTRAS_BY_TYPE}): climate setpoint, a robot's return to base,
|
|
58953
|
+
* a player's volume and remote. Each is a control the model already has, named by id,
|
|
58954
|
+
* in that order and at most {@link ROW_EXTRAS_MAX}; one the model lacks, or lists in
|
|
58955
|
+
* `gated`, is absent, and the one `primary` already is never repeats. Empty = the
|
|
58956
|
+
* row of an older server: `primary` alone.
|
|
58957
|
+
*
|
|
58152
58958
|
* `projectDeviceRow` is pure and computes nothing: it selects from a
|
|
58153
58959
|
* {@link HeroModel}. A row that needed a value the hero lacks would mean the
|
|
58154
58960
|
* model is missing it, so the fix goes in the model.
|
|
@@ -58180,8 +58986,16 @@ var HeroRowModelSchema = object({
|
|
|
58180
58986
|
* while armed. `null` when there is no slot, or the caller may not act.
|
|
58181
58987
|
*/
|
|
58182
58988
|
timedRun: HeroRowTimedRunSchema.nullable(),
|
|
58989
|
+
/**
|
|
58990
|
+
* The further inline controls after `primary`, in order (see the header). The remote
|
|
58991
|
+
* is an `action` with id `remote.open`: the client opens the remote, it does not run
|
|
58992
|
+
* the call. Absent or empty = `primary` alone.
|
|
58993
|
+
*/
|
|
58994
|
+
extras: array(HeroControlSchema).max(2).optional(),
|
|
58183
58995
|
trailing: HeroFactSchema.nullable()
|
|
58184
58996
|
});
|
|
58997
|
+
DeviceType.Climate;
|
|
58998
|
+
DeviceType.Climate, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer;
|
|
58185
58999
|
/**
|
|
58186
59000
|
* hero-layout — what a device's hero says (D1023). Read-only: the FIXED
|
|
58187
59001
|
* layouts live in `@camstack/types` and nothing here stores or edits one.
|
|
@@ -58213,7 +59027,10 @@ method(heroInput, HeroModelSchema.nullable(), {
|
|
|
58213
59027
|
}), method(rowModelsInput, array(HeroRowModelSchema.nullable()), {
|
|
58214
59028
|
auth: "protected",
|
|
58215
59029
|
role: "viewer"
|
|
58216
|
-
}), method(object({
|
|
59030
|
+
}), method(object({
|
|
59031
|
+
deviceType: string(),
|
|
59032
|
+
role: string().nullable().optional()
|
|
59033
|
+
}), HeroLayoutRowsSchema, {
|
|
58217
59034
|
auth: "protected",
|
|
58218
59035
|
role: "viewer"
|
|
58219
59036
|
});
|
|
@@ -58260,6 +59077,11 @@ var humidifierCapability = {
|
|
|
58260
59077
|
deviceNative: true,
|
|
58261
59078
|
mode: "singleton",
|
|
58262
59079
|
deviceTypes: [DeviceType.Humidifier],
|
|
59080
|
+
power: {
|
|
59081
|
+
kind: "field",
|
|
59082
|
+
field: "on",
|
|
59083
|
+
set: "setOn"
|
|
59084
|
+
},
|
|
58263
59085
|
methods: {
|
|
58264
59086
|
setOn: method(object({
|
|
58265
59087
|
deviceId: number$1().int().nonnegative(),
|
|
@@ -58333,7 +59155,7 @@ var humiditySensorCapability = {
|
|
|
58333
59155
|
scope: "device",
|
|
58334
59156
|
deviceNative: true,
|
|
58335
59157
|
mode: "singleton",
|
|
58336
|
-
deviceTypes: [DeviceType.Sensor, DeviceType.
|
|
59158
|
+
deviceTypes: [DeviceType.Sensor, DeviceType.Climate],
|
|
58337
59159
|
methods: {},
|
|
58338
59160
|
status: {
|
|
58339
59161
|
schema: HumiditySensorStatusSchema,
|
|
@@ -58627,6 +59449,12 @@ var IntegrationLiteSchema = object({
|
|
|
58627
59449
|
var AvailableIntegrationTypeSchema = object({
|
|
58628
59450
|
addonId: string(),
|
|
58629
59451
|
name: string(),
|
|
59452
|
+
/**
|
|
59453
|
+
* Product name a new integration is prefilled from (`<label> - Home`).
|
|
59454
|
+
* Absent: the client uses `name`. Set when the catalog title is not the
|
|
59455
|
+
* product (Bosch SHC, MQTT).
|
|
59456
|
+
*/
|
|
59457
|
+
providerLabel: string().optional(),
|
|
58630
59458
|
description: string(),
|
|
58631
59459
|
iconUrl: string().nullable(),
|
|
58632
59460
|
color: string(),
|
|
@@ -71515,7 +72343,18 @@ var OauthIntegrationDescriptorSchema = object({
|
|
|
71515
72343
|
* The value is baked into the authorization code at `/authorize` and travels
|
|
71516
72344
|
* on the tokens, so editing this field changes FUTURE links only.
|
|
71517
72345
|
*/
|
|
71518
|
-
refreshTokenTtlSec: union([number$1().int().positive(), literal("never")]).optional()
|
|
72346
|
+
refreshTokenTtlSec: union([number$1().int().positive(), literal("never")]).optional(),
|
|
72347
|
+
/**
|
|
72348
|
+
* The integration instances an account can be linked to (planning#415),
|
|
72349
|
+
* beside the default one. `/api/oauth2/authorize?instance=<id>` is accepted
|
|
72350
|
+
* only for an id listed here; with no `instance=` the link is made to the
|
|
72351
|
+
* default instance, which is never listed and always accepted — it is the
|
|
72352
|
+
* address third parties already hold. Omit for a single-instance integration.
|
|
72353
|
+
*/
|
|
72354
|
+
instances: array(object({
|
|
72355
|
+
id: string(),
|
|
72356
|
+
displayName: string()
|
|
72357
|
+
})).optional()
|
|
71519
72358
|
});
|
|
71520
72359
|
method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" });
|
|
71521
72360
|
/**
|
|
@@ -71938,9 +72777,13 @@ var petFeederCapability = {
|
|
|
71938
72777
|
*/
|
|
71939
72778
|
durability: "session",
|
|
71940
72779
|
/**
|
|
71941
|
-
* History (D850): recorded series.
|
|
72780
|
+
* History (D850): recorded series. `feeding` / `eating` are the meals the
|
|
72781
|
+
* device hero's "Meals" button names — change-only booleans, so a dispense
|
|
72782
|
+
* or a pet at the bowl is one span on the history.
|
|
71942
72783
|
*/
|
|
71943
72784
|
history: { fields: {
|
|
72785
|
+
feeding: { kind: "boolean" },
|
|
72786
|
+
eating: { kind: "boolean" },
|
|
71944
72787
|
foodLevel: { kind: "gauge" },
|
|
71945
72788
|
bowlFood: { kind: "gauge" },
|
|
71946
72789
|
food1: { kind: "gauge" },
|
|
@@ -72001,6 +72844,11 @@ var petFountainCapability = {
|
|
|
72001
72844
|
deviceNative: true,
|
|
72002
72845
|
mode: "singleton",
|
|
72003
72846
|
deviceTypes: [DeviceType.PetFountain],
|
|
72847
|
+
power: {
|
|
72848
|
+
kind: "field",
|
|
72849
|
+
field: "pumpRunning",
|
|
72850
|
+
set: "setPump"
|
|
72851
|
+
},
|
|
72004
72852
|
methods: {
|
|
72005
72853
|
/** Start or stop the pump. */
|
|
72006
72854
|
setPump: method(object({
|
|
@@ -73576,7 +74424,6 @@ var POWER_METER_DEVICE_TYPES = [
|
|
|
73576
74424
|
DeviceType.Light,
|
|
73577
74425
|
DeviceType.Fan,
|
|
73578
74426
|
DeviceType.Climate,
|
|
73579
|
-
DeviceType.Thermostat,
|
|
73580
74427
|
DeviceType.Humidifier,
|
|
73581
74428
|
DeviceType.WaterHeater,
|
|
73582
74429
|
DeviceType.MediaPlayer,
|
|
@@ -73585,8 +74432,7 @@ var POWER_METER_DEVICE_TYPES = [
|
|
|
73585
74432
|
DeviceType.Dishwasher,
|
|
73586
74433
|
DeviceType.Vacuum,
|
|
73587
74434
|
DeviceType.LawnMower,
|
|
73588
|
-
DeviceType.PetFeeder
|
|
73589
|
-
DeviceType.Heater
|
|
74435
|
+
DeviceType.PetFeeder
|
|
73590
74436
|
];
|
|
73591
74437
|
/** Everything but a bare `Sensor`: a sensor that takes its reading from another sensor adds nothing. */
|
|
73592
74438
|
var POWER_METER_CUSTOMIZE_DEVICE_TYPES = POWER_METER_DEVICE_TYPES.filter((type) => type !== DeviceType.Sensor);
|
|
@@ -73699,118 +74545,6 @@ var powerMeterCapability = {
|
|
|
73699
74545
|
volatileStateFields: ["lastFetchedAt"],
|
|
73700
74546
|
customize: powerMeterCustomize
|
|
73701
74547
|
};
|
|
73702
|
-
var PowerSourceRefusalReasonSchema = _enum([
|
|
73703
|
-
"no-power-source",
|
|
73704
|
-
"switch-missing",
|
|
73705
|
-
"switch-offline",
|
|
73706
|
-
"switch-unknown",
|
|
73707
|
-
"restart-in-progress",
|
|
73708
|
-
"switch-call-failed"
|
|
73709
|
-
]);
|
|
73710
|
-
var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
|
|
73711
|
-
accepted: literal(false),
|
|
73712
|
-
reason: PowerSourceRefusalReasonSchema,
|
|
73713
|
-
/** One line for the operator, naming the switch. */
|
|
73714
|
-
message: string()
|
|
73715
|
-
})]);
|
|
73716
|
-
var PowerSourceInfoSchema = object({
|
|
73717
|
-
/** False when the device has no power source linked; the rest is then empty. */
|
|
73718
|
-
linked: boolean(),
|
|
73719
|
-
switchDeviceId: number$1().nullable(),
|
|
73720
|
-
switchName: string().nullable(),
|
|
73721
|
-
/** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
|
|
73722
|
-
switchOn: boolean().nullable(),
|
|
73723
|
-
switchOnline: boolean().nullable(),
|
|
73724
|
-
restartInProgress: boolean(),
|
|
73725
|
-
offSeconds: number$1().int()
|
|
73726
|
-
});
|
|
73727
|
-
/** The composed slice: the switch state the link mirrors, plus the stored off time. */
|
|
73728
|
-
var PowerSourceStateSchema = object({
|
|
73729
|
-
/** Null while the switch has not reported: unknown is never `false` (D393). */
|
|
73730
|
-
switchOn: boolean().nullable(),
|
|
73731
|
-
offSeconds: number$1().int(),
|
|
73732
|
-
lastFetchedAt: number$1()
|
|
73733
|
-
});
|
|
73734
|
-
var POWER_SOURCE_DEVICE_TYPES = [
|
|
73735
|
-
DeviceType.Gateway,
|
|
73736
|
-
DeviceType.Hub,
|
|
73737
|
-
DeviceType.Camera,
|
|
73738
|
-
DeviceType.Sensor,
|
|
73739
|
-
DeviceType.MediaPlayer,
|
|
73740
|
-
DeviceType.Vacuum,
|
|
73741
|
-
DeviceType.Generic,
|
|
73742
|
-
DeviceType.Washer,
|
|
73743
|
-
DeviceType.Dryer,
|
|
73744
|
-
DeviceType.Dishwasher,
|
|
73745
|
-
DeviceType.PetFountain,
|
|
73746
|
-
DeviceType.Fan,
|
|
73747
|
-
DeviceType.Humidifier,
|
|
73748
|
-
DeviceType.Climate,
|
|
73749
|
-
DeviceType.WaterHeater
|
|
73750
|
-
];
|
|
73751
|
-
var powerSourceCustomize = {
|
|
73752
|
-
version: 1,
|
|
73753
|
-
targets: [{
|
|
73754
|
-
id: "switch",
|
|
73755
|
-
cardinality: "scalar",
|
|
73756
|
-
vocabulary: {
|
|
73757
|
-
cap: "power-source",
|
|
73758
|
-
stateField: "switchOn"
|
|
73759
|
-
},
|
|
73760
|
-
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
73761
|
-
apply: "add",
|
|
73762
|
-
path: "switchOn",
|
|
73763
|
-
sources: [{
|
|
73764
|
-
kind: "switch",
|
|
73765
|
-
transforms: [{ kind: "identity" }]
|
|
73766
|
-
}],
|
|
73767
|
-
companions: [{
|
|
73768
|
-
kind: "constant",
|
|
73769
|
-
path: "offSeconds",
|
|
73770
|
-
value: 10,
|
|
73771
|
-
when: "add",
|
|
73772
|
-
formerValues: [0]
|
|
73773
|
-
}, {
|
|
73774
|
-
kind: "clock",
|
|
73775
|
-
path: "lastFetchedAt",
|
|
73776
|
-
when: "always"
|
|
73777
|
-
}],
|
|
73778
|
-
sourceReport: { status: "assignment-status" }
|
|
73779
|
-
}]
|
|
73780
|
-
};
|
|
73781
|
-
var deviceInput$1 = object({ deviceId: number$1().int().nonnegative() });
|
|
73782
|
-
var powerSourceCapability = {
|
|
73783
|
-
name: "power-source",
|
|
73784
|
-
scope: "device",
|
|
73785
|
-
mode: "singleton",
|
|
73786
|
-
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
73787
|
-
methods: {
|
|
73788
|
-
getStatus: systemMethod(deviceInput$1, PowerSourceInfoSchema, { auth: "protected" }),
|
|
73789
|
-
powerOn: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
73790
|
-
kind: "mutation",
|
|
73791
|
-
auth: "protected"
|
|
73792
|
-
}),
|
|
73793
|
-
powerOff: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
73794
|
-
kind: "mutation",
|
|
73795
|
-
auth: "protected"
|
|
73796
|
-
}),
|
|
73797
|
-
forceRestart: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
73798
|
-
kind: "mutation",
|
|
73799
|
-
auth: "protected"
|
|
73800
|
-
})
|
|
73801
|
-
},
|
|
73802
|
-
runtimeState: PowerSourceStateSchema,
|
|
73803
|
-
customize: powerSourceCustomize,
|
|
73804
|
-
/**
|
|
73805
|
-
* Runtime-state durability: **session** — mirrored from the linked switch on
|
|
73806
|
-
* its next report; a restored value would outlive the reading it names.
|
|
73807
|
-
*
|
|
73808
|
-
* See `RuntimeStateDurability`. Enforced by
|
|
73809
|
-
* `scripts/check-runtime-state-durability.ts`.
|
|
73810
|
-
*/
|
|
73811
|
-
durability: "session",
|
|
73812
|
-
history: "none"
|
|
73813
|
-
};
|
|
73814
74548
|
/**
|
|
73815
74549
|
* Presence cap. Models HA `person.*` and `device_tracker.*` entities
|
|
73816
74550
|
* on `DeviceType.Presence`. Read-only — no setters: presence is
|
|
@@ -77277,9 +78011,7 @@ var RUNNING_CUSTOMIZE_DEVICE_TYPES = [
|
|
|
77277
78011
|
DeviceType.Humidifier,
|
|
77278
78012
|
DeviceType.Fan,
|
|
77279
78013
|
DeviceType.Climate,
|
|
77280
|
-
DeviceType.
|
|
77281
|
-
DeviceType.PetFeeder,
|
|
77282
|
-
DeviceType.Heater
|
|
78014
|
+
DeviceType.PetFeeder
|
|
77283
78015
|
];
|
|
77284
78016
|
var runningStateCapability = {
|
|
77285
78017
|
name: "running-state",
|
|
@@ -78551,6 +79283,13 @@ var SsoBridgeClaimsSchema = object({
|
|
|
78551
79283
|
/** OAuth authorization-code binding — set only on `oauth-code` tokens. */
|
|
78552
79284
|
redirectUri: string().optional(),
|
|
78553
79285
|
integrationId: string().optional(),
|
|
79286
|
+
/**
|
|
79287
|
+
* The integration INSTANCE an OAuth link was made for (planning#415). Set on
|
|
79288
|
+
* `oauth-code`, `oauth-access` and `oauth-refresh` tokens of a link made
|
|
79289
|
+
* with `instance=`; absent on a link to the default instance. A cloud relay
|
|
79290
|
+
* reads it off the access token to route; the hub answers from the session.
|
|
79291
|
+
*/
|
|
79292
|
+
instanceId: string().optional(),
|
|
78554
79293
|
/** JWT ID — unique per issued code; consumed-set enforces single-use. */
|
|
78555
79294
|
jti: string().optional(),
|
|
78556
79295
|
/** PKCE S256 challenge — set only on `oauth-code` tokens issued to a public
|
|
@@ -79367,8 +80106,7 @@ var switchCapability = {
|
|
|
79367
80106
|
DeviceType.Switch,
|
|
79368
80107
|
DeviceType.Siren,
|
|
79369
80108
|
DeviceType.Light,
|
|
79370
|
-
DeviceType.Fan
|
|
79371
|
-
DeviceType.Heater
|
|
80109
|
+
DeviceType.Fan
|
|
79372
80110
|
],
|
|
79373
80111
|
methods: { setState: method(object({
|
|
79374
80112
|
deviceId: number$1(),
|
|
@@ -80062,7 +80800,7 @@ var temperatureSensorCapability = {
|
|
|
80062
80800
|
scope: "device",
|
|
80063
80801
|
deviceNative: true,
|
|
80064
80802
|
mode: "singleton",
|
|
80065
|
-
deviceTypes: [DeviceType.Sensor, DeviceType.
|
|
80803
|
+
deviceTypes: [DeviceType.Sensor, DeviceType.Climate],
|
|
80066
80804
|
methods: {},
|
|
80067
80805
|
status: {
|
|
80068
80806
|
schema: TemperatureSensorStatusSchema,
|
|
@@ -80930,6 +81668,8 @@ var OauthSessionSummarySchema = object({
|
|
|
80930
81668
|
userId: string(),
|
|
80931
81669
|
username: string(),
|
|
80932
81670
|
integrationId: string(),
|
|
81671
|
+
/** The instance the link was made for; `null` = the default instance. */
|
|
81672
|
+
instanceId: string().nullable().optional(),
|
|
80933
81673
|
scopes: array(TokenScopeSchema),
|
|
80934
81674
|
createdAt: number$1(),
|
|
80935
81675
|
lastUsedAt: number$1(),
|
|
@@ -81029,6 +81769,9 @@ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(_void(), ob
|
|
|
81029
81769
|
access: "view"
|
|
81030
81770
|
}), method(object({
|
|
81031
81771
|
integrationId: string(),
|
|
81772
|
+
/** The integration instance the link is for. Omit for the default
|
|
81773
|
+
* instance — never pass the addon id to mean it. */
|
|
81774
|
+
instanceId: string().optional(),
|
|
81032
81775
|
userId: string(),
|
|
81033
81776
|
username: string(),
|
|
81034
81777
|
scopes: array(TokenScopeSchema),
|
|
@@ -81067,7 +81810,13 @@ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(_void(), ob
|
|
|
81067
81810
|
}), method(object({ token: string() }), object({
|
|
81068
81811
|
userId: string(),
|
|
81069
81812
|
username: string(),
|
|
81070
|
-
scopes: array(TokenScopeSchema)
|
|
81813
|
+
scopes: array(TokenScopeSchema),
|
|
81814
|
+
/** The instance the link was made for, read from the SESSION.
|
|
81815
|
+
* Absent = the default instance. `oauthLinkServesInstance` is the
|
|
81816
|
+
* helper a per-instance route calls to compare it with the instance
|
|
81817
|
+
* of its path; no route declares `perInstance` yet (the Alexa
|
|
81818
|
+
* per-instance route, T3, is the first consumer). */
|
|
81819
|
+
instanceId: string().optional()
|
|
81071
81820
|
}).nullable(), { access: "view" }), method(object({ userId: string() }), array(ExternalIdentitySchema), { auth: "admin" }), method(ExternalIdentityKeySchema, object({
|
|
81072
81821
|
userId: string(),
|
|
81073
81822
|
issuer: string()
|
|
@@ -81709,6 +82458,13 @@ var waterHeaterCapability = {
|
|
|
81709
82458
|
deviceNative: true,
|
|
81710
82459
|
mode: "singleton",
|
|
81711
82460
|
deviceTypes: [DeviceType.WaterHeater],
|
|
82461
|
+
power: {
|
|
82462
|
+
kind: "mode",
|
|
82463
|
+
field: "operationMode",
|
|
82464
|
+
offValue: "off",
|
|
82465
|
+
set: "setOperationMode",
|
|
82466
|
+
lastField: "lastActiveMode"
|
|
82467
|
+
},
|
|
81712
82468
|
methods: {
|
|
81713
82469
|
setTargetTemp: method(object({
|
|
81714
82470
|
deviceId: number$1().int().nonnegative(),
|
|
@@ -83367,7 +84123,6 @@ var DEVICE_LOCAL_STATE_CAPS = {
|
|
|
83367
84123
|
control: controlCapability,
|
|
83368
84124
|
cover: coverCapability,
|
|
83369
84125
|
dayNight: dayNightCapability,
|
|
83370
|
-
deviceDiscovery: deviceDiscoveryCapability,
|
|
83371
84126
|
deviceStatus: deviceStatusCapability,
|
|
83372
84127
|
dishwasher: dishwasherCapability,
|
|
83373
84128
|
doNotDisturb: doNotDisturbCapability,
|
|
@@ -84462,15 +85217,16 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
84462
85217
|
});
|
|
84463
85218
|
const meta = await this.ctx.api.deviceManager.loadMeta.query({ deviceId: id });
|
|
84464
85219
|
if (meta === null) throw new Error(`${this.providerName}: no persisted meta for "${stableId}" (id ${id}) — cannot reload`);
|
|
84465
|
-
const
|
|
84466
|
-
|
|
85220
|
+
const Class = this.deviceClassForRow({
|
|
85221
|
+
type: meta.type,
|
|
85222
|
+
stableId
|
|
85223
|
+
});
|
|
84467
85224
|
if (!Class) throw new Error(`${this.providerName}: no device class for type "${meta.type}" — "${stableId}" is an accessory child; reload its parent instead`);
|
|
84468
85225
|
await devices.create(stableId, Class, {}, meta.parentDeviceId ?? null);
|
|
84469
85226
|
const rows = await this.ctx.api.deviceManager.listPersistedByAddon.query({ addonId: this.addonId });
|
|
84470
85227
|
for (const row of rows) {
|
|
84471
85228
|
if (row.parentDeviceId !== id) continue;
|
|
84472
|
-
const
|
|
84473
|
-
const ChildClass = childType !== void 0 ? this.deviceClasses[childType] : void 0;
|
|
85229
|
+
const ChildClass = this.deviceClassForRow(row);
|
|
84474
85230
|
if (!ChildClass) continue;
|
|
84475
85231
|
try {
|
|
84476
85232
|
await devices.create(row.stableId, ChildClass, {}, id);
|
|
@@ -84497,6 +85253,16 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
84497
85253
|
return { deviceId: id };
|
|
84498
85254
|
}
|
|
84499
85255
|
/**
|
|
85256
|
+
* The class a PERSISTED row is rebuilt with. Default: by type, from
|
|
85257
|
+
* `deviceClasses`. A provider whose distinct devices share one type (D1033:
|
|
85258
|
+
* a radiator valve and a room climate are both `climate`) overrides this
|
|
85259
|
+
* and tells them apart by the row's stable id or config.
|
|
85260
|
+
*/
|
|
85261
|
+
deviceClassForRow(row) {
|
|
85262
|
+
const type = Object.values(DeviceType).find((t) => t === row.type);
|
|
85263
|
+
return type !== void 0 ? this.deviceClasses[type] : void 0;
|
|
85264
|
+
}
|
|
85265
|
+
/**
|
|
84500
85266
|
* Restore devices from persisted state. Two-pass:
|
|
84501
85267
|
*
|
|
84502
85268
|
* 1. **Top-level pass** — invokes `kernel.devices.create()` for every
|
|
@@ -84557,7 +85323,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
84557
85323
|
restored.add(saved.id);
|
|
84558
85324
|
return;
|
|
84559
85325
|
}
|
|
84560
|
-
const Class = this.
|
|
85326
|
+
const Class = this.deviceClassForRow(saved);
|
|
84561
85327
|
if (!Class) throw new Error(`no device class registered for type "${saved.type}"`);
|
|
84562
85328
|
if (saved.parentDeviceId !== null && !restored.has(saved.parentDeviceId)) throw new Error(`parent device ${saved.parentDeviceId} not restored`);
|
|
84563
85329
|
await this.healSavedConfig(saved, savedDevices);
|
|
@@ -84574,7 +85340,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
84574
85340
|
};
|
|
84575
85341
|
const topLevel = savedDevices.filter((saved) => saved.parentDeviceId === null);
|
|
84576
85342
|
const restoreOne = async (saved) => {
|
|
84577
|
-
if (!this.
|
|
85343
|
+
if (!this.deviceClassForRow(saved)) {
|
|
84578
85344
|
this.ctx.logger.warn("No device class registered for restored type — skipping", {
|
|
84579
85345
|
tags: {
|
|
84580
85346
|
deviceId: saved.id,
|
|
@@ -84613,7 +85379,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
84613
85379
|
const failedTopLevelIds = new Set(failures.map((failure) => failure.saved.id));
|
|
84614
85380
|
const childRows = savedDevices.filter((s) => s.parentDeviceId !== null);
|
|
84615
85381
|
for (const saved of childRows) {
|
|
84616
|
-
if (!this.
|
|
85382
|
+
if (!this.deviceClassForRow(saved)) continue;
|
|
84617
85383
|
if (saved.parentDeviceId === null) continue;
|
|
84618
85384
|
if (restored.has(saved.parentDeviceId)) {
|
|
84619
85385
|
try {
|
|
@@ -84667,7 +85433,9 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
84667
85433
|
}
|
|
84668
85434
|
};
|
|
84669
85435
|
DeviceType.HeatPump;
|
|
84670
|
-
DeviceType.
|
|
85436
|
+
DeviceType.Climate;
|
|
85437
|
+
DeviceRole.ClimateThermostat, DeviceRole.ClimateAc, DeviceRole.ClimateRadiator, DeviceRole.ClimateFloor, DeviceRole.ClimateHeater, DeviceRole.ClimateHeatPumpCircuit;
|
|
85438
|
+
DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
|
|
84671
85439
|
new Set(Object.values(DeviceType));
|
|
84672
85440
|
/** Parses the raw slice with the cap's own schema; `undefined` when it does not parse. */
|
|
84673
85441
|
function contribution(cap, schema, build) {
|
|
@@ -86673,7 +87441,7 @@ var HEAT_PUMP_FAMILY_CONTRIBUTIONS = { "heat-pump-status": contribution("heat-pu
|
|
|
86673
87441
|
...Object.values(CLIMATE_FAMILY_CONTRIBUTIONS),
|
|
86674
87442
|
...Object.values(HEAT_PUMP_FAMILY_CONTRIBUTIONS)
|
|
86675
87443
|
];
|
|
86676
|
-
DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.
|
|
87444
|
+
DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
|
|
86677
87445
|
/**
|
|
86678
87446
|
* Who looks like what (D1023). One look per {@link DeviceType}, refined by a
|
|
86679
87447
|
* {@link DeviceRole} where the role changes how the device reads (a leak
|
|
@@ -87317,9 +88085,9 @@ lookOf({
|
|
|
87317
88085
|
idle: INFO,
|
|
87318
88086
|
standby: MUTED
|
|
87319
88087
|
});
|
|
87320
|
-
DeviceType.Climate
|
|
87321
|
-
DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.
|
|
87322
|
-
DeviceRole.Siren, DeviceRole.Floodlight, DeviceRole.Spotlight, DeviceRole.PirSensor, DeviceRole.Chime, DeviceRole.Autotrack, DeviceRole.Nightvision, DeviceRole.PrivacyMask, DeviceRole.Doorbell, DeviceRole.CoverBlind, DeviceRole.CoverShutter, DeviceRole.CoverAwning, DeviceRole.CoverCurtain, DeviceRole.CoverGarage, DeviceRole.CoverGate, DeviceRole.CoverDoor, DeviceRole.CoverWindow, DeviceRole.BinaryHelper, DeviceRole.Outlet, DeviceRole.MotionSensor, DeviceRole.ContactSensor, DeviceRole.LeakSensor, DeviceRole.SmokeSensor, DeviceRole.COSensor, DeviceRole.GasSensor, DeviceRole.TamperSensor, DeviceRole.VibrationSensor, DeviceRole.ConnectivitySensor, DeviceRole.SoundSensor, DeviceRole.BinarySensor, DeviceRole.TemperatureSensor, DeviceRole.HumiditySensor, DeviceRole.AmbientLightSensor, DeviceRole.PressureSensor, DeviceRole.WindSensor, DeviceRole.RainSensor, DeviceRole.UvSensor, DeviceRole.SolarRadiationSensor, DeviceRole.SoilMoistureSensor, DeviceRole.PowerSensor, DeviceRole.EnergySensor, DeviceRole.VoltageSensor, DeviceRole.CurrentSensor, DeviceRole.AirQualitySensor, DeviceRole.BatterySensor, DeviceRole.NumericSensor, DeviceRole.EnumSensor, DeviceRole.DateTimeSensor, DeviceRole.GenericSensor, DeviceRole.NumericControl, DeviceRole.SelectControl, DeviceRole.TextControl, DeviceRole.DateTimeControl, DeviceRole.MobilePushNotifier, DeviceRole.MessagingNotifier, DeviceRole.EmailNotifier, DeviceRole.GenericNotifier, DeviceRole.IrTransmitter;
|
|
88088
|
+
DeviceType.Climate;
|
|
88089
|
+
DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
|
|
88090
|
+
DeviceRole.Siren, DeviceRole.Floodlight, DeviceRole.Spotlight, DeviceRole.PirSensor, DeviceRole.Chime, DeviceRole.Autotrack, DeviceRole.Nightvision, DeviceRole.PrivacyMask, DeviceRole.Doorbell, DeviceRole.CoverBlind, DeviceRole.CoverShutter, DeviceRole.CoverAwning, DeviceRole.CoverCurtain, DeviceRole.CoverGarage, DeviceRole.CoverGate, DeviceRole.CoverDoor, DeviceRole.CoverWindow, DeviceRole.BinaryHelper, DeviceRole.Outlet, DeviceRole.SwitchHeater, DeviceRole.SwitchFan, DeviceRole.ClimateAc, DeviceRole.ClimateThermostat, DeviceRole.ClimateFloor, DeviceRole.ClimateRadiator, DeviceRole.ClimateHeater, DeviceRole.ClimateHeatPumpCircuit, DeviceRole.MotionSensor, DeviceRole.ContactSensor, DeviceRole.LeakSensor, DeviceRole.SmokeSensor, DeviceRole.COSensor, DeviceRole.GasSensor, DeviceRole.TamperSensor, DeviceRole.VibrationSensor, DeviceRole.ConnectivitySensor, DeviceRole.SoundSensor, DeviceRole.BinarySensor, DeviceRole.TemperatureSensor, DeviceRole.HumiditySensor, DeviceRole.AmbientLightSensor, DeviceRole.PressureSensor, DeviceRole.WindSensor, DeviceRole.RainSensor, DeviceRole.UvSensor, DeviceRole.SolarRadiationSensor, DeviceRole.SoilMoistureSensor, DeviceRole.PowerSensor, DeviceRole.EnergySensor, DeviceRole.VoltageSensor, DeviceRole.CurrentSensor, DeviceRole.AirQualitySensor, DeviceRole.BatterySensor, DeviceRole.NumericSensor, DeviceRole.EnumSensor, DeviceRole.DateTimeSensor, DeviceRole.GenericSensor, DeviceRole.NumericControl, DeviceRole.SelectControl, DeviceRole.TextControl, DeviceRole.DateTimeControl, DeviceRole.MobilePushNotifier, DeviceRole.MessagingNotifier, DeviceRole.EmailNotifier, DeviceRole.GenericNotifier, DeviceRole.IrTransmitter;
|
|
87323
88091
|
Object.freeze({
|
|
87324
88092
|
"accessories.getStatus": {
|
|
87325
88093
|
capName: "accessories",
|
|
@@ -89169,41 +89937,6 @@ Object.freeze({
|
|
|
89169
89937
|
access: "create",
|
|
89170
89938
|
role: "admin"
|
|
89171
89939
|
},
|
|
89172
|
-
"deviceDiscovery.adoptDevice": {
|
|
89173
|
-
capName: "device-discovery",
|
|
89174
|
-
capScope: "device",
|
|
89175
|
-
addonId: null,
|
|
89176
|
-
access: "create",
|
|
89177
|
-
role: "admin"
|
|
89178
|
-
},
|
|
89179
|
-
"deviceDiscovery.getStatus": {
|
|
89180
|
-
capName: "device-discovery",
|
|
89181
|
-
capScope: "device",
|
|
89182
|
-
addonId: null,
|
|
89183
|
-
access: "view",
|
|
89184
|
-
role: "viewer"
|
|
89185
|
-
},
|
|
89186
|
-
"deviceDiscovery.listDiscovered": {
|
|
89187
|
-
capName: "device-discovery",
|
|
89188
|
-
capScope: "device",
|
|
89189
|
-
addonId: null,
|
|
89190
|
-
access: "view",
|
|
89191
|
-
role: "viewer"
|
|
89192
|
-
},
|
|
89193
|
-
"deviceDiscovery.refreshDiscovery": {
|
|
89194
|
-
capName: "device-discovery",
|
|
89195
|
-
capScope: "device",
|
|
89196
|
-
addonId: null,
|
|
89197
|
-
access: "create",
|
|
89198
|
-
role: "admin"
|
|
89199
|
-
},
|
|
89200
|
-
"deviceDiscovery.releaseDevice": {
|
|
89201
|
-
capName: "device-discovery",
|
|
89202
|
-
capScope: "device",
|
|
89203
|
-
addonId: null,
|
|
89204
|
-
access: "create",
|
|
89205
|
-
role: "admin"
|
|
89206
|
-
},
|
|
89207
89940
|
"deviceExport.applyDeviceSettingsPatch": {
|
|
89208
89941
|
capName: "device-export",
|
|
89209
89942
|
capScope: "system",
|
|
@@ -90751,6 +91484,13 @@ Object.freeze({
|
|
|
90751
91484
|
access: "create",
|
|
90752
91485
|
role: "viewer"
|
|
90753
91486
|
},
|
|
91487
|
+
"fanControl.setOscillationAxis": {
|
|
91488
|
+
capName: "fan-control",
|
|
91489
|
+
capScope: "device",
|
|
91490
|
+
addonId: null,
|
|
91491
|
+
access: "create",
|
|
91492
|
+
role: "viewer"
|
|
91493
|
+
},
|
|
90754
91494
|
"fanControl.setPercentage": {
|
|
90755
91495
|
capName: "fan-control",
|
|
90756
91496
|
capScope: "device",
|
|
@@ -97772,7 +98512,7 @@ function projectAction(cap, action) {
|
|
|
97772
98512
|
};
|
|
97773
98513
|
}
|
|
97774
98514
|
Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
|
|
97775
|
-
DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.
|
|
98515
|
+
DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
|
|
97776
98516
|
DeviceFeature.BatteryOperated;
|
|
97777
98517
|
function bound(cap, methods = [], authority = "reported") {
|
|
97778
98518
|
return {
|
|
@@ -98261,35 +99001,6 @@ Object.freeze({
|
|
|
98261
99001
|
form: "single",
|
|
98262
99002
|
optional: false
|
|
98263
99003
|
}],
|
|
98264
|
-
"deviceDiscovery.adoptDevice": [{
|
|
98265
|
-
name: "deviceId",
|
|
98266
|
-
form: "single",
|
|
98267
|
-
optional: false
|
|
98268
|
-
}],
|
|
98269
|
-
"deviceDiscovery.getStatus": [{
|
|
98270
|
-
name: "deviceId",
|
|
98271
|
-
form: "single",
|
|
98272
|
-
optional: false
|
|
98273
|
-
}],
|
|
98274
|
-
"deviceDiscovery.listDiscovered": [{
|
|
98275
|
-
name: "deviceId",
|
|
98276
|
-
form: "single",
|
|
98277
|
-
optional: false
|
|
98278
|
-
}],
|
|
98279
|
-
"deviceDiscovery.refreshDiscovery": [{
|
|
98280
|
-
name: "deviceId",
|
|
98281
|
-
form: "single",
|
|
98282
|
-
optional: false
|
|
98283
|
-
}],
|
|
98284
|
-
"deviceDiscovery.releaseDevice": [{
|
|
98285
|
-
name: "childDeviceId",
|
|
98286
|
-
form: "single",
|
|
98287
|
-
optional: false
|
|
98288
|
-
}, {
|
|
98289
|
-
name: "deviceId",
|
|
98290
|
-
form: "single",
|
|
98291
|
-
optional: false
|
|
98292
|
-
}],
|
|
98293
99004
|
"deviceExport.applyDeviceSettingsPatch": [{
|
|
98294
99005
|
name: "deviceId",
|
|
98295
99006
|
form: "single",
|
|
@@ -98853,6 +99564,11 @@ Object.freeze({
|
|
|
98853
99564
|
form: "single",
|
|
98854
99565
|
optional: false
|
|
98855
99566
|
}],
|
|
99567
|
+
"fanControl.setOscillationAxis": [{
|
|
99568
|
+
name: "deviceId",
|
|
99569
|
+
form: "single",
|
|
99570
|
+
optional: false
|
|
99571
|
+
}],
|
|
98856
99572
|
"fanControl.setPercentage": [{
|
|
98857
99573
|
name: "deviceId",
|
|
98858
99574
|
form: "single",
|
|
@@ -102327,6 +103043,27 @@ function ingestReading(input) {
|
|
|
102327
103043
|
value: converted
|
|
102328
103044
|
};
|
|
102329
103045
|
}
|
|
103046
|
+
function collapse(value) {
|
|
103047
|
+
return value.trim().replace(/\s+/g, " ");
|
|
103048
|
+
}
|
|
103049
|
+
/** "KLF-200" → "KLF200". A hyphen between words ("Home Hub") stays. */
|
|
103050
|
+
function compactModelToken(value) {
|
|
103051
|
+
return value.replace(/([A-Za-z])-(\d)/g, "$1$2");
|
|
103052
|
+
}
|
|
103053
|
+
/**
|
|
103054
|
+
* A Gateway device: "<Brand> <Model>". An unknown model is the brand alone
|
|
103055
|
+
* (the caller passes the brand-level fallback it already has, e.g. "Velux KLF-200").
|
|
103056
|
+
*/
|
|
103057
|
+
function gatewayDeviceName(brand, model) {
|
|
103058
|
+
const brandPart = compactModelToken(collapse(brand));
|
|
103059
|
+
const raw = collapse(model ?? "");
|
|
103060
|
+
if (raw.length === 0) return brandPart;
|
|
103061
|
+
const modelPart = compactModelToken(raw);
|
|
103062
|
+
const prefix = `${brandPart.toLowerCase()} `;
|
|
103063
|
+
if (modelPart.toLowerCase() === brandPart.toLowerCase()) return brandPart;
|
|
103064
|
+
if (modelPart.toLowerCase().startsWith(prefix)) return modelPart;
|
|
103065
|
+
return `${brandPart} ${modelPart}`;
|
|
103066
|
+
}
|
|
102330
103067
|
/**
|
|
102331
103068
|
* Closes the gap D49 leaves: an integration deleted while its provider was down never emits an
|
|
102332
103069
|
* event the provider sees. An integration id absent from an ANSWERED list is only a suspect; its
|
|
@@ -103713,7 +104450,7 @@ function buildEnpalCandidates(input) {
|
|
|
103713
104450
|
const boxChildren = box === void 0 ? [] : reportedChildren(box, snapshot);
|
|
103714
104451
|
const out = [{
|
|
103715
104452
|
childNativeId: "box",
|
|
103716
|
-
name:
|
|
104453
|
+
name: gatewayDeviceName("Enpal", "Box"),
|
|
103717
104454
|
type: DeviceType.Gateway,
|
|
103718
104455
|
status: input.box.online ? "online" : "offline",
|
|
103719
104456
|
metadata: METADATA,
|
|
@@ -103837,7 +104574,7 @@ function buildEnpalAdoptionProvider(deps) {
|
|
|
103837
104574
|
const specIds = reportedSpecIds(equipment, candidate);
|
|
103838
104575
|
const { deviceId, accessoryDeviceIds } = await deps.adoptGateway({
|
|
103839
104576
|
integrationId,
|
|
103840
|
-
name: perCandidate?.[equipmentId]?.name ??
|
|
104577
|
+
name: perCandidate?.[equipmentId]?.name ?? gatewayDeviceName("Enpal", "Box"),
|
|
103841
104578
|
specIds,
|
|
103842
104579
|
childLayout: childLayoutOf(equipment, specIds)
|
|
103843
104580
|
});
|