@camstack/addon-provider-enpal 0.1.16 → 0.1.18
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 +12 -0
- package/assets/icon.svg +7 -10
- package/dist/addon.js +1632 -542
- package/dist/addon.mjs +1632 -542
- package/package.json +2 -2
package/dist/addon.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
//#region ../types/dist/event-category-
|
|
5
|
+
//#region ../types/dist/event-category-Dv-Gcv1f.mjs
|
|
6
6
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
7
7
|
EventCategory["SystemBoot"] = "system.boot";
|
|
8
8
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -443,6 +443,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
443
443
|
*/
|
|
444
444
|
EventCategory["DeviceStateChanged"] = "device.state-changed";
|
|
445
445
|
/**
|
|
446
|
+
* A device COMMAND (a vocabulary action, D920/D983) settled in the hub's
|
|
447
|
+
* transition audit (D1022): confirmed, already in place, unconfirmed or
|
|
448
|
+
* failed. Never a device's own change. Payload: `DeviceCommandSettledEvent`
|
|
449
|
+
* (`{deviceId, action, outcome, actor, at}`, no arguments). It feeds
|
|
450
|
+
* the Notification Center `device-command` kind. Telemetry (D8): losing one
|
|
451
|
+
* costs a notification, never state; the audit row is the record.
|
|
452
|
+
*/
|
|
453
|
+
EventCategory["DeviceCommandSettled"] = "device.command-settled";
|
|
454
|
+
/**
|
|
446
455
|
* The NATIVE shadow of a cap a composition has claimed fields of moved
|
|
447
456
|
* (D663). Payload: `{ deviceId, capName, native }` — what the provider
|
|
448
457
|
* wrote, BEFORE the owned fields are overlaid. Owner-only (D224): it is for
|
|
@@ -805,7 +814,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
805
814
|
return EventCategory;
|
|
806
815
|
}({});
|
|
807
816
|
//#endregion
|
|
808
|
-
//#region ../types/dist/composed-stable-id-
|
|
817
|
+
//#region ../types/dist/composed-stable-id-CPp3votu.mjs
|
|
809
818
|
/**
|
|
810
819
|
import { errMsg } from '@camstack/types'
|
|
811
820
|
* Extract a human-readable message from an unknown error value.
|
|
@@ -818,17 +827,18 @@ function errMsg(err) {
|
|
|
818
827
|
}
|
|
819
828
|
var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
820
829
|
DeviceType["Camera"] = "camera";
|
|
830
|
+
/** Legacy stored value, read only by the hub-to-integration migrations; no live device carries it. */
|
|
821
831
|
DeviceType["Hub"] = "hub";
|
|
822
832
|
DeviceType["Light"] = "light";
|
|
823
833
|
DeviceType["Siren"] = "siren";
|
|
824
834
|
DeviceType["Switch"] = "switch";
|
|
825
835
|
DeviceType["Sensor"] = "sensor";
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
* `climate-control`
|
|
829
|
-
*
|
|
830
|
-
*
|
|
831
|
-
*
|
|
836
|
+
/** Every climate device: an air conditioner, a room thermostat, a radiator
|
|
837
|
+
* valve, a floor-heating zone, a heat-pump circuit. They share one set of
|
|
838
|
+
* controls (`climate-control`), so they are one type; WHICH kind of climate
|
|
839
|
+
* device it is is its {@link DeviceRole} (`climate-*`), which the UI and the
|
|
840
|
+
* exports branch on. The retired `thermostat` type is read as this one
|
|
841
|
+
* (`canonicalStoredDeviceType`). */
|
|
832
842
|
DeviceType["Climate"] = "climate";
|
|
833
843
|
DeviceType["Button"] = "button";
|
|
834
844
|
/** Generic stateless event emitter — carries a device's EXACT declared
|
|
@@ -919,10 +929,6 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
|
919
929
|
/** Washing machine. A plug-built one is running while its power stays
|
|
920
930
|
* above a threshold. No program cap — that surface is the dishwasher's. */
|
|
921
931
|
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
932
|
/** Tumble dryer. Same plug-built running surface as {@link Washer}. */
|
|
927
933
|
DeviceType["Dryer"] = "dryer";
|
|
928
934
|
/** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
|
|
@@ -1142,6 +1148,27 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
|
|
|
1142
1148
|
/** A smart plug: a `switch` whose source says it is an outlet (HA
|
|
1143
1149
|
* `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
|
|
1144
1150
|
DeviceRole["Outlet"] = "outlet";
|
|
1151
|
+
/** A dumb plug-in stove or heater behind a switch: it runs only while the
|
|
1152
|
+
* switch is on. Offered `timed-run`, which switches it off again after a
|
|
1153
|
+
* chosen time. */
|
|
1154
|
+
DeviceRole["SwitchHeater"] = "switch-heater";
|
|
1155
|
+
/** A plain fan behind a switch (a smart plug powering it): drawn as a fan,
|
|
1156
|
+
* with the switch's on/off only. Speed or oscillation appear only when the
|
|
1157
|
+
* device also has a fan cap (D1033). */
|
|
1158
|
+
DeviceRole["SwitchFan"] = "switch-fan";
|
|
1159
|
+
/** Air conditioner / split unit: cooling, fan speed, louvres. */
|
|
1160
|
+
DeviceRole["ClimateAc"] = "climate-ac";
|
|
1161
|
+
/** Room thermostat: a setpoint and a room temperature. The default. */
|
|
1162
|
+
DeviceRole["ClimateThermostat"] = "climate-thermostat";
|
|
1163
|
+
/** Floor-heating zone actuator. */
|
|
1164
|
+
DeviceRole["ClimateFloor"] = "climate-floor";
|
|
1165
|
+
/** Radiator valve (thermostatic radiator head). */
|
|
1166
|
+
DeviceRole["ClimateRadiator"] = "climate-radiator";
|
|
1167
|
+
/** A heater with its own thermostat (stove, convector). */
|
|
1168
|
+
DeviceRole["ClimateHeater"] = "climate-heater";
|
|
1169
|
+
/** One heating/cooling circuit of a heat pump: a water loop with comfort /
|
|
1170
|
+
* eco setpoints and a heat/cool mode, no room sensor. */
|
|
1171
|
+
DeviceRole["ClimateHeatPumpCircuit"] = "climate-heat-pump-circuit";
|
|
1145
1172
|
/** Generic motion / occupancy / moving event source. Distinct from
|
|
1146
1173
|
* the camera accessory PirSensor role: that one is a camera child;
|
|
1147
1174
|
* this is a standalone HA / 3rd-party motion sensor. */
|
|
@@ -6029,7 +6056,7 @@ var ZodIssueCode = {
|
|
|
6029
6056
|
var ZodFirstPartyTypeKind;
|
|
6030
6057
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
6031
6058
|
//#endregion
|
|
6032
|
-
//#region ../types/dist/sleep-
|
|
6059
|
+
//#region ../types/dist/sleep-Czv-4wFH.mjs
|
|
6033
6060
|
/**
|
|
6034
6061
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
6035
6062
|
* window to float samples (D455).
|
|
@@ -6712,6 +6739,13 @@ async function runAddonMigrations(host, migrations, logger, options = {}) {
|
|
|
6712
6739
|
else skipped.push(id);
|
|
6713
6740
|
};
|
|
6714
6741
|
const selected = options.only === void 0 ? null : new Set(options.only);
|
|
6742
|
+
const deferred = /* @__PURE__ */ new Set();
|
|
6743
|
+
const deferredBy = (reason) => {
|
|
6744
|
+
if (selected === null || reason === null) return null;
|
|
6745
|
+
const dep = /^dependency-(?:pending|blocked):(.+)$/.exec(reason)?.[1];
|
|
6746
|
+
if (dep === void 0 || !byId.has(dep)) return null;
|
|
6747
|
+
return !selected.has(dep) || deferred.has(dep) ? dep : null;
|
|
6748
|
+
};
|
|
6715
6749
|
for (const step of declaration.steps) {
|
|
6716
6750
|
if (selected !== null && !selected.has(step.id)) continue;
|
|
6717
6751
|
const bound = byId.get(step.id);
|
|
@@ -6723,6 +6757,19 @@ async function runAddonMigrations(host, migrations, logger, options = {}) {
|
|
|
6723
6757
|
try {
|
|
6724
6758
|
const gate = await host.startMigration({ id: step.id });
|
|
6725
6759
|
if (!gate.proceed) {
|
|
6760
|
+
const waitsFor = deferredBy(gate.reason);
|
|
6761
|
+
if (waitsFor !== null) {
|
|
6762
|
+
log.info("migration deferred: its predecessor runs in another phase of this owner", {
|
|
6763
|
+
tags,
|
|
6764
|
+
meta: {
|
|
6765
|
+
waitsFor,
|
|
6766
|
+
reason: gate.reason
|
|
6767
|
+
}
|
|
6768
|
+
});
|
|
6769
|
+
deferred.add(step.id);
|
|
6770
|
+
skipped.push(step.id);
|
|
6771
|
+
continue;
|
|
6772
|
+
}
|
|
6726
6773
|
log.warn("migration not run", {
|
|
6727
6774
|
tags,
|
|
6728
6775
|
meta: {
|
|
@@ -10260,6 +10307,13 @@ var DeviceTransitionSchema = object({
|
|
|
10260
10307
|
actor: TransitionActorSchema,
|
|
10261
10308
|
command: TransitionCommandSchema.optional()
|
|
10262
10309
|
});
|
|
10310
|
+
object({
|
|
10311
|
+
deviceId: number$1().int(),
|
|
10312
|
+
action: string(),
|
|
10313
|
+
outcome: TransitionOutcomeSchema,
|
|
10314
|
+
actor: TransitionActorSchema,
|
|
10315
|
+
at: number$1()
|
|
10316
|
+
});
|
|
10263
10317
|
var READING_QUANTITIES = [
|
|
10264
10318
|
"temperature",
|
|
10265
10319
|
"pressure",
|
|
@@ -11806,6 +11860,50 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
11806
11860
|
value: false
|
|
11807
11861
|
},
|
|
11808
11862
|
args: { oscillating: false }
|
|
11863
|
+
},
|
|
11864
|
+
{
|
|
11865
|
+
id: "fan.horizontalOscillateOn",
|
|
11866
|
+
method: "setOscillationAxis",
|
|
11867
|
+
label: t("Horizontal oscillation on", "Oscillazione orizzontale attiva"),
|
|
11868
|
+
phrase: t("Start the horizontal oscillation of {device}", "Attiva l’oscillazione orizzontale di {device}"),
|
|
11869
|
+
icon: "move-horizontal",
|
|
11870
|
+
args: {
|
|
11871
|
+
axis: "horizontal",
|
|
11872
|
+
oscillating: true
|
|
11873
|
+
}
|
|
11874
|
+
},
|
|
11875
|
+
{
|
|
11876
|
+
id: "fan.horizontalOscillateOff",
|
|
11877
|
+
method: "setOscillationAxis",
|
|
11878
|
+
label: t("Horizontal oscillation off", "Oscillazione orizzontale spenta"),
|
|
11879
|
+
phrase: t("Stop the horizontal oscillation of {device}", "Ferma l’oscillazione orizzontale di {device}"),
|
|
11880
|
+
icon: "move-horizontal",
|
|
11881
|
+
args: {
|
|
11882
|
+
axis: "horizontal",
|
|
11883
|
+
oscillating: false
|
|
11884
|
+
}
|
|
11885
|
+
},
|
|
11886
|
+
{
|
|
11887
|
+
id: "fan.verticalOscillateOn",
|
|
11888
|
+
method: "setOscillationAxis",
|
|
11889
|
+
label: t("Vertical oscillation on", "Oscillazione verticale attiva"),
|
|
11890
|
+
phrase: t("Start the vertical oscillation of {device}", "Attiva l’oscillazione verticale di {device}"),
|
|
11891
|
+
icon: "move-vertical",
|
|
11892
|
+
args: {
|
|
11893
|
+
axis: "vertical",
|
|
11894
|
+
oscillating: true
|
|
11895
|
+
}
|
|
11896
|
+
},
|
|
11897
|
+
{
|
|
11898
|
+
id: "fan.verticalOscillateOff",
|
|
11899
|
+
method: "setOscillationAxis",
|
|
11900
|
+
label: t("Vertical oscillation off", "Oscillazione verticale spenta"),
|
|
11901
|
+
phrase: t("Stop the vertical oscillation of {device}", "Ferma l’oscillazione verticale di {device}"),
|
|
11902
|
+
icon: "move-vertical",
|
|
11903
|
+
args: {
|
|
11904
|
+
axis: "vertical",
|
|
11905
|
+
oscillating: false
|
|
11906
|
+
}
|
|
11809
11907
|
}
|
|
11810
11908
|
],
|
|
11811
11909
|
state: [{
|
|
@@ -12239,7 +12337,10 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
12239
12337
|
"ir-transmitter": {
|
|
12240
12338
|
scope: "device",
|
|
12241
12339
|
noun: t("IR / RF transmitter", "Trasmettitore IR / RF"),
|
|
12242
|
-
notOperatorFacing: {
|
|
12340
|
+
notOperatorFacing: {
|
|
12341
|
+
send: "a raw pulse train is not something an operator composes in an automation; the remote that owns the command table sends it",
|
|
12342
|
+
learn: "an interactive capture the operator starts from the source dialog and answers by pressing a physical remote; no automation can supply the signal"
|
|
12343
|
+
}
|
|
12243
12344
|
},
|
|
12244
12345
|
control: {
|
|
12245
12346
|
scope: "device",
|
|
@@ -13895,10 +13996,6 @@ var CAMERA_CAP_VOCABULARY = {
|
|
|
13895
13996
|
scope: "device",
|
|
13896
13997
|
noun: t("Admin link", "Link di amministrazione")
|
|
13897
13998
|
},
|
|
13898
|
-
"device-discovery": {
|
|
13899
|
-
scope: "device",
|
|
13900
|
-
noun: t("Discovery", "Rilevamento dispositivi")
|
|
13901
|
-
},
|
|
13902
13999
|
"device-extension": {
|
|
13903
14000
|
scope: "device",
|
|
13904
14001
|
noun: t("Device extension", "Estensione del dispositivo")
|
|
@@ -15356,7 +15453,6 @@ var CAP_NAMES = Object.freeze([
|
|
|
15356
15453
|
"device-admin-link",
|
|
15357
15454
|
"device-adoption",
|
|
15358
15455
|
"device-credentials",
|
|
15359
|
-
"device-discovery",
|
|
15360
15456
|
"device-export",
|
|
15361
15457
|
"device-extension",
|
|
15362
15458
|
"device-history",
|
|
@@ -15978,13 +16074,6 @@ Object.freeze({
|
|
|
15978
16074
|
"valueType": "string"
|
|
15979
16075
|
}
|
|
15980
16076
|
],
|
|
15981
|
-
"device-discovery": [{
|
|
15982
|
-
"path": "lastDiscoveryAt",
|
|
15983
|
-
"valueType": "number"
|
|
15984
|
-
}, {
|
|
15985
|
-
"path": "lastError",
|
|
15986
|
-
"valueType": "string"
|
|
15987
|
-
}],
|
|
15988
16077
|
"device-export": [
|
|
15989
16078
|
{
|
|
15990
16079
|
"path": "linkState",
|
|
@@ -16173,6 +16262,10 @@ Object.freeze({
|
|
|
16173
16262
|
"path": "lastEvent.eventType",
|
|
16174
16263
|
"valueType": "string"
|
|
16175
16264
|
},
|
|
16265
|
+
{
|
|
16266
|
+
"path": "lastEvent.sourceId",
|
|
16267
|
+
"valueType": "string"
|
|
16268
|
+
},
|
|
16176
16269
|
{
|
|
16177
16270
|
"path": "lastEvent.timestamp",
|
|
16178
16271
|
"valueType": "number"
|
|
@@ -17436,7 +17529,7 @@ Object.freeze({
|
|
|
17436
17529
|
}
|
|
17437
17530
|
});
|
|
17438
17531
|
//#endregion
|
|
17439
|
-
//#region ../types/dist/implemented-methods-
|
|
17532
|
+
//#region ../types/dist/implemented-methods-5YMDeE3f.mjs
|
|
17440
17533
|
/**
|
|
17441
17534
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
17442
17535
|
*
|
|
@@ -44097,6 +44190,41 @@ function macroOnly(macro) {
|
|
|
44097
44190
|
};
|
|
44098
44191
|
}
|
|
44099
44192
|
}
|
|
44193
|
+
var EventVocabularyKindSchema = _enum([
|
|
44194
|
+
"press",
|
|
44195
|
+
"long-press",
|
|
44196
|
+
"long-press-start",
|
|
44197
|
+
"release",
|
|
44198
|
+
"repeat",
|
|
44199
|
+
"double-press",
|
|
44200
|
+
"other"
|
|
44201
|
+
]);
|
|
44202
|
+
/** The part of the device an event comes from: a button, a channel. */
|
|
44203
|
+
var EventVocabularySourceSchema = object({
|
|
44204
|
+
id: string().min(1),
|
|
44205
|
+
label: string().min(1)
|
|
44206
|
+
});
|
|
44207
|
+
/** One scalar field of an event's `data`, by dotted path. */
|
|
44208
|
+
var EventVocabularyFieldSchema = object({
|
|
44209
|
+
path: string().min(1),
|
|
44210
|
+
kind: _enum([
|
|
44211
|
+
"string",
|
|
44212
|
+
"number",
|
|
44213
|
+
"boolean",
|
|
44214
|
+
"enum"
|
|
44215
|
+
]),
|
|
44216
|
+
enumValues: array(string()).optional(),
|
|
44217
|
+
label: string().min(1).optional()
|
|
44218
|
+
});
|
|
44219
|
+
var EventVocabularyEntrySchema = object({
|
|
44220
|
+
/** The native token, verbatim. */
|
|
44221
|
+
id: string().min(1),
|
|
44222
|
+
source: EventVocabularySourceSchema.optional(),
|
|
44223
|
+
label: string().min(1),
|
|
44224
|
+
description: string().optional(),
|
|
44225
|
+
kind: EventVocabularyKindSchema.optional(),
|
|
44226
|
+
payload: array(EventVocabularyFieldSchema).optional()
|
|
44227
|
+
});
|
|
44100
44228
|
Math.PI / 180;
|
|
44101
44229
|
/**
|
|
44102
44230
|
* WHEN something happens, as data (D983) — the one time vocabulary of the
|
|
@@ -44149,6 +44277,62 @@ var WeekdayKeySchema = _enum([
|
|
|
44149
44277
|
"sat",
|
|
44150
44278
|
"sun"
|
|
44151
44279
|
]);
|
|
44280
|
+
/**
|
|
44281
|
+
* Hold semantics — "for N seconds" on a leaf of the `when` tree (#498).
|
|
44282
|
+
*
|
|
44283
|
+
* ONE primitive, `for: { ms, mode, tolerance? }`, shared by `device-becomes`,
|
|
44284
|
+
* `device-is` and `expression`:
|
|
44285
|
+
*
|
|
44286
|
+
* - `held`: the condition stays true for `ms`;
|
|
44287
|
+
* - `stable`: the value does not move by more than `tolerance` (absolute, in
|
|
44288
|
+
* the field's base unit) for `ms` while the condition holds.
|
|
44289
|
+
*
|
|
44290
|
+
* {@link HoldCore} is the pure state machine (an input observation → a reading).
|
|
44291
|
+
* {@link HoldTracker} keys cores by a leaf path for the STATE leaves the tree
|
|
44292
|
+
* evaluator judges. {@link BecomesWatch} wraps a core with a timer for the
|
|
44293
|
+
* `device-becomes` EVENT leaf: the deadline is armed when the hold starts and
|
|
44294
|
+
* cancelled when it breaks, and the leaf fires ONCE per episode.
|
|
44295
|
+
*
|
|
44296
|
+
* Hold state is memory only. A hold that is already running when the watcher
|
|
44297
|
+
* first looks (a restart) is INHERITED: nobody knows when it began, so it never
|
|
44298
|
+
* fires for that episode and a state leaf reads it as unknown until the window
|
|
44299
|
+
* has elapsed from the moment the watcher started (D49: not-known is not false).
|
|
44300
|
+
*/
|
|
44301
|
+
var HOLD_MAX_MS = 864e5;
|
|
44302
|
+
var HoldModeSchema = _enum(["held", "stable"]);
|
|
44303
|
+
var HoldSpecSchema = object({
|
|
44304
|
+
/** How long the condition (or the value) must hold. */
|
|
44305
|
+
ms: number$1().int().min(1).max(HOLD_MAX_MS),
|
|
44306
|
+
mode: HoldModeSchema,
|
|
44307
|
+
/** `stable` only: the largest move of the value that still counts as unchanged (absolute, base unit). */
|
|
44308
|
+
tolerance: number$1().min(0).optional()
|
|
44309
|
+
});
|
|
44310
|
+
/** What a trace says about a hold. */
|
|
44311
|
+
var HoldTraceSchema = object({
|
|
44312
|
+
phase: _enum([
|
|
44313
|
+
"pending",
|
|
44314
|
+
"cancelled",
|
|
44315
|
+
"fired",
|
|
44316
|
+
"reseeded"
|
|
44317
|
+
]),
|
|
44318
|
+
ms: number$1().int(),
|
|
44319
|
+
mode: HoldModeSchema,
|
|
44320
|
+
/** When the hold started (ms epoch). */
|
|
44321
|
+
since: number$1().optional(),
|
|
44322
|
+
/** When it completes (ms epoch), for a pending hold. */
|
|
44323
|
+
deadline: number$1().optional()
|
|
44324
|
+
});
|
|
44325
|
+
/** The operators a `device-becomes` leaf may carry: a threshold or a range. */
|
|
44326
|
+
var BecomesOperatorSchema = _enum([
|
|
44327
|
+
"eq",
|
|
44328
|
+
"ne",
|
|
44329
|
+
"gt",
|
|
44330
|
+
"gte",
|
|
44331
|
+
"lt",
|
|
44332
|
+
"lte",
|
|
44333
|
+
"between",
|
|
44334
|
+
"outside"
|
|
44335
|
+
]);
|
|
44152
44336
|
/** How a state leaf compares a device field to a value. Derived from the
|
|
44153
44337
|
* field's `kind` in `deviceManager.getWireableFields`, never hand-maintained. */
|
|
44154
44338
|
var AutomationConditionOperatorSchema = _enum([
|
|
@@ -44162,6 +44346,13 @@ var AutomationConditionOperatorSchema = _enum([
|
|
|
44162
44346
|
"in"
|
|
44163
44347
|
]);
|
|
44164
44348
|
/**
|
|
44349
|
+
* The stable name of an event leaf. A path (`when.0.1`) moves when a row is
|
|
44350
|
+
* reordered; the id does not, so a `triggered-by` leaf keeps pointing at the
|
|
44351
|
+
* same row. Optional: a leaf nothing refers to needs none.
|
|
44352
|
+
*/
|
|
44353
|
+
var EVENT_LEAF_ID_RE = /^[A-Za-z0-9_-]{1,40}$/;
|
|
44354
|
+
var eventLeafId = { id: string().regex(EVENT_LEAF_ID_RE).optional() };
|
|
44355
|
+
/**
|
|
44165
44356
|
* D8 compliance, and it is the reason a "becomes" leaf is not merely an event
|
|
44166
44357
|
* subscription: it evaluates against the **state mirror**, which is reconciled,
|
|
44167
44358
|
* and an event only WAKES the evaluation. A dropped event therefore DELAYS a
|
|
@@ -44169,24 +44360,60 @@ var AutomationConditionOperatorSchema = _enum([
|
|
|
44169
44360
|
*/
|
|
44170
44361
|
var DeviceBecomesLeafSchema = object({
|
|
44171
44362
|
kind: literal("device-becomes"),
|
|
44363
|
+
...eventLeafId,
|
|
44172
44364
|
deviceId: number$1().int().nonnegative(),
|
|
44173
44365
|
cap: string().min(1),
|
|
44174
44366
|
fieldPath: string().min(1),
|
|
44175
|
-
/** Fire when the field takes this value. Omit to fire on any change. */
|
|
44367
|
+
/** Fire when the field takes this value. Omit (with no `operator`) to fire on any change. */
|
|
44176
44368
|
becomes: union([
|
|
44177
44369
|
string(),
|
|
44178
44370
|
number$1(),
|
|
44179
44371
|
boolean()
|
|
44180
44372
|
]).optional(),
|
|
44181
|
-
/**
|
|
44373
|
+
/**
|
|
44374
|
+
* A threshold or a range instead of an equality: "crosses above 3000", "enters
|
|
44375
|
+
* 18..24". The leaf fires on the TRANSITION into the predicate, never on a re-report.
|
|
44376
|
+
* `value` is in the field's base unit; `between` / `outside` take `[low, high]`.
|
|
44377
|
+
*/
|
|
44378
|
+
operator: BecomesOperatorSchema.optional(),
|
|
44379
|
+
value: union([
|
|
44380
|
+
number$1(),
|
|
44381
|
+
string(),
|
|
44382
|
+
boolean(),
|
|
44383
|
+
array(number$1()).length(2)
|
|
44384
|
+
]).optional(),
|
|
44385
|
+
/**
|
|
44386
|
+
* Hold: the transition (or, with no predicate, the new value) must stay for
|
|
44387
|
+
* `for.ms` before the leaf fires, ONCE per episode. See `hold.ts`.
|
|
44388
|
+
*/
|
|
44389
|
+
for: HoldSpecSchema.optional(),
|
|
44390
|
+
/** @deprecated every leaf fires on a transition now; read as always on. */
|
|
44182
44391
|
edge: boolean().optional(),
|
|
44183
|
-
/**
|
|
44184
|
-
forMs: number$1().int().min(0).max(
|
|
44392
|
+
/** @deprecated legacy `held` window — read through `holdOf` as `for: { ms, mode: 'held' }`. */
|
|
44393
|
+
forMs: number$1().int().min(0).max(HOLD_MAX_MS).optional(),
|
|
44185
44394
|
/** Collapse a burst into one occurrence. */
|
|
44186
44395
|
debounceMs: number$1().int().min(0).max(6e5).optional()
|
|
44396
|
+
}).superRefine((leaf, ctx) => {
|
|
44397
|
+
if (leaf.becomes !== void 0 && leaf.operator !== void 0) ctx.addIssue({
|
|
44398
|
+
code: "custom",
|
|
44399
|
+
message: "a leaf names `becomes` or `operator`, not both",
|
|
44400
|
+
path: ["operator"]
|
|
44401
|
+
});
|
|
44402
|
+
if (leaf.operator !== void 0 && leaf.value === void 0) ctx.addIssue({
|
|
44403
|
+
code: "custom",
|
|
44404
|
+
message: "an operator needs the value to compare to",
|
|
44405
|
+
path: ["value"]
|
|
44406
|
+
});
|
|
44407
|
+
const range = leaf.operator === "between" || leaf.operator === "outside";
|
|
44408
|
+
if (leaf.operator !== void 0 && range !== Array.isArray(leaf.value)) ctx.addIssue({
|
|
44409
|
+
code: "custom",
|
|
44410
|
+
message: range ? "a range takes [low, high]" : "only a range takes [low, high]",
|
|
44411
|
+
path: ["value"]
|
|
44412
|
+
});
|
|
44187
44413
|
});
|
|
44188
44414
|
var DeviceEventLeafSchema = object({
|
|
44189
44415
|
kind: literal("device-event"),
|
|
44416
|
+
...eventLeafId,
|
|
44190
44417
|
/** An `EventCategory` value. */
|
|
44191
44418
|
category: string().min(1),
|
|
44192
44419
|
deviceId: number$1().int().nonnegative().optional()
|
|
@@ -44199,18 +44426,51 @@ var DeviceEventLeafSchema = object({
|
|
|
44199
44426
|
*/
|
|
44200
44427
|
var ScheduleLeafSchema = object({
|
|
44201
44428
|
kind: literal("schedule"),
|
|
44429
|
+
...eventLeafId,
|
|
44202
44430
|
at: TimeSpecSchema.optional(),
|
|
44203
44431
|
days: array(WeekdayKeySchema).min(1).max(7).optional(),
|
|
44204
44432
|
/** @deprecated legacy cron — new schedules use `at`. */
|
|
44205
44433
|
cron: string().min(1).max(120).optional()
|
|
44206
44434
|
}).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
|
-
|
|
44435
|
+
/**
|
|
44436
|
+
* One payload predicate of a `device-fired` row: the value at `path` inside
|
|
44437
|
+
* the fire's `data` (dotted) equals `equals`. Compared strictly, no coercion.
|
|
44438
|
+
*/
|
|
44439
|
+
var DeviceFiredMatchSchema = object({
|
|
44440
|
+
path: string().min(1).max(120),
|
|
44441
|
+
equals: union([
|
|
44442
|
+
string(),
|
|
44443
|
+
number$1(),
|
|
44444
|
+
boolean()
|
|
44445
|
+
])
|
|
44446
|
+
});
|
|
44447
|
+
/**
|
|
44448
|
+
* "fires": one device of the `event-emitter` capability reported an event
|
|
44449
|
+
* (D1045). Every list is an OR, the lists together an AND; an absent list does
|
|
44450
|
+
* not narrow. `eventIds` are the device's own tokens, `sourceIds` the parts of
|
|
44451
|
+
* the device (a button), `kinds` the shared press kinds of its vocabulary.
|
|
44452
|
+
*/
|
|
44453
|
+
var DeviceFiredLeafSchema = object({
|
|
44454
|
+
kind: literal("device-fired"),
|
|
44455
|
+
...eventLeafId,
|
|
44456
|
+
deviceId: number$1().int().nonnegative(),
|
|
44457
|
+
eventIds: array(string().min(1)).min(1).max(64).optional(),
|
|
44458
|
+
sourceIds: array(string().min(1)).min(1).max(64).optional(),
|
|
44459
|
+
kinds: array(EventVocabularyKindSchema).min(1).optional(),
|
|
44460
|
+
match: array(DeviceFiredMatchSchema).min(1).max(8).optional()
|
|
44461
|
+
});
|
|
44462
|
+
var ManualLeafSchema = object({
|
|
44463
|
+
kind: literal("manual"),
|
|
44464
|
+
...eventLeafId
|
|
44465
|
+
});
|
|
44208
44466
|
var IntervalLeafSchema = object({
|
|
44209
44467
|
kind: literal("interval"),
|
|
44468
|
+
...eventLeafId,
|
|
44210
44469
|
minutes: number$1().int().min(1).max(1440)
|
|
44211
44470
|
});
|
|
44212
44471
|
var DetectionLeafSchema = object({
|
|
44213
44472
|
kind: literal("detection"),
|
|
44473
|
+
...eventLeafId,
|
|
44214
44474
|
/** One macro class each, optionally narrowed by a label; a track of any listed subject fires. */
|
|
44215
44475
|
subjects: array(DetectionSubjectSchema).min(1),
|
|
44216
44476
|
deviceIds: array(number$1().int().nonnegative()).min(1),
|
|
@@ -44221,6 +44481,7 @@ var DetectionLeafSchema = object({
|
|
|
44221
44481
|
});
|
|
44222
44482
|
var StationaryLeafSchema = object({
|
|
44223
44483
|
kind: literal("stationary"),
|
|
44484
|
+
...eventLeafId,
|
|
44224
44485
|
deviceIds: array(number$1().int().nonnegative()).min(1),
|
|
44225
44486
|
subjects: array(StationarySubjectSchema).min(1).optional(),
|
|
44226
44487
|
zones: array(string().min(1)).min(1).optional(),
|
|
@@ -44248,7 +44509,9 @@ var DeviceIsLeafSchema = object({
|
|
|
44248
44509
|
QuantityValueSchema
|
|
44249
44510
|
]),
|
|
44250
44511
|
/** The unit the person picked for `value` (display only; `value` is in the base unit). */
|
|
44251
|
-
unit: string().min(1).optional()
|
|
44512
|
+
unit: string().min(1).optional(),
|
|
44513
|
+
/** The comparison must hold (`held`) or the value stay put (`stable`) this long. */
|
|
44514
|
+
for: HoldSpecSchema.optional()
|
|
44252
44515
|
});
|
|
44253
44516
|
/**
|
|
44254
44517
|
* True when the camera currently has a PARKED object matching `subjects` in at
|
|
@@ -44264,6 +44527,41 @@ var StationaryPresentLeafSchema = object({
|
|
|
44264
44527
|
minCount: number$1().int().positive().optional()
|
|
44265
44528
|
});
|
|
44266
44529
|
/**
|
|
44530
|
+
* True while the site-local clock is inside a daily window and/or on one of
|
|
44531
|
+
* the named days. `days` names the local day a window STARTS on, so
|
|
44532
|
+
* 22:00-06:00 on weekdays is still inside at 01:00 Saturday. A one-date spec
|
|
44533
|
+
* (`kind: 'date'`) is refused: only a clock time or a sun event makes a window
|
|
44534
|
+
* that repeats every day.
|
|
44535
|
+
*/
|
|
44536
|
+
var TimeIsLeafSchema = object({
|
|
44537
|
+
kind: literal("time-is"),
|
|
44538
|
+
between: object({
|
|
44539
|
+
from: TimeSpecSchema,
|
|
44540
|
+
to: TimeSpecSchema
|
|
44541
|
+
}).optional(),
|
|
44542
|
+
days: array(WeekdayKeySchema).min(1).max(7).optional()
|
|
44543
|
+
}).superRefine((leaf, ctx) => {
|
|
44544
|
+
if (leaf.between === void 0 && leaf.days === void 0) ctx.addIssue({
|
|
44545
|
+
code: "custom",
|
|
44546
|
+
message: "a time-is leaf needs a between window, days, or both"
|
|
44547
|
+
});
|
|
44548
|
+
for (const end of ["from", "to"]) if (leaf.between?.[end].kind === "date") ctx.addIssue({
|
|
44549
|
+
code: "custom",
|
|
44550
|
+
message: "a one-date time cannot bound a daily window: use a clock time or a sun event",
|
|
44551
|
+
path: ["between", end]
|
|
44552
|
+
});
|
|
44553
|
+
});
|
|
44554
|
+
/**
|
|
44555
|
+
* True for the run that one of the named event leaves started. It reads the
|
|
44556
|
+
* run, not a device: in the tree it is judged against the leaf that fired, in
|
|
44557
|
+
* a step guard against the trigger the run carries. A run no event leaf
|
|
44558
|
+
* started (a state-only tree becoming true) is false.
|
|
44559
|
+
*/
|
|
44560
|
+
var TriggeredByLeafSchema = object({
|
|
44561
|
+
kind: literal("triggered-by"),
|
|
44562
|
+
leafIds: array(string().regex(EVENT_LEAF_ID_RE)).min(1).max(20)
|
|
44563
|
+
});
|
|
44564
|
+
/**
|
|
44267
44565
|
* The escape hatch: the bounded expression engine over named device bindings.
|
|
44268
44566
|
* Declared as a plain object so a discriminated union can read `kind`; the
|
|
44269
44567
|
* author-time validation is the SAME function `ExpressionSourceSchema` runs.
|
|
@@ -44271,7 +44569,9 @@ var StationaryPresentLeafSchema = object({
|
|
|
44271
44569
|
var ExpressionLeafSchema = object({
|
|
44272
44570
|
kind: literal("expression"),
|
|
44273
44571
|
expr: string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
|
|
44274
|
-
bindings: record(string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
|
|
44572
|
+
bindings: record(string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema),
|
|
44573
|
+
/** The expression must stay true this long. An expression has no single value, so only `held`. */
|
|
44574
|
+
for: HoldSpecSchema.optional()
|
|
44275
44575
|
}).superRefine((src, ctx) => {
|
|
44276
44576
|
const err = validateExpressionSource(src);
|
|
44277
44577
|
if (err !== null) ctx.addIssue({
|
|
@@ -44279,6 +44579,11 @@ var ExpressionLeafSchema = object({
|
|
|
44279
44579
|
message: err,
|
|
44280
44580
|
path: ["expr"]
|
|
44281
44581
|
});
|
|
44582
|
+
if (src.for?.mode === "stable") ctx.addIssue({
|
|
44583
|
+
code: "custom",
|
|
44584
|
+
message: "an expression is held, not stable: it has no single value to keep still",
|
|
44585
|
+
path: ["for", "mode"]
|
|
44586
|
+
});
|
|
44282
44587
|
});
|
|
44283
44588
|
var AutomationWhenSchema = lazy(() => discriminatedUnion("kind", [
|
|
44284
44589
|
object({
|
|
@@ -44295,6 +44600,7 @@ var AutomationWhenSchema = lazy(() => discriminatedUnion("kind", [
|
|
|
44295
44600
|
}),
|
|
44296
44601
|
DeviceBecomesLeafSchema,
|
|
44297
44602
|
DeviceEventLeafSchema,
|
|
44603
|
+
DeviceFiredLeafSchema,
|
|
44298
44604
|
ScheduleLeafSchema,
|
|
44299
44605
|
ManualLeafSchema,
|
|
44300
44606
|
IntervalLeafSchema,
|
|
@@ -44302,11 +44608,14 @@ var AutomationWhenSchema = lazy(() => discriminatedUnion("kind", [
|
|
|
44302
44608
|
StationaryLeafSchema,
|
|
44303
44609
|
DeviceIsLeafSchema,
|
|
44304
44610
|
StationaryPresentLeafSchema,
|
|
44305
|
-
ExpressionLeafSchema
|
|
44611
|
+
ExpressionLeafSchema,
|
|
44612
|
+
TimeIsLeafSchema,
|
|
44613
|
+
TriggeredByLeafSchema
|
|
44306
44614
|
]));
|
|
44307
44615
|
var EVENT_KINDS = new Set([
|
|
44308
44616
|
"device-becomes",
|
|
44309
44617
|
"device-event",
|
|
44618
|
+
"device-fired",
|
|
44310
44619
|
"schedule",
|
|
44311
44620
|
"manual",
|
|
44312
44621
|
"interval",
|
|
@@ -44356,6 +44665,28 @@ function hasEventLeaves(node) {
|
|
|
44356
44665
|
});
|
|
44357
44666
|
return found;
|
|
44358
44667
|
}
|
|
44668
|
+
/** The event leaves of the tree that carry an id, in tree order. */
|
|
44669
|
+
function identifiedEventLeaves(when) {
|
|
44670
|
+
const found = [];
|
|
44671
|
+
forEachLeaf(when, (leaf, path) => {
|
|
44672
|
+
if (isEventLeaf(leaf) && leaf.id !== void 0) found.push({
|
|
44673
|
+
id: leaf.id,
|
|
44674
|
+
path,
|
|
44675
|
+
leaf
|
|
44676
|
+
});
|
|
44677
|
+
});
|
|
44678
|
+
return found;
|
|
44679
|
+
}
|
|
44680
|
+
/** Does the tree hold a `triggered-by` leaf? */
|
|
44681
|
+
function hasTriggeredByLeaves(when) {
|
|
44682
|
+
let found = false;
|
|
44683
|
+
forEachLeaf(when, (leaf) => {
|
|
44684
|
+
if (leaf.kind === "triggered-by") found = true;
|
|
44685
|
+
});
|
|
44686
|
+
return found;
|
|
44687
|
+
}
|
|
44688
|
+
/** Why a `triggered-by` leaf is refused outside an automation: there is no trigger row to name. */
|
|
44689
|
+
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
44690
|
function hasEmptyContainer(node) {
|
|
44360
44691
|
if (isLeaf(node)) return false;
|
|
44361
44692
|
if (node.kind === "not") return hasEmptyContainer(node.child);
|
|
@@ -44365,6 +44696,7 @@ function hasEmptyContainer(node) {
|
|
|
44365
44696
|
var LEAF_WORDS = {
|
|
44366
44697
|
"device-becomes": "a device value that becomes",
|
|
44367
44698
|
"device-event": "a device event",
|
|
44699
|
+
"device-fired": "a device firing an event",
|
|
44368
44700
|
schedule: "a schedule",
|
|
44369
44701
|
manual: "a manual run",
|
|
44370
44702
|
interval: "an interval",
|
|
@@ -44414,6 +44746,11 @@ function validateWhenTree(when) {
|
|
|
44414
44746
|
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
44747
|
const leaves = countWhenLeaves(when);
|
|
44416
44748
|
if (leaves > 32) return `the rules use ${leaves} rows — the limit is 32`;
|
|
44749
|
+
const seen = /* @__PURE__ */ new Set();
|
|
44750
|
+
for (const entry of identifiedEventLeaves(when)) {
|
|
44751
|
+
if (seen.has(entry.id)) return `two trigger rows share the id "${entry.id}" — an id names one row`;
|
|
44752
|
+
seen.add(entry.id);
|
|
44753
|
+
}
|
|
44417
44754
|
const need = eventNeed(when, "when", false);
|
|
44418
44755
|
if ("error" in need) return need.error;
|
|
44419
44756
|
const [first, second] = need.paths;
|
|
@@ -44625,9 +44962,12 @@ function convertLegacyWhen(half) {
|
|
|
44625
44962
|
* every caller — automations, Actions, and the notification centre, which runs
|
|
44626
44963
|
* only Actions (planning#473).
|
|
44627
44964
|
*/
|
|
44965
|
+
/** The longest one `wait` step lasts. It is cut by a restart of the run and does not survive a hub restart. */
|
|
44966
|
+
var WAIT_MAX_SECONDS = 3600;
|
|
44967
|
+
var WAIT_TOO_LONG = `a wait is at most one hour (${WAIT_MAX_SECONDS} s)`;
|
|
44628
44968
|
var AutomationWaitStepSchema = object({
|
|
44629
44969
|
kind: literal("wait"),
|
|
44630
|
-
seconds: number$1().min(0).max(
|
|
44970
|
+
seconds: number$1().min(0).max(WAIT_MAX_SECONDS, WAIT_TOO_LONG)
|
|
44631
44971
|
});
|
|
44632
44972
|
/** The legacy `deviceId` of a cap step: still READ, never written (D983). */
|
|
44633
44973
|
var LegacyStepDeviceSchema = union([
|
|
@@ -44697,28 +45037,227 @@ var elseNeedsIf = {
|
|
|
44697
45037
|
path: ["else"]
|
|
44698
45038
|
};
|
|
44699
45039
|
var GuardedWaitStepSchema = AutomationWaitStepSchema.extend(stepGuardShape).refine((step) => step.else === void 0 || step.if !== void 0, elseNeedsIf);
|
|
44700
|
-
|
|
45040
|
+
/**
|
|
45041
|
+
* A step of a STANDALONE list (an action, a scheduler point, a preset): the
|
|
45042
|
+
* same two step kinds an automation runs, minus a guard that reads the
|
|
45043
|
+
* automation's trigger, which such a list does not have.
|
|
45044
|
+
*/
|
|
45045
|
+
var ActionStepSchema = discriminatedUnion("kind", [GuardedWaitStepSchema, AutomationCapStepBaseSchema.extend(stepGuardShape).refine((step) => step.target !== void 0 || step.deviceId !== void 0, {
|
|
45046
|
+
message: "a cap step needs a target",
|
|
45047
|
+
path: ["target"]
|
|
45048
|
+
}).refine((step) => step.target?.kind !== "domain" || step.actionId !== void 0, {
|
|
45049
|
+
message: "a domain target needs the vocabulary action it applies to (actionId)",
|
|
45050
|
+
path: ["actionId"]
|
|
45051
|
+
}).refine((step) => step.else === void 0 || step.if !== void 0, elseNeedsIf)]).superRefine((step, ctx) => {
|
|
45052
|
+
if (step.if !== void 0 && hasTriggeredByLeaves(step.if)) ctx.addIssue({
|
|
45053
|
+
code: "custom",
|
|
45054
|
+
message: TRIGGERED_BY_NEEDS_AUTOMATION,
|
|
45055
|
+
path: ["if"]
|
|
45056
|
+
});
|
|
45057
|
+
});
|
|
45058
|
+
/**
|
|
45059
|
+
* Flow control of an AUTOMATION's list (#500). A standalone list (an action, a
|
|
45060
|
+
* scheduler point, a preset) stays `wait` and `cap`: it has no run of its own
|
|
45061
|
+
* to end and no variable bag to write.
|
|
45062
|
+
*/
|
|
45063
|
+
var continueOnErrorShape = {
|
|
45064
|
+
/** A failure of this step is traced `failed-continued` and the next step runs. */
|
|
45065
|
+
continueOnError: literal(true).optional() };
|
|
45066
|
+
/** An automation's cap step: the shared guarded cap step, plus `continueOnError`. */
|
|
45067
|
+
var AutomationGuardedCapStepSchema = AutomationCapStepBaseSchema.extend({
|
|
45068
|
+
...stepGuardShape,
|
|
45069
|
+
...continueOnErrorShape
|
|
45070
|
+
}).refine((step) => step.target !== void 0 || step.deviceId !== void 0, {
|
|
44701
45071
|
message: "a cap step needs a target",
|
|
44702
45072
|
path: ["target"]
|
|
44703
45073
|
}).refine((step) => step.target?.kind !== "domain" || step.actionId !== void 0, {
|
|
44704
45074
|
message: "a domain target needs the vocabulary action it applies to (actionId)",
|
|
44705
45075
|
path: ["actionId"]
|
|
44706
45076
|
}).refine((step) => step.else === void 0 || step.if !== void 0, elseNeedsIf);
|
|
44707
|
-
|
|
45077
|
+
/** End the run here, on purpose. `reason` may carry `{{vars.x}}` slots. */
|
|
45078
|
+
var AutomationStopStepSchema = object({
|
|
45079
|
+
kind: literal("stop"),
|
|
45080
|
+
reason: string().min(1, "a stop needs a reason: it is what the run trace shows").max(200),
|
|
45081
|
+
/** `ok`: the run ends as a success. `failed`: it ends as a failure, with the reason. */
|
|
45082
|
+
outcome: _enum(["ok", "failed"]),
|
|
45083
|
+
/** Stop only when this reads true; false or unknown does not stop. */
|
|
45084
|
+
if: AutomationWhenSchema.optional()
|
|
45085
|
+
});
|
|
45086
|
+
var SET_VAR_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]{0,39}$/;
|
|
45087
|
+
/** Names the run itself writes and reads (`leaf` is what `triggered-by` judges): a step may not overwrite them. */
|
|
45088
|
+
var RESERVED_RUN_VARS = [
|
|
45089
|
+
"kind",
|
|
45090
|
+
"leaf",
|
|
45091
|
+
"at"
|
|
45092
|
+
];
|
|
45093
|
+
/**
|
|
45094
|
+
* Write variables into the run's bag, for the steps after it. SUBSTITUTION
|
|
45095
|
+
* only, never evaluation: a text value may carry `{{vars.x}}` slots.
|
|
45096
|
+
*/
|
|
45097
|
+
var AutomationSetStepSchema = object({
|
|
45098
|
+
kind: literal("set"),
|
|
45099
|
+
vars: record(string(), union([
|
|
45100
|
+
string().max(500),
|
|
45101
|
+
number$1(),
|
|
45102
|
+
boolean()
|
|
45103
|
+
])).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` }),
|
|
45104
|
+
/** Set only when this reads true; false or unknown leaves the bag as it is. */
|
|
45105
|
+
if: AutomationWhenSchema.optional()
|
|
45106
|
+
});
|
|
45107
|
+
var AutomationRunActionStepSchema = object({
|
|
45108
|
+
kind: literal("action"),
|
|
45109
|
+
/** An entry of the `actions` surface (`internal:<uuid>` or `<addonId>:<localId>`). */
|
|
45110
|
+
actionId: string().min(1),
|
|
45111
|
+
/** Values for the action's declared variables; the others take their default. */
|
|
45112
|
+
variables: record(string(), union([
|
|
45113
|
+
string(),
|
|
45114
|
+
number$1(),
|
|
45115
|
+
boolean()
|
|
45116
|
+
])).optional(),
|
|
45117
|
+
...continueOnErrorShape
|
|
45118
|
+
});
|
|
45119
|
+
/**
|
|
45120
|
+
* A step INSIDE a branch of a `choose`: a flow step with no condition of its
|
|
45121
|
+
* own (the branch is the condition) and no block inside it. Strict, so an `if`
|
|
45122
|
+
* written on such a step is refused rather than dropped.
|
|
45123
|
+
*/
|
|
45124
|
+
/** The longest a `wait-until` may wait: six hours. */
|
|
45125
|
+
var WAIT_UNTIL_TIMEOUT_MAX_S = 21600;
|
|
45126
|
+
/**
|
|
45127
|
+
* Pause the run until a condition is true (#500). The timeout is mandatory: a
|
|
45128
|
+
* run never waits without an end. `onTimeout` says what a wait that ran out
|
|
45129
|
+
* does: `continue` goes on to the next step, `stop` ends the run as a failure.
|
|
45130
|
+
* A condition that cannot be read is not "true": the wait goes on (D49).
|
|
45131
|
+
*/
|
|
45132
|
+
var AutomationWaitUntilStepSchema = object({
|
|
45133
|
+
kind: literal("wait-until"),
|
|
45134
|
+
when: AutomationWhenSchema,
|
|
45135
|
+
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)`),
|
|
45136
|
+
onTimeout: _enum(["continue", "stop"])
|
|
45137
|
+
}).strict();
|
|
45138
|
+
/** The step kinds that hold steps of their own, or pause on a condition: they nest two levels at most. */
|
|
45139
|
+
var BLOCK_STEP_KINDS = [
|
|
45140
|
+
"choose",
|
|
45141
|
+
"repeat",
|
|
45142
|
+
"parallel",
|
|
45143
|
+
"wait-until"
|
|
45144
|
+
];
|
|
45145
|
+
var NEST_TOO_DEEP = `steps nest two levels at most: a step inside a nested choose or repeat can be a plain step only`;
|
|
45146
|
+
function kindOf(input) {
|
|
45147
|
+
return typeof input === "object" && input !== null ? Reflect.get(input, "kind") : void 0;
|
|
45148
|
+
}
|
|
45149
|
+
/** Where the nesting ends, a block step is refused in words rather than as an unknown kind. */
|
|
45150
|
+
function nestTooDeep(issue) {
|
|
45151
|
+
const kind = kindOf(issue.input);
|
|
45152
|
+
return typeof kind === "string" && BLOCK_STEP_KINDS.includes(kind) ? NEST_TOO_DEEP : void 0;
|
|
45153
|
+
}
|
|
45154
|
+
var plainStepSchemas = [
|
|
45155
|
+
AutomationWaitStepSchema.strict(),
|
|
45156
|
+
AutomationCapStepBaseSchema.extend(continueOnErrorShape).strict().refine((step) => step.target !== void 0 || step.deviceId !== void 0, {
|
|
45157
|
+
message: "a cap step needs a target",
|
|
45158
|
+
path: ["target"]
|
|
45159
|
+
}).refine((step) => step.target?.kind !== "domain" || step.actionId !== void 0, {
|
|
45160
|
+
message: "a domain target needs the vocabulary action it applies to (actionId)",
|
|
45161
|
+
path: ["actionId"]
|
|
45162
|
+
}),
|
|
45163
|
+
AutomationStopStepSchema.omit({ if: true }).strict(),
|
|
45164
|
+
AutomationSetStepSchema.omit({ if: true }).strict(),
|
|
45165
|
+
AutomationRunActionStepSchema.strict()
|
|
45166
|
+
];
|
|
45167
|
+
/**
|
|
45168
|
+
* A plain step: a flow step with no condition of its own and no step inside
|
|
45169
|
+
* it. The second (last) level of nesting holds these only.
|
|
45170
|
+
*/
|
|
45171
|
+
var AutomationBranchStepSchema = discriminatedUnion("kind", plainStepSchemas, { error: nestTooDeep });
|
|
45172
|
+
/** A step that can run beside others: it writes no variable and does not end the run. */
|
|
45173
|
+
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");
|
|
45174
|
+
/**
|
|
45175
|
+
* Several steps at once (#500): all are started together and all are awaited.
|
|
45176
|
+
* A failure fails the run once the others finished, as in a sequence; a member
|
|
45177
|
+
* marked "continue if this fails" does not.
|
|
45178
|
+
*/
|
|
45179
|
+
var AutomationParallelStepSchema = object({
|
|
45180
|
+
kind: literal("parallel"),
|
|
45181
|
+
steps: array(AutomationParallelMemberSchema).min(2, "a parallel needs at least two steps").max(5, `a parallel takes at most 5 steps`)
|
|
45182
|
+
}).strict();
|
|
45183
|
+
var REPEAT_NEEDS_MAX = "a repeat on a condition needs a maximum number of iterations";
|
|
45184
|
+
/** A loop on a condition: the bound is mandatory, so a run never loops without an end. */
|
|
45185
|
+
var repeatConditionShape = {
|
|
45186
|
+
when: AutomationWhenSchema,
|
|
45187
|
+
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`),
|
|
45188
|
+
/** A pause between two iterations, never after the last. */
|
|
45189
|
+
waitS: number$1().int().min(1).max(WAIT_MAX_SECONDS, WAIT_TOO_LONG).optional()
|
|
45190
|
+
};
|
|
45191
|
+
/** What decides how many times a `repeat` runs. */
|
|
45192
|
+
var AutomationRepeatLoopSchema = discriminatedUnion("by", [
|
|
45193
|
+
object({
|
|
45194
|
+
by: literal("count"),
|
|
45195
|
+
count: number$1().int().min(1).max(20, `a repeat runs at most 20 times`)
|
|
45196
|
+
}).strict(),
|
|
45197
|
+
object({
|
|
45198
|
+
by: literal("while"),
|
|
45199
|
+
...repeatConditionShape
|
|
45200
|
+
}).strict(),
|
|
45201
|
+
object({
|
|
45202
|
+
by: literal("until"),
|
|
45203
|
+
...repeatConditionShape
|
|
45204
|
+
}).strict(),
|
|
45205
|
+
object({
|
|
45206
|
+
by: literal("forEach"),
|
|
45207
|
+
target: object({
|
|
45208
|
+
kind: literal("devices"),
|
|
45209
|
+
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`)
|
|
45210
|
+
}).strict()
|
|
45211
|
+
}).strict()
|
|
45212
|
+
]);
|
|
45213
|
+
/**
|
|
45214
|
+
* The same steps several times (#500). Every form is bounded at save, so a
|
|
45215
|
+
* run cannot loop without an end. A failure or a `stop` in the steps ends the
|
|
45216
|
+
* loop and the run, as in a sequence.
|
|
45217
|
+
*/
|
|
45218
|
+
function repeatSchemaOf(step) {
|
|
45219
|
+
return object({
|
|
45220
|
+
kind: literal("repeat"),
|
|
45221
|
+
loop: AutomationRepeatLoopSchema,
|
|
45222
|
+
steps: array(step).min(1, "a repeat needs at least one step").max(10, `a repeat takes at most 10 steps`)
|
|
45223
|
+
}).strict();
|
|
45224
|
+
}
|
|
45225
|
+
/**
|
|
45226
|
+
* One step, several ways (#500): the branches are read in order and the FIRST
|
|
45227
|
+
* whose condition is true runs; `else` runs when every branch is false. A
|
|
45228
|
+
* branch that cannot be read ends the choice: nothing runs, not the branches
|
|
45229
|
+
* after it and not the else (D49). The #472 `if` / `else` of a wait or cap
|
|
45230
|
+
* step is the one-branch case and stays as it is stored.
|
|
45231
|
+
*/
|
|
45232
|
+
function chooseSchemaOf(step) {
|
|
45233
|
+
const steps = array(step).max(10);
|
|
45234
|
+
return object({
|
|
45235
|
+
kind: literal("choose"),
|
|
45236
|
+
branches: array(object({
|
|
45237
|
+
if: AutomationWhenSchema,
|
|
45238
|
+
steps: steps.min(1, "a branch needs at least one step")
|
|
45239
|
+
})).min(1, "a choose needs at least one branch").max(6, `a choose takes at most 6 branches`),
|
|
45240
|
+
else: steps.optional()
|
|
45241
|
+
});
|
|
45242
|
+
}
|
|
45243
|
+
var AutomationBodyStepSchema = discriminatedUnion("kind", [
|
|
45244
|
+
...plainStepSchemas,
|
|
45245
|
+
AutomationWaitUntilStepSchema,
|
|
45246
|
+
AutomationParallelStepSchema,
|
|
45247
|
+
chooseSchemaOf(AutomationBranchStepSchema),
|
|
45248
|
+
repeatSchemaOf(AutomationBranchStepSchema)
|
|
45249
|
+
]);
|
|
45250
|
+
var AutomationRepeatStepSchema = repeatSchemaOf(AutomationBodyStepSchema);
|
|
44708
45251
|
var AutomationActionSchema = discriminatedUnion("kind", [
|
|
44709
45252
|
GuardedWaitStepSchema,
|
|
44710
|
-
|
|
44711
|
-
|
|
44712
|
-
|
|
44713
|
-
|
|
44714
|
-
|
|
44715
|
-
|
|
44716
|
-
|
|
44717
|
-
|
|
44718
|
-
number$1(),
|
|
44719
|
-
boolean()
|
|
44720
|
-
])).optional()
|
|
44721
|
-
}),
|
|
45253
|
+
AutomationGuardedCapStepSchema,
|
|
45254
|
+
AutomationStopStepSchema,
|
|
45255
|
+
AutomationSetStepSchema,
|
|
45256
|
+
chooseSchemaOf(AutomationBodyStepSchema),
|
|
45257
|
+
AutomationWaitUntilStepSchema,
|
|
45258
|
+
AutomationParallelStepSchema,
|
|
45259
|
+
AutomationRepeatStepSchema,
|
|
45260
|
+
AutomationRunActionStepSchema,
|
|
44722
45261
|
object({
|
|
44723
45262
|
kind: literal("code"),
|
|
44724
45263
|
/** Compiled into the automation's OWN block by esbuild — not a third
|
|
@@ -44781,11 +45320,6 @@ function convertLegacyRecipe(legacy) {
|
|
|
44781
45320
|
...rest
|
|
44782
45321
|
};
|
|
44783
45322
|
}
|
|
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
45323
|
var AutomationRecipeSchema = union([CurrentAutomationRecipeSchema, LegacyAutomationRecipeSchema.transform(convertLegacyRecipe)]);
|
|
44790
45324
|
var DETECTION_DESCRIPTIONS = {
|
|
44791
45325
|
deviceId: "the camera that saw it",
|
|
@@ -44801,6 +45335,21 @@ var STATIONARY_DESCRIPTIONS = {
|
|
|
44801
45335
|
phase: "appeared or departed",
|
|
44802
45336
|
entryId: "the stopped object"
|
|
44803
45337
|
};
|
|
45338
|
+
var BECOMES_DESCRIPTIONS = {
|
|
45339
|
+
deviceId: "the device that changed",
|
|
45340
|
+
from: "the value before the change, empty when it is not known",
|
|
45341
|
+
to: "the value it changed to"
|
|
45342
|
+
};
|
|
45343
|
+
/**
|
|
45344
|
+
* A fire's payload is offered too, as `data.<path>` (`{{vars.data.channel}}`):
|
|
45345
|
+
* those names depend on the device, so they are not in this list.
|
|
45346
|
+
*/
|
|
45347
|
+
var FIRED_DESCRIPTIONS = {
|
|
45348
|
+
deviceId: "the device that fired",
|
|
45349
|
+
eventType: "the event, as the device names it",
|
|
45350
|
+
sourceId: "the part of the device that fired (a button), empty when it names none",
|
|
45351
|
+
eventKind: "the kind of press (press, long-press …), empty when the device declares none"
|
|
45352
|
+
};
|
|
44804
45353
|
function describe$1(descriptions) {
|
|
44805
45354
|
return Object.entries(descriptions).map(([name, description]) => ({
|
|
44806
45355
|
name,
|
|
@@ -44809,8 +45358,74 @@ function describe$1(descriptions) {
|
|
|
44809
45358
|
}
|
|
44810
45359
|
describe$1(DETECTION_DESCRIPTIONS);
|
|
44811
45360
|
describe$1(STATIONARY_DESCRIPTIONS);
|
|
45361
|
+
describe$1(FIRED_DESCRIPTIONS).map((v) => v.name === "deviceId" ? {
|
|
45362
|
+
...v,
|
|
45363
|
+
hintKey: "firedDeviceId"
|
|
45364
|
+
} : v);
|
|
45365
|
+
describe$1(BECOMES_DESCRIPTIONS).map((v) => v.name === "deviceId" ? {
|
|
45366
|
+
...v,
|
|
45367
|
+
hintKey: "becomesDeviceId"
|
|
45368
|
+
} : v);
|
|
44812
45369
|
new Set(Object.values(COCO_TO_MACRO.mapping));
|
|
44813
45370
|
/**
|
|
45371
|
+
* Generic stateless event emitter. Installed on a `DeviceType.EventEmitter`
|
|
45372
|
+
* device. Carries the device's EXACT declared event vocabulary verbatim
|
|
45373
|
+
* (NO normalization). Two shapes:
|
|
45374
|
+
* - structured source (HA `event.*` entity) → `vocabulary` is the fixed
|
|
45375
|
+
* declared set (e.g. press_short, press_long, …).
|
|
45376
|
+
* - generic/legacy source (HA bus events, e.g. zha_event) → `vocabulary`
|
|
45377
|
+
* is [] (open) and `lastEvent.data` carries the complex payload.
|
|
45378
|
+
* `vocabulary` (D1044) names each event for a person and types it for a
|
|
45379
|
+
* rule; `eventTypes` is its list of native tokens, kept for older readers.
|
|
45380
|
+
* A fire names the part of the device it came from in `sourceId` when the
|
|
45381
|
+
* vocabulary declares sources (a remote's buttons).
|
|
45382
|
+
* `seq` is monotonic so two identical events still produce distinct slice writes.
|
|
45383
|
+
*/
|
|
45384
|
+
var EventFireSchema = object({
|
|
45385
|
+
deviceId: number$1(),
|
|
45386
|
+
eventType: string(),
|
|
45387
|
+
sourceId: string().optional(),
|
|
45388
|
+
data: record(string(), unknown()).nullable(),
|
|
45389
|
+
timestamp: number$1().meta(readingMeta("none")),
|
|
45390
|
+
seq: number$1().meta(readingMeta("count"))
|
|
45391
|
+
});
|
|
45392
|
+
var EventEmitterStatusSchema = object({
|
|
45393
|
+
eventTypes: array(string()),
|
|
45394
|
+
/**
|
|
45395
|
+
* Required of a provider by the type. A status written before the field
|
|
45396
|
+
* existed still parses, as an empty vocabulary: read it through
|
|
45397
|
+
* `readEventVocabulary`, which then answers with the plain tokens.
|
|
45398
|
+
*/
|
|
45399
|
+
vocabulary: array(EventVocabularyEntrySchema).default([]),
|
|
45400
|
+
lastEvent: EventFireSchema.nullable(),
|
|
45401
|
+
eventCountSinceStart: number$1().meta(readingMeta("count"))
|
|
45402
|
+
});
|
|
45403
|
+
var eventEmitterCapability = {
|
|
45404
|
+
name: "event-emitter",
|
|
45405
|
+
scope: "device",
|
|
45406
|
+
deviceNative: true,
|
|
45407
|
+
mode: "singleton",
|
|
45408
|
+
deviceTypes: [DeviceType.EventEmitter],
|
|
45409
|
+
methods: {},
|
|
45410
|
+
events: { onEvent: { data: EventFireSchema } },
|
|
45411
|
+
status: {
|
|
45412
|
+
schema: EventEmitterStatusSchema,
|
|
45413
|
+
kind: "push"
|
|
45414
|
+
},
|
|
45415
|
+
runtimeState: EventEmitterStatusSchema,
|
|
45416
|
+
/**
|
|
45417
|
+
* Runtime-state durability: **session** — `eventCountSinceStart` names its own scope.
|
|
45418
|
+
*
|
|
45419
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
45420
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
45421
|
+
*/
|
|
45422
|
+
durability: "session",
|
|
45423
|
+
/**
|
|
45424
|
+
* History (D850): none — `eventCountSinceStart` resets with the addon; events live in ledgers.
|
|
45425
|
+
*/
|
|
45426
|
+
history: "none"
|
|
45427
|
+
};
|
|
45428
|
+
/**
|
|
44814
45429
|
* Schedulers (D983) — an entity that performs actions over the day.
|
|
44815
45430
|
*
|
|
44816
45431
|
* A scheduler has profiles; a profile has weekdays and a timeline of POINTS.
|
|
@@ -44833,6 +45448,13 @@ var SchedulerStateTreeSchema = AutomationWhenSchema.superRefine((when, ctx) => {
|
|
|
44833
45448
|
});
|
|
44834
45449
|
return;
|
|
44835
45450
|
}
|
|
45451
|
+
if (hasTriggeredByLeaves(when)) {
|
|
45452
|
+
ctx.addIssue({
|
|
45453
|
+
code: "custom",
|
|
45454
|
+
message: TRIGGERED_BY_NEEDS_AUTOMATION
|
|
45455
|
+
});
|
|
45456
|
+
return;
|
|
45457
|
+
}
|
|
44836
45458
|
const problem = validateWhenTree(when);
|
|
44837
45459
|
if (problem !== null) ctx.addIssue({
|
|
44838
45460
|
code: "custom",
|
|
@@ -44854,12 +45476,16 @@ var HoldOverrideSchema = discriminatedUnion("mode", [object({ mode: literal("res
|
|
|
44854
45476
|
})]);
|
|
44855
45477
|
/** A missed edge up to this late still runs. */
|
|
44856
45478
|
var DEFAULT_MAX_LATE_MS = 10 * 6e4;
|
|
45479
|
+
var SchedulerStepSchema = ActionStepSchema.refine((step) => step.kind !== "wait" || step.seconds <= 300, {
|
|
45480
|
+
message: `a scheduler point waits at most five minutes (300 s); put a longer wait in an action`,
|
|
45481
|
+
path: ["seconds"]
|
|
45482
|
+
});
|
|
44857
45483
|
var SchedulerPointSchema = object({
|
|
44858
45484
|
id: string().min(1).max(64),
|
|
44859
45485
|
at: SchedulerTimeSpecSchema,
|
|
44860
45486
|
mode: SchedulerPointModeSchema,
|
|
44861
45487
|
/** The actions of an `edge` or `hold`; a `release` has none. */
|
|
44862
|
-
steps: array(
|
|
45488
|
+
steps: array(SchedulerStepSchema).max(20).default([]),
|
|
44863
45489
|
/** Checked when the point runs (an edge) or is applied (a hold). */
|
|
44864
45490
|
conditions: SchedulerStateTreeSchema.optional(),
|
|
44865
45491
|
/** Hold only; absent means `respect`. */
|
|
@@ -45038,7 +45664,9 @@ var AutomationTriggerTraceSchema = object({
|
|
|
45038
45664
|
/** Why an event was rejected ("class person not in [dog]") or what fired. */
|
|
45039
45665
|
reason: string(),
|
|
45040
45666
|
/** deviceId, className, trackId, score … — only what the trigger exposes. */
|
|
45041
|
-
vars: record(string(), TraceValueSchema)
|
|
45667
|
+
vars: record(string(), TraceValueSchema),
|
|
45668
|
+
/** A hold (`for`) this occurrence went through: pending, cancelled, fired or re-seeded. */
|
|
45669
|
+
hold: HoldTraceSchema.optional()
|
|
45042
45670
|
});
|
|
45043
45671
|
var AutomationConditionTraceNodeSchema = object({
|
|
45044
45672
|
/** `when` for the root, `when.<i>` for its i-th child, … (`conditions…` on an old trace). */
|
|
@@ -45051,8 +45679,11 @@ var AutomationConditionTraceNodeSchema = object({
|
|
|
45051
45679
|
"device-is",
|
|
45052
45680
|
"expression",
|
|
45053
45681
|
"stationary-present",
|
|
45682
|
+
"time-is",
|
|
45683
|
+
"triggered-by",
|
|
45054
45684
|
"device-becomes",
|
|
45055
45685
|
"device-event",
|
|
45686
|
+
"device-fired",
|
|
45056
45687
|
"schedule",
|
|
45057
45688
|
"interval",
|
|
45058
45689
|
"manual",
|
|
@@ -45123,18 +45754,46 @@ var StepTraceBaseSchema = object({
|
|
|
45123
45754
|
]).optional(),
|
|
45124
45755
|
/** Why the condition read what it read, in the evaluator's own words. */
|
|
45125
45756
|
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. */
|
|
45757
|
+
/** `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
45758
|
outcome: _enum([
|
|
45128
45759
|
"ok",
|
|
45129
45760
|
"error",
|
|
45130
45761
|
"not-run",
|
|
45131
45762
|
"empty",
|
|
45132
|
-
"skipped"
|
|
45763
|
+
"skipped",
|
|
45764
|
+
"failed-continued",
|
|
45765
|
+
"timed-out"
|
|
45133
45766
|
]),
|
|
45134
45767
|
message: string().optional()
|
|
45135
45768
|
});
|
|
45769
|
+
/** What a block step adds to its row: which list ran, what each branch read, how many rounds. */
|
|
45770
|
+
var blockTraceShape = {
|
|
45771
|
+
/** A `choose` step (#500): the list that ran, `branches.<n>` or `else`. Absent when none did. */
|
|
45772
|
+
branch: string().optional(),
|
|
45773
|
+
/** A `choose` step: what each branch read, in order, up to the one that decided. */
|
|
45774
|
+
branchChecks: array(object({
|
|
45775
|
+
condition: _enum([
|
|
45776
|
+
"true",
|
|
45777
|
+
"false",
|
|
45778
|
+
"unknown"
|
|
45779
|
+
]),
|
|
45780
|
+
conditionReason: string().optional()
|
|
45781
|
+
})).optional(),
|
|
45782
|
+
/** A `repeat` step (#500): how many times its steps ran. */
|
|
45783
|
+
iterations: number$1().int().optional()
|
|
45784
|
+
};
|
|
45785
|
+
/** A step one level down: it may be a block step itself, whose own steps are plain rows (the nesting ends there). */
|
|
45786
|
+
var NestedStepTraceSchema = StepTraceBaseSchema.extend({
|
|
45787
|
+
...blockTraceShape,
|
|
45788
|
+
steps: array(StepTraceBaseSchema).optional()
|
|
45789
|
+
});
|
|
45136
45790
|
/** 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({
|
|
45791
|
+
var AutomationStepTraceSchema = StepTraceBaseSchema.extend({
|
|
45792
|
+
else: array(StepTraceBaseSchema).optional(),
|
|
45793
|
+
...blockTraceShape,
|
|
45794
|
+
/** A `choose` step: the steps of the list that ran. A `parallel`: its members. A `repeat`: the LAST iteration. */
|
|
45795
|
+
steps: array(NestedStepTraceSchema).optional()
|
|
45796
|
+
});
|
|
45138
45797
|
var AutomationRunTraceSchema = object({
|
|
45139
45798
|
runId: string().min(1),
|
|
45140
45799
|
startedAt: number$1(),
|
|
@@ -45384,7 +46043,7 @@ object({
|
|
|
45384
46043
|
* is irrelevant; the array is treated as a set by consumers. */
|
|
45385
46044
|
hiddenChildIds: array(number$1()).readonly()
|
|
45386
46045
|
});
|
|
45387
|
-
DeviceType.Camera, DeviceType.
|
|
46046
|
+
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
46047
|
deviceId: number$1().int().nonnegative(),
|
|
45389
46048
|
childDeviceId: number$1().int().nonnegative(),
|
|
45390
46049
|
hidden: boolean()
|
|
@@ -45541,7 +46200,9 @@ var AddonHttpRouteSchema = object({
|
|
|
45541
46200
|
]).optional(),
|
|
45542
46201
|
description: string().optional(),
|
|
45543
46202
|
/** The hub limits this route per client address before the handler runs. */
|
|
45544
|
-
rateLimit: _enum(["auth"]).optional()
|
|
46203
|
+
rateLimit: _enum(["auth"]).optional(),
|
|
46204
|
+
/** Served once per integration instance; the handler reads `instanceId`. */
|
|
46205
|
+
perInstance: boolean().optional()
|
|
45545
46206
|
});
|
|
45546
46207
|
/**
|
|
45547
46208
|
* Cross-process route invocation envelope. The hub captures the
|
|
@@ -45558,6 +46219,8 @@ var InvokeRequestSchema = object({
|
|
|
45558
46219
|
query: record(string(), string()),
|
|
45559
46220
|
body: unknown(),
|
|
45560
46221
|
headers: record(string(), string()),
|
|
46222
|
+
/** The instance the hub resolved for a `perInstance` route. */
|
|
46223
|
+
instanceId: string().optional(),
|
|
45561
46224
|
user: object({
|
|
45562
46225
|
id: string(),
|
|
45563
46226
|
username: string(),
|
|
@@ -47682,7 +48345,7 @@ var BATTERY_DEVICE_TYPES = [
|
|
|
47682
48345
|
DeviceType.Switch,
|
|
47683
48346
|
DeviceType.PetFeeder,
|
|
47684
48347
|
DeviceType.Container,
|
|
47685
|
-
DeviceType.
|
|
48348
|
+
DeviceType.Climate,
|
|
47686
48349
|
DeviceType.EventEmitter
|
|
47687
48350
|
];
|
|
47688
48351
|
/** Generic Customize target for the percentage reading (D936). */
|
|
@@ -49445,7 +50108,14 @@ var climateControlCapability = {
|
|
|
49445
50108
|
scope: "device",
|
|
49446
50109
|
deviceNative: true,
|
|
49447
50110
|
mode: "singleton",
|
|
49448
|
-
deviceTypes: [DeviceType.
|
|
50111
|
+
deviceTypes: [DeviceType.Climate],
|
|
50112
|
+
power: {
|
|
50113
|
+
kind: "mode",
|
|
50114
|
+
field: "mode",
|
|
50115
|
+
offValue: "off",
|
|
50116
|
+
set: "setMode",
|
|
50117
|
+
lastField: "lastActiveMode"
|
|
50118
|
+
},
|
|
49449
50119
|
methods: {
|
|
49450
50120
|
setMode: method(object({
|
|
49451
50121
|
deviceId: number$1().int().nonnegative(),
|
|
@@ -49830,6 +50500,7 @@ var CompositionProblemSchema = object({
|
|
|
49830
50500
|
"nested-field",
|
|
49831
50501
|
"missing-field",
|
|
49832
50502
|
"kind-mismatch",
|
|
50503
|
+
"source-kind-mismatch",
|
|
49833
50504
|
"may-be-null",
|
|
49834
50505
|
"source-missing",
|
|
49835
50506
|
"source-unreadable",
|
|
@@ -49851,7 +50522,8 @@ var CompositionProblemSchema = object({
|
|
|
49851
50522
|
"command-method-unknown",
|
|
49852
50523
|
"command-source-unknown",
|
|
49853
50524
|
"command-input-unknown",
|
|
49854
|
-
"command-input-missing"
|
|
50525
|
+
"command-input-missing",
|
|
50526
|
+
"power-source-is-actuator"
|
|
49855
50527
|
]),
|
|
49856
50528
|
path: string(),
|
|
49857
50529
|
message: string()
|
|
@@ -50533,11 +51205,13 @@ var IrCodecError = class extends Error {
|
|
|
50533
51205
|
this.code = code;
|
|
50534
51206
|
}
|
|
50535
51207
|
};
|
|
51208
|
+
/** Maximum number of mark/space entries one pulse train may carry. */
|
|
51209
|
+
var IR_TIMINGS_MAX = 1024;
|
|
50536
51210
|
/**
|
|
50537
51211
|
* A pulse train in signed microseconds: mark (+) first, then space (-), strictly
|
|
50538
51212
|
* alternating, no zeros, 2..1024 entries.
|
|
50539
51213
|
*/
|
|
50540
|
-
var IrTimingsSchema = array(number$1().int()).min(2).max(
|
|
51214
|
+
var IrTimingsSchema = array(number$1().int()).min(2).max(IR_TIMINGS_MAX).superRefine((timings, ctx) => {
|
|
50541
51215
|
timings.forEach((value, index) => {
|
|
50542
51216
|
const expectMark = index % 2 === 0;
|
|
50543
51217
|
if (value === 0 || value > 0 !== expectMark) ctx.addIssue({
|
|
@@ -50584,15 +51258,186 @@ function decodePronto(code) {
|
|
|
50584
51258
|
carrierHz: Math.round(1e6 / unitUs)
|
|
50585
51259
|
};
|
|
50586
51260
|
}
|
|
50587
|
-
/**
|
|
50588
|
-
|
|
50589
|
-
|
|
50590
|
-
|
|
50591
|
-
|
|
50592
|
-
|
|
50593
|
-
|
|
50594
|
-
|
|
50595
|
-
|
|
51261
|
+
/**
|
|
51262
|
+
* FastLZ level 1: a byte-oriented LZ77 stream. A control byte below 32 starts a
|
|
51263
|
+
* literal run of `ctrl + 1` bytes; otherwise it is a back-reference whose length
|
|
51264
|
+
* is `ctrl >> 5` (extended by one byte when 7) and whose distance is split
|
|
51265
|
+
* between the low 5 bits and the next byte.
|
|
51266
|
+
*/
|
|
51267
|
+
var MAX_DISTANCE = 8192;
|
|
51268
|
+
var MAX_LITERAL_RUN = 32;
|
|
51269
|
+
var MIN_MATCH = 3;
|
|
51270
|
+
var MAX_MATCH = 264;
|
|
51271
|
+
/**
|
|
51272
|
+
* Most bytes a decoded IR code may hold: two bytes per timing. A level-1 stream
|
|
51273
|
+
* can expand ~130x, so the decoder checks this bound before every write.
|
|
51274
|
+
*/
|
|
51275
|
+
var IR_DECOMPRESSED_MAX = IR_TIMINGS_MAX * 2;
|
|
51276
|
+
function fastlzDecompress(input) {
|
|
51277
|
+
const out = [];
|
|
51278
|
+
let ip = 0;
|
|
51279
|
+
const room = (count) => {
|
|
51280
|
+
if (out.length + count > IR_DECOMPRESSED_MAX) throw new IrCodecError("tuya-malformed", "decompressed code is larger than any IR train");
|
|
51281
|
+
};
|
|
51282
|
+
const next = () => {
|
|
51283
|
+
const byte = input[ip++];
|
|
51284
|
+
if (byte === void 0) throw new IrCodecError("tuya-malformed", "compressed stream is truncated");
|
|
51285
|
+
return byte;
|
|
51286
|
+
};
|
|
51287
|
+
while (ip < input.length) {
|
|
51288
|
+
const ctrl = next();
|
|
51289
|
+
if (ctrl < 32) {
|
|
51290
|
+
room(ctrl + 1);
|
|
51291
|
+
for (let i = 0; i <= ctrl; i++) out.push(next());
|
|
51292
|
+
continue;
|
|
51293
|
+
}
|
|
51294
|
+
let len = (ctrl >> 5) - 1;
|
|
51295
|
+
if (len === 6) len += next();
|
|
51296
|
+
const distance = ((ctrl & 31) << 8) + next() + 1;
|
|
51297
|
+
const start = out.length - distance;
|
|
51298
|
+
if (start < 0) throw new IrCodecError("tuya-malformed", "back-reference points before the stream");
|
|
51299
|
+
room(len + 3);
|
|
51300
|
+
for (let i = 0; i < len + 3; i++) {
|
|
51301
|
+
const byte = out[start + i];
|
|
51302
|
+
if (byte === void 0) throw new IrCodecError("tuya-malformed", "back-reference is invalid");
|
|
51303
|
+
out.push(byte);
|
|
51304
|
+
}
|
|
51305
|
+
}
|
|
51306
|
+
return Uint8Array.from(out);
|
|
51307
|
+
}
|
|
51308
|
+
function fastlzCompress(input) {
|
|
51309
|
+
const out = [];
|
|
51310
|
+
const lastSeen = /* @__PURE__ */ new Map();
|
|
51311
|
+
let literalStart = 0;
|
|
51312
|
+
const flushLiterals = (end) => {
|
|
51313
|
+
for (let from = literalStart; from < end; from += MAX_LITERAL_RUN) {
|
|
51314
|
+
const to = Math.min(from + MAX_LITERAL_RUN, end);
|
|
51315
|
+
out.push(to - from - 1);
|
|
51316
|
+
for (let i = from; i < to; i++) out.push(input[i] ?? 0);
|
|
51317
|
+
}
|
|
51318
|
+
};
|
|
51319
|
+
let pos = 0;
|
|
51320
|
+
while (pos < input.length) {
|
|
51321
|
+
let matchLen = 0;
|
|
51322
|
+
let matchDistance = 0;
|
|
51323
|
+
if (pos + MIN_MATCH <= input.length) {
|
|
51324
|
+
const key = (input[pos] ?? 0) << 16 | (input[pos + 1] ?? 0) << 8 | (input[pos + 2] ?? 0);
|
|
51325
|
+
const candidate = lastSeen.get(key);
|
|
51326
|
+
lastSeen.set(key, pos);
|
|
51327
|
+
if (candidate !== void 0 && pos - candidate <= MAX_DISTANCE) {
|
|
51328
|
+
let len = 0;
|
|
51329
|
+
while (pos + len < input.length && len < MAX_MATCH && input[candidate + len] === input[pos + len]) len++;
|
|
51330
|
+
if (len >= MIN_MATCH) {
|
|
51331
|
+
matchLen = len;
|
|
51332
|
+
matchDistance = pos - candidate - 1;
|
|
51333
|
+
}
|
|
51334
|
+
}
|
|
51335
|
+
}
|
|
51336
|
+
if (matchLen === 0) {
|
|
51337
|
+
pos++;
|
|
51338
|
+
continue;
|
|
51339
|
+
}
|
|
51340
|
+
flushLiterals(pos);
|
|
51341
|
+
const enc = matchLen - 2;
|
|
51342
|
+
if (enc < 7) out.push(enc << 5 | matchDistance >> 8);
|
|
51343
|
+
else out.push(224 | matchDistance >> 8, enc - 7);
|
|
51344
|
+
out.push(matchDistance & 255);
|
|
51345
|
+
pos += matchLen;
|
|
51346
|
+
literalStart = pos;
|
|
51347
|
+
}
|
|
51348
|
+
flushLiterals(input.length);
|
|
51349
|
+
return Uint8Array.from(out);
|
|
51350
|
+
}
|
|
51351
|
+
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
51352
|
+
var MAX_DURATION_US = 65535;
|
|
51353
|
+
function toBase64(bytes) {
|
|
51354
|
+
let out = "";
|
|
51355
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
51356
|
+
const a = bytes[i] ?? 0;
|
|
51357
|
+
const b = bytes[i + 1] ?? 0;
|
|
51358
|
+
const c = bytes[i + 2] ?? 0;
|
|
51359
|
+
const word = a << 16 | b << 8 | c;
|
|
51360
|
+
out += ALPHABET[word >> 18 & 63];
|
|
51361
|
+
out += ALPHABET[word >> 12 & 63];
|
|
51362
|
+
out += i + 1 < bytes.length ? ALPHABET[word >> 6 & 63] : "=";
|
|
51363
|
+
out += i + 2 < bytes.length ? ALPHABET[word & 63] : "=";
|
|
51364
|
+
}
|
|
51365
|
+
return out;
|
|
51366
|
+
}
|
|
51367
|
+
function fromBase64(text) {
|
|
51368
|
+
const clean = text.trim().replace(/=+$/, "");
|
|
51369
|
+
if (clean.length === 0 || clean.length % 4 === 1) throw new IrCodecError("tuya-malformed", "not a base64 string");
|
|
51370
|
+
const out = [];
|
|
51371
|
+
let bits = 0;
|
|
51372
|
+
let acc = 0;
|
|
51373
|
+
for (const ch of clean) {
|
|
51374
|
+
const value = ALPHABET.indexOf(ch);
|
|
51375
|
+
if (value < 0) throw new IrCodecError("tuya-malformed", "not a base64 string");
|
|
51376
|
+
acc = acc << 6 | value;
|
|
51377
|
+
bits += 6;
|
|
51378
|
+
if (bits >= 8) {
|
|
51379
|
+
bits -= 8;
|
|
51380
|
+
out.push(acc >> bits & 255);
|
|
51381
|
+
}
|
|
51382
|
+
}
|
|
51383
|
+
return Uint8Array.from(out);
|
|
51384
|
+
}
|
|
51385
|
+
/** Signed pulse train to a Tuya IR code: base64(FastLZ(uint16 LE durations)). */
|
|
51386
|
+
function encodeTuya(timings) {
|
|
51387
|
+
const bytes = new Uint8Array(timings.length * 2);
|
|
51388
|
+
timings.forEach((value, index) => {
|
|
51389
|
+
const duration = Math.abs(value);
|
|
51390
|
+
if (duration === 0 || duration > MAX_DURATION_US) throw new IrCodecError("invalid-timings", `timing ${index} does not fit 16 bits`);
|
|
51391
|
+
bytes[index * 2] = duration & 255;
|
|
51392
|
+
bytes[index * 2 + 1] = duration >> 8;
|
|
51393
|
+
});
|
|
51394
|
+
return toBase64(fastlzCompress(bytes));
|
|
51395
|
+
}
|
|
51396
|
+
/** A Tuya IR code to a signed pulse train (mark first). Throws `IrCodecError`. */
|
|
51397
|
+
function decodeTuya(code) {
|
|
51398
|
+
const bytes = fastlzDecompress(fromBase64(code));
|
|
51399
|
+
if (bytes.length === 0 || bytes.length % 2 !== 0) throw new IrCodecError("tuya-malformed", "payload is not a list of 16-bit durations");
|
|
51400
|
+
const timings = [];
|
|
51401
|
+
for (let i = 0; i < bytes.length; i += 2) {
|
|
51402
|
+
const duration = (bytes[i] ?? 0) | (bytes[i + 1] ?? 0) << 8;
|
|
51403
|
+
timings.push(i / 2 % 2 === 0 ? duration : -duration);
|
|
51404
|
+
}
|
|
51405
|
+
const checked = IrTimingsSchema.safeParse(timings);
|
|
51406
|
+
if (!checked.success) throw new IrCodecError("tuya-malformed", checked.error.message);
|
|
51407
|
+
return checked.data;
|
|
51408
|
+
}
|
|
51409
|
+
/** Device-native IR code formats a transmitter can replay verbatim. */
|
|
51410
|
+
var IrNativeFormatSchema = _enum(["tuya"]);
|
|
51411
|
+
/**
|
|
51412
|
+
* Longest native code accepted: base64 of the worst-case compressed stream
|
|
51413
|
+
* (every decoded byte as a literal, plus one control byte per 32), with margin.
|
|
51414
|
+
*/
|
|
51415
|
+
var IR_NATIVE_CODE_MAX_CHARS = Math.ceil((IR_DECOMPRESSED_MAX + Math.ceil(IR_DECOMPRESSED_MAX / 32)) / 3) * 4 + 256;
|
|
51416
|
+
var IrNativeCodeSchema = object({
|
|
51417
|
+
format: IrNativeFormatSchema,
|
|
51418
|
+
code: string().min(1).max(IR_NATIVE_CODE_MAX_CHARS)
|
|
51419
|
+
});
|
|
51420
|
+
var IR_NATIVE_CODECS = { tuya: {
|
|
51421
|
+
encode: encodeTuya,
|
|
51422
|
+
decode: decodeTuya
|
|
51423
|
+
} };
|
|
51424
|
+
/** What a remote command stores: a raw pulse train, a learned Pronto code, or a device-native code. */
|
|
51425
|
+
var IrPayloadSchema = discriminatedUnion("kind", [
|
|
51426
|
+
object({
|
|
51427
|
+
kind: literal("raw"),
|
|
51428
|
+
timings: array(number$1().int()),
|
|
51429
|
+
settings: TransmitSettingsSchema.optional()
|
|
51430
|
+
}),
|
|
51431
|
+
object({
|
|
51432
|
+
kind: literal("pronto"),
|
|
51433
|
+
code: string()
|
|
51434
|
+
}),
|
|
51435
|
+
object({
|
|
51436
|
+
kind: literal("native"),
|
|
51437
|
+
format: IrNativeFormatSchema,
|
|
51438
|
+
code: string()
|
|
51439
|
+
})
|
|
51440
|
+
]);
|
|
50596
51441
|
/** Resolve a payload to what `ir-transmitter.send` takes. Throws `IrCodecError`. */
|
|
50597
51442
|
function toTimings(payload) {
|
|
50598
51443
|
if (payload.kind === "pronto") {
|
|
@@ -50602,6 +51447,10 @@ function toTimings(payload) {
|
|
|
50602
51447
|
settings: { carrierHz: decoded.carrierHz }
|
|
50603
51448
|
};
|
|
50604
51449
|
}
|
|
51450
|
+
if (payload.kind === "native") return {
|
|
51451
|
+
timings: IR_NATIVE_CODECS[payload.format].decode(payload.code),
|
|
51452
|
+
settings: {}
|
|
51453
|
+
};
|
|
50605
51454
|
const checked = IrTimingsSchema.safeParse(payload.timings);
|
|
50606
51455
|
if (!checked.success) throw new IrCodecError("invalid-timings", checked.error.message);
|
|
50607
51456
|
return {
|
|
@@ -50889,6 +51738,13 @@ var mediaPlayerCapability = {
|
|
|
50889
51738
|
deviceNative: true,
|
|
50890
51739
|
mode: "singleton",
|
|
50891
51740
|
deviceTypes: [DeviceType.MediaPlayer],
|
|
51741
|
+
power: {
|
|
51742
|
+
kind: "methods",
|
|
51743
|
+
field: "state",
|
|
51744
|
+
offValues: ["off", "standby"],
|
|
51745
|
+
on: "turnOn",
|
|
51746
|
+
off: "turnOff"
|
|
51747
|
+
},
|
|
50892
51748
|
methods: {
|
|
50893
51749
|
turnOn: optionalMethod(deviceOnly, _void(), {
|
|
50894
51750
|
kind: "mutation",
|
|
@@ -51019,7 +51875,7 @@ var mediaPlayerCapability = {
|
|
|
51019
51875
|
isMuted: { kind: "boolean" }
|
|
51020
51876
|
} }
|
|
51021
51877
|
};
|
|
51022
|
-
DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.
|
|
51878
|
+
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
51879
|
var NUMBER$1 = {
|
|
51024
51880
|
kind: "fixed",
|
|
51025
51881
|
result: ["number"]
|
|
@@ -51102,7 +51958,7 @@ function numeric$1(input) {
|
|
|
51102
51958
|
}, ...alsoFrom === void 0 ? [] : [alsoFrom.accept]]
|
|
51103
51959
|
};
|
|
51104
51960
|
}
|
|
51105
|
-
var CLIMATE_DEVICES = [DeviceType.
|
|
51961
|
+
var CLIMATE_DEVICES = [DeviceType.Climate];
|
|
51106
51962
|
function binary(input) {
|
|
51107
51963
|
return {
|
|
51108
51964
|
...input,
|
|
@@ -51424,8 +52280,8 @@ var GROUP_TEMPLATES = [
|
|
|
51424
52280
|
actuator({
|
|
51425
52281
|
id: "climate",
|
|
51426
52282
|
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.
|
|
52283
|
+
deviceType: DeviceType.Climate,
|
|
52284
|
+
memberDeviceTypes: [DeviceType.Climate],
|
|
51429
52285
|
outputCap: "group-active",
|
|
51430
52286
|
outputField: "active",
|
|
51431
52287
|
member: {
|
|
@@ -51468,6 +52324,118 @@ function actuator(input) {
|
|
|
51468
52324
|
new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
|
|
51469
52325
|
DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
|
|
51470
52326
|
DeviceType.Camera;
|
|
52327
|
+
var PowerSourceRefusalReasonSchema = _enum([
|
|
52328
|
+
"no-power-source",
|
|
52329
|
+
"switch-missing",
|
|
52330
|
+
"switch-offline",
|
|
52331
|
+
"switch-unknown",
|
|
52332
|
+
"restart-in-progress",
|
|
52333
|
+
"switch-call-failed",
|
|
52334
|
+
"switch-is-actuator"
|
|
52335
|
+
]);
|
|
52336
|
+
var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
|
|
52337
|
+
accepted: literal(false),
|
|
52338
|
+
reason: PowerSourceRefusalReasonSchema,
|
|
52339
|
+
/** One line for the operator, naming the switch. */
|
|
52340
|
+
message: string()
|
|
52341
|
+
})]);
|
|
52342
|
+
var PowerSourceInfoSchema = object({
|
|
52343
|
+
/** False when the device has no power source linked; the rest is then empty. */
|
|
52344
|
+
linked: boolean(),
|
|
52345
|
+
switchDeviceId: number$1().nullable(),
|
|
52346
|
+
switchName: string().nullable(),
|
|
52347
|
+
/** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
|
|
52348
|
+
switchOn: boolean().nullable(),
|
|
52349
|
+
switchOnline: boolean().nullable(),
|
|
52350
|
+
restartInProgress: boolean(),
|
|
52351
|
+
offSeconds: number$1().int()
|
|
52352
|
+
});
|
|
52353
|
+
/** The composed slice: the switch state the link mirrors, plus the stored off time. */
|
|
52354
|
+
var PowerSourceStateSchema = object({
|
|
52355
|
+
/** Null while the switch has not reported: unknown is never `false` (D393). */
|
|
52356
|
+
switchOn: boolean().nullable(),
|
|
52357
|
+
offSeconds: number$1().int(),
|
|
52358
|
+
lastFetchedAt: number$1()
|
|
52359
|
+
});
|
|
52360
|
+
var POWER_SOURCE_DEVICE_TYPES = [
|
|
52361
|
+
DeviceType.Gateway,
|
|
52362
|
+
DeviceType.Camera,
|
|
52363
|
+
DeviceType.Sensor,
|
|
52364
|
+
DeviceType.MediaPlayer,
|
|
52365
|
+
DeviceType.Vacuum,
|
|
52366
|
+
DeviceType.Generic,
|
|
52367
|
+
DeviceType.Washer,
|
|
52368
|
+
DeviceType.Dryer,
|
|
52369
|
+
DeviceType.Dishwasher,
|
|
52370
|
+
DeviceType.PetFountain,
|
|
52371
|
+
DeviceType.Fan,
|
|
52372
|
+
DeviceType.Humidifier,
|
|
52373
|
+
DeviceType.Climate,
|
|
52374
|
+
DeviceType.WaterHeater
|
|
52375
|
+
];
|
|
52376
|
+
var powerSourceCustomize = {
|
|
52377
|
+
version: 1,
|
|
52378
|
+
targets: [{
|
|
52379
|
+
id: "switch",
|
|
52380
|
+
cardinality: "scalar",
|
|
52381
|
+
vocabulary: {
|
|
52382
|
+
cap: "power-source",
|
|
52383
|
+
stateField: "switchOn"
|
|
52384
|
+
},
|
|
52385
|
+
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
52386
|
+
apply: "add",
|
|
52387
|
+
path: "switchOn",
|
|
52388
|
+
sources: [{
|
|
52389
|
+
kind: "switch",
|
|
52390
|
+
transforms: [{ kind: "identity" }]
|
|
52391
|
+
}],
|
|
52392
|
+
companions: [{
|
|
52393
|
+
kind: "constant",
|
|
52394
|
+
path: "offSeconds",
|
|
52395
|
+
value: 10,
|
|
52396
|
+
when: "add",
|
|
52397
|
+
formerValues: [0]
|
|
52398
|
+
}, {
|
|
52399
|
+
kind: "clock",
|
|
52400
|
+
path: "lastFetchedAt",
|
|
52401
|
+
when: "always"
|
|
52402
|
+
}],
|
|
52403
|
+
sourceReport: { status: "assignment-status" }
|
|
52404
|
+
}]
|
|
52405
|
+
};
|
|
52406
|
+
var deviceInput$2 = object({ deviceId: number$1().int().nonnegative() });
|
|
52407
|
+
var powerSourceCapability = {
|
|
52408
|
+
name: "power-source",
|
|
52409
|
+
scope: "device",
|
|
52410
|
+
mode: "singleton",
|
|
52411
|
+
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
52412
|
+
methods: {
|
|
52413
|
+
getStatus: systemMethod(deviceInput$2, PowerSourceInfoSchema, { auth: "protected" }),
|
|
52414
|
+
powerOn: systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
52415
|
+
kind: "mutation",
|
|
52416
|
+
auth: "protected"
|
|
52417
|
+
}),
|
|
52418
|
+
powerOff: systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
52419
|
+
kind: "mutation",
|
|
52420
|
+
auth: "protected"
|
|
52421
|
+
}),
|
|
52422
|
+
forceRestart: systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
|
|
52423
|
+
kind: "mutation",
|
|
52424
|
+
auth: "protected"
|
|
52425
|
+
})
|
|
52426
|
+
},
|
|
52427
|
+
runtimeState: PowerSourceStateSchema,
|
|
52428
|
+
customize: powerSourceCustomize,
|
|
52429
|
+
/**
|
|
52430
|
+
* Runtime-state durability: **session** — mirrored from the linked switch on
|
|
52431
|
+
* its next report; a restored value would outlive the reading it names.
|
|
52432
|
+
*
|
|
52433
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
52434
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
52435
|
+
*/
|
|
52436
|
+
durability: "session",
|
|
52437
|
+
history: "none"
|
|
52438
|
+
};
|
|
51471
52439
|
`${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
|
|
51472
52440
|
/**
|
|
51473
52441
|
* What a composition may BUILD on a device type (G2, D676): the caps a new
|
|
@@ -52512,27 +53480,35 @@ var deviceAdminLinkCapability = {
|
|
|
52512
53480
|
getAdminLink: method(object({ deviceId: number$1().int().nonnegative() }), DeviceAdminLinkSchema.nullable(), { auth: "admin" }) }
|
|
52513
53481
|
};
|
|
52514
53482
|
/**
|
|
52515
|
-
* device-
|
|
52516
|
-
*
|
|
52517
|
-
*
|
|
52518
|
-
*
|
|
52519
|
-
*
|
|
52520
|
-
*
|
|
52521
|
-
*
|
|
52522
|
-
*
|
|
52523
|
-
*
|
|
52524
|
-
*
|
|
52525
|
-
*
|
|
52526
|
-
*
|
|
52527
|
-
*
|
|
52528
|
-
*
|
|
52529
|
-
*
|
|
52530
|
-
*
|
|
52531
|
-
*
|
|
52532
|
-
|
|
52533
|
-
|
|
52534
|
-
*
|
|
52535
|
-
*
|
|
53483
|
+
* `device-adoption` — generic discovery + adoption surface,
|
|
53484
|
+
* system-scoped collection (one provider per integration addon), routed
|
|
53485
|
+
* by explicit `{ addonId }` on direct `deviceAdoption.*` calls — the same
|
|
53486
|
+
* pin model as `connection-test`. The admin UI keeps using
|
|
53487
|
+
* `deviceManager.adoption*` (which already threads `addonId`). The de-HA-ified
|
|
53488
|
+
* successor to `ha-discovery`:
|
|
53489
|
+
* keyed by `integrationId` (the addon resolves integrationId → broker
|
|
53490
|
+
* internally) rather than a connection-specific `brokerId`, and it
|
|
53491
|
+
* OWNS the shared `DiscoveredChildDevice` candidate type
|
|
53492
|
+
* (declared below) so a single shared adoption modal renders any integration's
|
|
53493
|
+
* candidate tree.
|
|
53494
|
+
*
|
|
53495
|
+
* UI flow (Integrations page → integration detail):
|
|
53496
|
+
* 1. `listCandidates({integrationId, page, pageSize, filter})` →
|
|
53497
|
+
* paginated candidate list, each row collapsed.
|
|
53498
|
+
* 2. User expands a row → `getCandidate({integrationId,
|
|
53499
|
+
* childNativeId})` returns the single candidate (with its nested
|
|
53500
|
+
* `children`) for the accordion render.
|
|
53501
|
+
* 3. User clicks "Adopt" on one or more candidates →
|
|
53502
|
+
* `adopt({integrationId, childNativeIds, perCandidate?})`
|
|
53503
|
+
* materialises one parent CamStack device per candidate plus its
|
|
53504
|
+
* accessory children. `perCandidate[childNativeId]` carries an
|
|
53505
|
+
* optional display-name override + a `hiddenChildIds` pre-hide set
|
|
53506
|
+
* (the parent's `accessories.hiddenChildIds` gates UI visibility).
|
|
53507
|
+
* 4. User clicks "Release" → `release({integrationId, camDeviceId})`
|
|
53508
|
+
* removes the parent + every child from the kernel registry.
|
|
53509
|
+
*/
|
|
53510
|
+
/**
|
|
53511
|
+
* Status enum for a discovered device. Generic enough for
|
|
52536
53512
|
* battery cams, awake cams, and unreachable cams alike.
|
|
52537
53513
|
*/
|
|
52538
53514
|
var DiscoveredChildStatusSchema = _enum([
|
|
@@ -52549,12 +53525,12 @@ var DiscoveredChildDeviceSchema = lazy(() => object({
|
|
|
52549
53525
|
/** DeviceType the child should be created with on adopt. */
|
|
52550
53526
|
type: _enum(DeviceType),
|
|
52551
53527
|
status: DiscoveredChildStatusSchema,
|
|
52552
|
-
/** Free-form integration-specific metadata surfaced in the
|
|
53528
|
+
/** Free-form integration-specific metadata surfaced in the adoption modal. */
|
|
52553
53529
|
metadata: object({
|
|
52554
53530
|
model: string().optional(),
|
|
52555
53531
|
serialNumber: string().optional(),
|
|
52556
53532
|
uid: string().optional(),
|
|
52557
|
-
/** Reolink: 0-based channel index inside the parent NVR
|
|
53533
|
+
/** Reolink: 0-based channel index inside the parent NVR. */
|
|
52558
53534
|
rtspChannel: number$1().int().nonnegative().optional(),
|
|
52559
53535
|
isBattery: boolean().optional(),
|
|
52560
53536
|
isDoorbell: boolean().optional(),
|
|
@@ -52570,137 +53546,24 @@ var DiscoveredChildDeviceSchema = lazy(() => object({
|
|
|
52570
53546
|
}).default({}),
|
|
52571
53547
|
/**
|
|
52572
53548
|
* `true` when the framework already created a child device for this
|
|
52573
|
-
* `childNativeId` under the current parent. The
|
|
53549
|
+
* `childNativeId` under the current parent. The adoption modal uses it to
|
|
52574
53550
|
* gate the Add/Remove button and surface the existing `deviceId`.
|
|
52575
53551
|
*/
|
|
52576
53552
|
alreadyAdopted: boolean(),
|
|
52577
53553
|
/** When `alreadyAdopted=true`, the framework-assigned child device id. */
|
|
52578
53554
|
adoptedDeviceId: number$1().int().nonnegative().nullable(),
|
|
52579
|
-
/** Capability names the candidate advertises — integrations populate,
|
|
53555
|
+
/** Capability names the candidate advertises — integrations populate, flat enumerations omit. */
|
|
52580
53556
|
capabilities: array(string()).readonly().optional(),
|
|
52581
|
-
/** Feature flags the candidate advertises — integrations populate,
|
|
53557
|
+
/** Feature flags the candidate advertises — integrations populate, flat enumerations omit. */
|
|
52582
53558
|
features: array(string()).readonly().optional(),
|
|
52583
53559
|
/** Upstream-system identity + rendering hints for the candidate. */
|
|
52584
53560
|
sourceInfo: SourceInfoSchema.optional(),
|
|
52585
|
-
/** Nested entity-children for accordion preview — integrations populate,
|
|
53561
|
+
/** Nested entity-children for accordion preview — integrations populate, flat enumerations omit. */
|
|
52586
53562
|
children: array(DiscoveredChildDeviceSchema).readonly().optional(),
|
|
52587
53563
|
/** `childNativeId` of the gateway candidate in the same list this device sits behind. */
|
|
52588
53564
|
parentStableId: string().optional()
|
|
52589
53565
|
}));
|
|
52590
53566
|
/**
|
|
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
53567
|
* Provider-declared discovery *granularity* filter (the keystone of the
|
|
52705
53568
|
* single-entity-import design). A provider advertises which discovery
|
|
52706
53569
|
* granularities it supports via `listCandidateFilters`; the operator picks
|
|
@@ -54128,8 +54991,14 @@ method(object({
|
|
|
54128
54991
|
"string",
|
|
54129
54992
|
"number",
|
|
54130
54993
|
"boolean",
|
|
54131
|
-
"enum"
|
|
54994
|
+
"enum",
|
|
54995
|
+
"union"
|
|
54132
54996
|
]),
|
|
54997
|
+
unionMembers: array(_enum([
|
|
54998
|
+
"string",
|
|
54999
|
+
"number",
|
|
55000
|
+
"boolean"
|
|
55001
|
+
])).optional(),
|
|
54133
55002
|
enumValues: array(string()).optional(),
|
|
54134
55003
|
item: boolean().optional(),
|
|
54135
55004
|
quantity: ReadingQuantitySchema.optional()
|
|
@@ -56221,53 +57090,6 @@ var enumSensorCapability = {
|
|
|
56221
57090
|
* whether persisting is worth a SQLite commit. */
|
|
56222
57091
|
volatileStateFields: ["lastFetchedAt", "timestamp"]
|
|
56223
57092
|
};
|
|
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
57093
|
var IdentitySchema = object({
|
|
56272
57094
|
id: string(),
|
|
56273
57095
|
name: string(),
|
|
@@ -56757,8 +57579,85 @@ method(_void(), array(FailureContributionSchema).readonly());
|
|
|
56757
57579
|
* - `FanPreset` — preset selector
|
|
56758
57580
|
* - `FanDirection` — direction toggle (ceiling fans)
|
|
56759
57581
|
* - `FanOscillating` — oscillation toggle
|
|
57582
|
+
*
|
|
57583
|
+
* Per-axis oscillation (`oscillationAxes`, `setOscillationAxis`, D1034): an air
|
|
57584
|
+
* circulator sweeps horizontally and vertically, each axis with its own
|
|
57585
|
+
* switch, fixed angle, sweep bounds and limits. A provider may report them
|
|
57586
|
+
* natively; otherwise the OPERATOR links sibling devices to each field in
|
|
57587
|
+
* Customize (a `replace` over the fields listed in `overridable` — nothing is
|
|
57588
|
+
* linked or inferred by itself), and a write to a linked field is forwarded to
|
|
57589
|
+
* its source. `oscillating` stays the single summary flag: when axes exist it
|
|
57590
|
+
* means "any axis oscillates" (`anyAxisOscillating`).
|
|
56760
57591
|
*/
|
|
56761
57592
|
var FanDirectionSchema = _enum(["forward", "reverse"]);
|
|
57593
|
+
var FAN_OSCILLATION_AXES = ["horizontal", "vertical"];
|
|
57594
|
+
var FanOscillationAxisNameSchema = _enum(FAN_OSCILLATION_AXES);
|
|
57595
|
+
var FanOscillationAxisFieldSchema = _enum([
|
|
57596
|
+
"oscillating",
|
|
57597
|
+
"angle",
|
|
57598
|
+
"sweepFrom",
|
|
57599
|
+
"sweepTo"
|
|
57600
|
+
]);
|
|
57601
|
+
/** The limits of one axis, read from the source that serves its angle. */
|
|
57602
|
+
var FAN_OSCILLATION_AXIS_LIMITS = [
|
|
57603
|
+
"min",
|
|
57604
|
+
"max",
|
|
57605
|
+
"step"
|
|
57606
|
+
];
|
|
57607
|
+
/**
|
|
57608
|
+
* One oscillation axis. Every reading the device has not reported is `null`,
|
|
57609
|
+
* never `0` (D393): `0°` is a real position. Each leaf defaults to `null`, so
|
|
57610
|
+
* a slice that names only the leaves it knows (a Customize link of one field)
|
|
57611
|
+
* is still a whole axis.
|
|
57612
|
+
*/
|
|
57613
|
+
var FanOscillationAxisSchema = object({
|
|
57614
|
+
/** Whether the axis sweeps. */
|
|
57615
|
+
oscillating: boolean().nullable().default(null),
|
|
57616
|
+
/** The fixed position (degrees) the axis holds when it does not sweep. */
|
|
57617
|
+
angle: number$1().nullable().default(null),
|
|
57618
|
+
/** The sweep bounds (degrees): left → right, bottom → top. */
|
|
57619
|
+
sweepFrom: number$1().nullable().default(null),
|
|
57620
|
+
sweepTo: number$1().nullable().default(null),
|
|
57621
|
+
/** The angles the axis accepts. Null when the device states none. */
|
|
57622
|
+
min: number$1().nullable().default(null),
|
|
57623
|
+
max: number$1().nullable().default(null),
|
|
57624
|
+
step: number$1().nullable().default(null)
|
|
57625
|
+
});
|
|
57626
|
+
/** Both axes; an axis the device does not have is `null`. */
|
|
57627
|
+
var FanOscillationAxesSchema = object({
|
|
57628
|
+
horizontal: FanOscillationAxisSchema.nullable().default(null),
|
|
57629
|
+
vertical: FanOscillationAxisSchema.nullable().default(null)
|
|
57630
|
+
});
|
|
57631
|
+
var FanOscillationAxisRefusalSchema = _enum([
|
|
57632
|
+
"nothing-to-set",
|
|
57633
|
+
"axis-not-linked",
|
|
57634
|
+
"out-of-range",
|
|
57635
|
+
"source-gone",
|
|
57636
|
+
"source-unknown",
|
|
57637
|
+
"source-call-failed"
|
|
57638
|
+
]);
|
|
57639
|
+
var FanOscillationAxisResultSchema = discriminatedUnion("status", [object({ status: literal("applied") }), object({
|
|
57640
|
+
status: literal("refused"),
|
|
57641
|
+
reason: FanOscillationAxisRefusalSchema,
|
|
57642
|
+
/** The field at fault, when one is. */
|
|
57643
|
+
field: FanOscillationAxisFieldSchema.optional(),
|
|
57644
|
+
/** One line for the operator, naming the axis, the field and the source. */
|
|
57645
|
+
detail: string(),
|
|
57646
|
+
/** Fields that DID land before the refusal (writes run in order, stop at the first failure). */
|
|
57647
|
+
applied: array(FanOscillationAxisFieldSchema)
|
|
57648
|
+
})]);
|
|
57649
|
+
var SetOscillationAxisInputSchema = object({
|
|
57650
|
+
deviceId: number$1().int().nonnegative(),
|
|
57651
|
+
axis: FanOscillationAxisNameSchema,
|
|
57652
|
+
oscillating: boolean().optional(),
|
|
57653
|
+
angle: number$1().optional(),
|
|
57654
|
+
sweepFrom: number$1().optional(),
|
|
57655
|
+
sweepTo: number$1().optional()
|
|
57656
|
+
});
|
|
57657
|
+
/** The state path of one axis field: `oscillationAxes.<axis>.<field>`. */
|
|
57658
|
+
function fanAxisPath(axis, field) {
|
|
57659
|
+
return `oscillationAxes.${axis}.${field}`;
|
|
57660
|
+
}
|
|
56762
57661
|
var FanControlStatusSchema = object({
|
|
56763
57662
|
/** Active speed as 0..100 inclusive. Null when the device has no
|
|
56764
57663
|
* speed surface (single-speed fan). */
|
|
@@ -56777,9 +57676,102 @@ var FanControlStatusSchema = object({
|
|
|
56777
57676
|
/** Oscillation toggle. Null when the device has no oscillation
|
|
56778
57677
|
* surface. */
|
|
56779
57678
|
oscillating: boolean().nullable(),
|
|
57679
|
+
/**
|
|
57680
|
+
* Per-axis oscillation (air circulators). Absent when the device has no
|
|
57681
|
+
* per-axis surface and nothing is linked in Customize.
|
|
57682
|
+
*/
|
|
57683
|
+
oscillationAxes: FanOscillationAxesSchema.optional(),
|
|
56780
57684
|
/** Ms epoch when the slice was last updated. */
|
|
56781
57685
|
lastChangedAt: number$1()
|
|
56782
57686
|
});
|
|
57687
|
+
var FAN_CUSTOMIZE_DEVICE_TYPES = [DeviceType.Fan];
|
|
57688
|
+
/** The Customize section the axis targets are listed under. */
|
|
57689
|
+
var AXES_SECTION = "oscillation-axes";
|
|
57690
|
+
function axisOscillatingTarget(axis) {
|
|
57691
|
+
return {
|
|
57692
|
+
id: `${axis}-oscillating`,
|
|
57693
|
+
cardinality: "scalar",
|
|
57694
|
+
vocabulary: {
|
|
57695
|
+
cap: "fan-control",
|
|
57696
|
+
stateField: "oscillating"
|
|
57697
|
+
},
|
|
57698
|
+
deviceTypes: FAN_CUSTOMIZE_DEVICE_TYPES,
|
|
57699
|
+
apply: "replace",
|
|
57700
|
+
section: AXES_SECTION,
|
|
57701
|
+
path: fanAxisPath(axis, "oscillating"),
|
|
57702
|
+
sources: [{
|
|
57703
|
+
kind: "switch",
|
|
57704
|
+
transforms: [{ kind: "identity" }]
|
|
57705
|
+
}],
|
|
57706
|
+
sourceReport: { status: "assignment-status" }
|
|
57707
|
+
};
|
|
57708
|
+
}
|
|
57709
|
+
/**
|
|
57710
|
+
* A numeric axis field. The `angle` link also carries the axis limits, read
|
|
57711
|
+
* from the SAME linked control's own range (`min`/`max`/`step`), so the limits
|
|
57712
|
+
* are the source's, never guessed.
|
|
57713
|
+
*/
|
|
57714
|
+
function axisNumericTarget(axis, field) {
|
|
57715
|
+
const base = {
|
|
57716
|
+
id: `${axis}-${field}`,
|
|
57717
|
+
cardinality: "scalar",
|
|
57718
|
+
vocabulary: { cap: "fan-control" },
|
|
57719
|
+
deviceTypes: FAN_CUSTOMIZE_DEVICE_TYPES,
|
|
57720
|
+
apply: "replace",
|
|
57721
|
+
section: AXES_SECTION,
|
|
57722
|
+
path: fanAxisPath(axis, field),
|
|
57723
|
+
sources: [{
|
|
57724
|
+
kind: "numeric",
|
|
57725
|
+
transforms: [{ kind: "identity" }]
|
|
57726
|
+
}],
|
|
57727
|
+
sourceReport: { status: "assignment-status" }
|
|
57728
|
+
};
|
|
57729
|
+
if (field !== "angle") return base;
|
|
57730
|
+
return {
|
|
57731
|
+
...base,
|
|
57732
|
+
companions: FAN_OSCILLATION_AXIS_LIMITS.map((limit) => ({
|
|
57733
|
+
kind: "source-field",
|
|
57734
|
+
path: fanAxisPath(axis, limit),
|
|
57735
|
+
field: limit
|
|
57736
|
+
}))
|
|
57737
|
+
};
|
|
57738
|
+
}
|
|
57739
|
+
var AXIS_TARGETS = FAN_OSCILLATION_AXES.flatMap((axis) => [
|
|
57740
|
+
axisOscillatingTarget(axis),
|
|
57741
|
+
axisNumericTarget(axis, "angle"),
|
|
57742
|
+
axisNumericTarget(axis, "sweepFrom"),
|
|
57743
|
+
axisNumericTarget(axis, "sweepTo")
|
|
57744
|
+
]);
|
|
57745
|
+
/** A select (an enum control) the operator links to the fan's `preset`. */
|
|
57746
|
+
var PRESET_TARGET = {
|
|
57747
|
+
id: "preset",
|
|
57748
|
+
cardinality: "scalar",
|
|
57749
|
+
vocabulary: { cap: "fan-control" },
|
|
57750
|
+
deviceTypes: FAN_CUSTOMIZE_DEVICE_TYPES,
|
|
57751
|
+
apply: "replace",
|
|
57752
|
+
path: "preset",
|
|
57753
|
+
sources: [{
|
|
57754
|
+
kind: "field",
|
|
57755
|
+
valueKinds: ["string"],
|
|
57756
|
+
transforms: [{ kind: "identity" }]
|
|
57757
|
+
}],
|
|
57758
|
+
sourceReport: { status: "assignment-status" }
|
|
57759
|
+
};
|
|
57760
|
+
var FAN_CONTROL_TARGETS = [...AXIS_TARGETS, PRESET_TARGET];
|
|
57761
|
+
/**
|
|
57762
|
+
* Customize (D1034): the fan already serves `fan-control` natively, so every
|
|
57763
|
+
* target is a `replace` over a field listed in `overridable` — the axis leaves
|
|
57764
|
+
* and `preset`, nothing else. Nothing is pre-linked: the operator picks each
|
|
57765
|
+
* source (D356).
|
|
57766
|
+
*/
|
|
57767
|
+
var fanControlCustomize = {
|
|
57768
|
+
version: 1,
|
|
57769
|
+
overridable: FAN_CONTROL_TARGETS.map((target) => ({
|
|
57770
|
+
path: target.path,
|
|
57771
|
+
by: target.id
|
|
57772
|
+
})),
|
|
57773
|
+
targets: FAN_CONTROL_TARGETS
|
|
57774
|
+
};
|
|
56783
57775
|
var fanControlCapability = {
|
|
56784
57776
|
name: "fan-control",
|
|
56785
57777
|
scope: "device",
|
|
@@ -56814,6 +57806,18 @@ var fanControlCapability = {
|
|
|
56814
57806
|
}), _void(), {
|
|
56815
57807
|
kind: "mutation",
|
|
56816
57808
|
auth: "protected"
|
|
57809
|
+
}),
|
|
57810
|
+
/**
|
|
57811
|
+
* Sets one oscillation axis: its switch, fixed angle and/or sweep bounds.
|
|
57812
|
+
* Fields left out are untouched. A field linked in Customize is forwarded
|
|
57813
|
+
* to its linked source (D1034); otherwise the native provider serves it;
|
|
57814
|
+
* neither → `axis-not-linked`. Ranges are checked against the axis limits
|
|
57815
|
+
* before anything is written; writes run in order and stop at the first
|
|
57816
|
+
* failure, which the result names.
|
|
57817
|
+
*/
|
|
57818
|
+
setOscillationAxis: optionalMethod(SetOscillationAxisInputSchema, FanOscillationAxisResultSchema, {
|
|
57819
|
+
kind: "mutation",
|
|
57820
|
+
auth: "protected"
|
|
56817
57821
|
})
|
|
56818
57822
|
},
|
|
56819
57823
|
status: {
|
|
@@ -56825,6 +57829,7 @@ var fanControlCapability = {
|
|
|
56825
57829
|
* sliders read `speed` for live updates.
|
|
56826
57830
|
*/
|
|
56827
57831
|
runtimeState: FanControlStatusSchema,
|
|
57832
|
+
customize: fanControlCustomize,
|
|
56828
57833
|
/**
|
|
56829
57834
|
* Runtime-state durability: **session** — as `brightness`.
|
|
56830
57835
|
*
|
|
@@ -56844,16 +57849,33 @@ var fanControlCapability = {
|
|
|
56844
57849
|
oscillating: { kind: "boolean" }
|
|
56845
57850
|
} }
|
|
56846
57851
|
};
|
|
57852
|
+
/** One axis as a preset wants it; an absent field is left as it is. */
|
|
57853
|
+
var FanPresetAxisSchema = object({
|
|
57854
|
+
oscillating: boolean().optional(),
|
|
57855
|
+
angle: number$1().optional(),
|
|
57856
|
+
sweepFrom: number$1().optional(),
|
|
57857
|
+
sweepTo: number$1().optional()
|
|
57858
|
+
});
|
|
57859
|
+
/** The fan-control state a preset sets; an absent field is left as it is. */
|
|
57860
|
+
var FanPresetStateSchema = object({
|
|
57861
|
+
speed: number$1().min(0).max(100).optional(),
|
|
57862
|
+
/** The fan's own preset mode (or the linked select's option). */
|
|
57863
|
+
preset: string().min(1).optional(),
|
|
57864
|
+
direction: FanDirectionSchema.optional(),
|
|
57865
|
+
oscillationAxes: object({
|
|
57866
|
+
horizontal: FanPresetAxisSchema.optional(),
|
|
57867
|
+
vertical: FanPresetAxisSchema.optional()
|
|
57868
|
+
}).optional()
|
|
57869
|
+
});
|
|
56847
57870
|
var FanPresetSchema = object({
|
|
56848
57871
|
/** Stable key; never reused after a delete within one device's list. */
|
|
56849
57872
|
id: string().min(1).max(64),
|
|
56850
57873
|
/** What the operator and a voice assistant call it. */
|
|
56851
57874
|
name: string().trim().min(1).max(40),
|
|
56852
|
-
|
|
57875
|
+
state: FanPresetStateSchema
|
|
56853
57876
|
});
|
|
56854
57877
|
var FanPresetsRefusalSchema = _enum([
|
|
56855
|
-
"
|
|
56856
|
-
"bad-target",
|
|
57878
|
+
"empty-state",
|
|
56857
57879
|
"duplicate-name",
|
|
56858
57880
|
"duplicate-id",
|
|
56859
57881
|
"too-many",
|
|
@@ -56865,72 +57887,55 @@ var FanPresetsSetResultSchema = discriminatedUnion("status", [object({
|
|
|
56865
57887
|
}), object({
|
|
56866
57888
|
status: literal("refused"),
|
|
56867
57889
|
reason: FanPresetsRefusalSchema,
|
|
56868
|
-
/** Names the preset
|
|
57890
|
+
/** Names the preset at fault. */
|
|
56869
57891
|
detail: string()
|
|
56870
57892
|
})]);
|
|
56871
|
-
var
|
|
56872
|
-
|
|
57893
|
+
var FanPresetFieldResultSchema = object({
|
|
57894
|
+
/** The state path: `speed`, `preset`, `direction`, `oscillationAxes.horizontal.angle`. */
|
|
57895
|
+
field: string(),
|
|
56873
57896
|
outcome: _enum([
|
|
56874
57897
|
"applied",
|
|
56875
57898
|
"failed",
|
|
56876
57899
|
"not-run",
|
|
56877
57900
|
"refused"
|
|
56878
57901
|
]),
|
|
56879
|
-
deviceId: number$1().int().nonnegative().optional(),
|
|
56880
|
-
cap: string().optional(),
|
|
56881
|
-
method: string().optional(),
|
|
56882
57902
|
/** The cause, for every outcome but `applied`. */
|
|
56883
57903
|
message: string().optional()
|
|
56884
57904
|
});
|
|
56885
57905
|
var FanPresetApplyResultSchema = object({
|
|
56886
|
-
/** `applied`: every
|
|
57906
|
+
/** `applied`: every field landed. `partial`: some did, one did not. `refused`: nothing ran. */
|
|
56887
57907
|
status: _enum([
|
|
56888
57908
|
"applied",
|
|
56889
57909
|
"partial",
|
|
56890
57910
|
"refused"
|
|
56891
57911
|
]),
|
|
56892
|
-
|
|
57912
|
+
fields: array(FanPresetFieldResultSchema)
|
|
56893
57913
|
});
|
|
56894
57914
|
var FanCaptureFieldSchema = _enum([
|
|
56895
57915
|
"speed",
|
|
56896
|
-
"oscillating",
|
|
56897
57916
|
"direction",
|
|
56898
|
-
"preset"
|
|
57917
|
+
"preset",
|
|
57918
|
+
"oscillationAxes"
|
|
56899
57919
|
]);
|
|
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
57920
|
var FanPresetCaptureResultSchema = object({
|
|
56916
|
-
/** The
|
|
56917
|
-
|
|
56918
|
-
|
|
56919
|
-
|
|
56920
|
-
|
|
57921
|
+
/** The fan's CURRENT fan-control state, as a preset; NOT saved. */
|
|
57922
|
+
state: FanPresetStateSchema,
|
|
57923
|
+
omitted: array(object({
|
|
57924
|
+
/** A state path (`speed`, `oscillationAxes.vertical.angle`). */
|
|
57925
|
+
what: string(),
|
|
57926
|
+
/** Why no value was taken: an unknown reading is never stored as 0 (D393). */
|
|
57927
|
+
reason: string()
|
|
57928
|
+
}))
|
|
56921
57929
|
});
|
|
56922
|
-
var deviceInput$
|
|
57930
|
+
var deviceInput$1 = object({ deviceId: number$1().int().nonnegative() });
|
|
56923
57931
|
DeviceType.Fan;
|
|
56924
|
-
method(deviceInput$
|
|
57932
|
+
method(deviceInput$1, array(FanPresetSchema), { auth: "protected" }), method(deviceInput$1.extend({ presets: array(FanPresetSchema).max(12) }), FanPresetsSetResultSchema, {
|
|
56925
57933
|
kind: "mutation",
|
|
56926
57934
|
auth: "admin"
|
|
56927
|
-
}), method(deviceInput$
|
|
57935
|
+
}), method(deviceInput$1.extend({ presetId: string().min(1) }), FanPresetApplyResultSchema, {
|
|
56928
57936
|
kind: "mutation",
|
|
56929
57937
|
auth: "protected"
|
|
56930
|
-
}), method(deviceInput$
|
|
56931
|
-
fields: array(FanCaptureFieldSchema).optional(),
|
|
56932
|
-
steps: array(ActionStepSchema).max(20).default([])
|
|
56933
|
-
}), FanPresetCaptureResultSchema, { auth: "protected" });
|
|
57938
|
+
}), method(deviceInput$1.extend({ fields: array(FanCaptureFieldSchema).optional() }), FanPresetCaptureResultSchema, { auth: "protected" });
|
|
56934
57939
|
/**
|
|
56935
57940
|
* Per-device feature/identity probe slice. Holds the runtime-resolved
|
|
56936
57941
|
* truth about what a device CAN do — which the kernel uses to:
|
|
@@ -57142,8 +58147,7 @@ var groupActiveCapability = {
|
|
|
57142
58147
|
DeviceType.Valve,
|
|
57143
58148
|
DeviceType.Lock,
|
|
57144
58149
|
DeviceType.MediaPlayer,
|
|
57145
|
-
DeviceType.Climate
|
|
57146
|
-
DeviceType.Thermostat
|
|
58150
|
+
DeviceType.Climate
|
|
57147
58151
|
],
|
|
57148
58152
|
methods: {},
|
|
57149
58153
|
status: {
|
|
@@ -57185,7 +58189,6 @@ var groupCommandsCapability = {
|
|
|
57185
58189
|
DeviceType.Valve,
|
|
57186
58190
|
DeviceType.Lock,
|
|
57187
58191
|
DeviceType.MediaPlayer,
|
|
57188
|
-
DeviceType.Thermostat,
|
|
57189
58192
|
DeviceType.Climate
|
|
57190
58193
|
],
|
|
57191
58194
|
methods: {},
|
|
@@ -57241,7 +58244,6 @@ var groupStatusCapability = {
|
|
|
57241
58244
|
DeviceType.Valve,
|
|
57242
58245
|
DeviceType.Lock,
|
|
57243
58246
|
DeviceType.MediaPlayer,
|
|
57244
|
-
DeviceType.Thermostat,
|
|
57245
58247
|
DeviceType.Climate
|
|
57246
58248
|
],
|
|
57247
58249
|
methods: {},
|
|
@@ -57570,7 +58572,7 @@ var heatingValveCapability = {
|
|
|
57570
58572
|
scope: "device",
|
|
57571
58573
|
deviceNative: true,
|
|
57572
58574
|
mode: "singleton",
|
|
57573
|
-
deviceTypes: [DeviceType.
|
|
58575
|
+
deviceTypes: [DeviceType.Climate],
|
|
57574
58576
|
methods: {},
|
|
57575
58577
|
status: {
|
|
57576
58578
|
schema: HeatingValveStatusSchema,
|
|
@@ -57600,13 +58602,18 @@ var heatingValveCapability = {
|
|
|
57600
58602
|
* retried in a loop - D49); a future one is re-armed.
|
|
57601
58603
|
*
|
|
57602
58604
|
* What a device may be run for, and for how long at most, is per device type
|
|
57603
|
-
* ({@link
|
|
58605
|
+
* and role ({@link timedRunLimitOf}): the maximum is a safety bound, not a preference,
|
|
57604
58606
|
* so a request above it is REFUSED with a typed reason, never clamped.
|
|
57605
58607
|
*/
|
|
57606
58608
|
var TimedRunUnitSchema = _enum(["s", "min"]);
|
|
57607
58609
|
_enum(["switch", "pet-fountain"]);
|
|
57608
|
-
|
|
57609
|
-
|
|
58610
|
+
/**
|
|
58611
|
+
* The device types `timed-run` may be offered on. A `switch` is narrowed by
|
|
58612
|
+
* role ({@link TIMED_RUN_DEVICE_ROLES}): only a plug-in heater runs on a timer.
|
|
58613
|
+
*/
|
|
58614
|
+
var TIMED_RUN_DEVICE_TYPES = [DeviceType.Switch, DeviceType.PetFountain];
|
|
58615
|
+
/** The roles that make a device of these types timed-run-able (D1033). */
|
|
58616
|
+
var TIMED_RUN_DEVICE_ROLES = { [DeviceType.Switch]: [DeviceRole.SwitchHeater] };
|
|
57610
58617
|
/** One duration, as the one-tap run asks for it. */
|
|
57611
58618
|
var TimedRunQuickRunSchema = object({
|
|
57612
58619
|
duration: number$1().positive().finite(),
|
|
@@ -57656,6 +58663,7 @@ var timedRunCapability = {
|
|
|
57656
58663
|
kind: "wrapper",
|
|
57657
58664
|
defaultActive: true,
|
|
57658
58665
|
deviceTypes: TIMED_RUN_DEVICE_TYPES,
|
|
58666
|
+
deviceRoles: TIMED_RUN_DEVICE_ROLES,
|
|
57659
58667
|
deviceConfig: { ui: {
|
|
57660
58668
|
kind: "derived-form",
|
|
57661
58669
|
builderId: "timed-run",
|
|
@@ -57816,8 +58824,8 @@ var HeroSceneSchema = object({
|
|
|
57816
58824
|
/** 0..1: volume, cover position, cycle progress. */
|
|
57817
58825
|
level: number$1().min(0).max(1).nullable()
|
|
57818
58826
|
});
|
|
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;
|
|
58827
|
+
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;
|
|
58828
|
+
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
58829
|
/** A string in both languages, as the cap vocabulary writes it (D920): no `t()` in the model. */
|
|
57822
58830
|
var HeroTextSchema = object({
|
|
57823
58831
|
en: string(),
|
|
@@ -57883,13 +58891,11 @@ var HeroConfirmSchema = _enum([
|
|
|
57883
58891
|
"press"
|
|
57884
58892
|
]);
|
|
57885
58893
|
var HeroActionSchema = object({
|
|
57886
|
-
/** Unique within the model; the `gated` list and the specs name it. */
|
|
57887
58894
|
id: string(),
|
|
57888
58895
|
label: HeroTextSchema,
|
|
57889
58896
|
icon: IconKeySchema,
|
|
57890
58897
|
call: HeroCallSchema,
|
|
57891
58898
|
emphasis: _enum(["primary", "normal"]),
|
|
57892
|
-
/** Ask before running it. */
|
|
57893
58899
|
confirm: HeroConfirmSchema.nullable()
|
|
57894
58900
|
});
|
|
57895
58901
|
var HeroControlSchema = discriminatedUnion("kind", [
|
|
@@ -57972,9 +58978,16 @@ var HeroBadgeSchema = object({
|
|
|
57972
58978
|
value: number$1().nullable(),
|
|
57973
58979
|
label: HeroTextSchema
|
|
57974
58980
|
});
|
|
57975
|
-
var HeroRemoteSlotSchema = object({
|
|
57976
|
-
/** Saved commands; `null` = not known yet (the button still shows). */
|
|
57977
|
-
commands: number$1().int().nullable()
|
|
58981
|
+
var HeroRemoteSlotSchema = object({
|
|
58982
|
+
/** Saved commands; `null` = not known yet (the button still shows). */
|
|
58983
|
+
commands: number$1().int().nullable(),
|
|
58984
|
+
/**
|
|
58985
|
+
* The control a list row draws as the "open the remote" icon. Recognised by its id
|
|
58986
|
+
* ({@link REMOTE_OPEN_ACTION_ID}); it changes no state, so a client opens the remote
|
|
58987
|
+
* instead of running `call`. Absent on a model from an older server.
|
|
58988
|
+
*/
|
|
58989
|
+
open: HeroActionSchema.optional()
|
|
58990
|
+
});
|
|
57978
58991
|
var HeroConsumablesSlotSchema = object({
|
|
57979
58992
|
items: array(object({
|
|
57980
58993
|
key: string(),
|
|
@@ -58142,6 +59155,8 @@ var HeroModelSchema = object({
|
|
|
58142
59155
|
* the FIRST row that has any (a later row's control is never promoted). When
|
|
58143
59156
|
* the model lists that part in `gated`, the row has no inline control: the
|
|
58144
59157
|
* projection never falls through to another control the caller could use.
|
|
59158
|
+
* - a climate or thermostat row's primary is its `climate.mode` control (no power
|
|
59159
|
+
* toggle: "off" is a mode); when the model gates it the rule above applies as before;
|
|
58145
59160
|
* - a timed run, when the model carries the slot, sits beside that primary:
|
|
58146
59161
|
* Run while idle, Stop while armed. Gating the slot hides the control.
|
|
58147
59162
|
* - the status line is the head (what plays, else the state) plus at most
|
|
@@ -58149,6 +59164,13 @@ var HeroModelSchema = object({
|
|
|
58149
59164
|
* time left on an armed timed run;
|
|
58150
59165
|
* - the trailing fact is the first of power, battery, consumables.
|
|
58151
59166
|
*
|
|
59167
|
+
* - `extras` are the further inline controls of the types that carry more than one
|
|
59168
|
+
* ({@link ROW_EXTRAS_BY_TYPE}): climate setpoint, a robot's return to base,
|
|
59169
|
+
* a player's volume and remote. Each is a control the model already has, named by id,
|
|
59170
|
+
* in that order and at most {@link ROW_EXTRAS_MAX}; one the model lacks, or lists in
|
|
59171
|
+
* `gated`, is absent, and the one `primary` already is never repeats. Empty = the
|
|
59172
|
+
* row of an older server: `primary` alone.
|
|
59173
|
+
*
|
|
58152
59174
|
* `projectDeviceRow` is pure and computes nothing: it selects from a
|
|
58153
59175
|
* {@link HeroModel}. A row that needed a value the hero lacks would mean the
|
|
58154
59176
|
* model is missing it, so the fix goes in the model.
|
|
@@ -58180,8 +59202,25 @@ var HeroRowModelSchema = object({
|
|
|
58180
59202
|
* while armed. `null` when there is no slot, or the caller may not act.
|
|
58181
59203
|
*/
|
|
58182
59204
|
timedRun: HeroRowTimedRunSchema.nullable(),
|
|
59205
|
+
/**
|
|
59206
|
+
* The further inline controls after `primary`, in order (see the header). The remote
|
|
59207
|
+
* is an `action` with id `remote.open`: the client opens the remote, it does not run
|
|
59208
|
+
* the call. Absent or empty = `primary` alone.
|
|
59209
|
+
*/
|
|
59210
|
+
extras: array(HeroControlSchema).max(2).optional(),
|
|
58183
59211
|
trailing: HeroFactSchema.nullable()
|
|
58184
59212
|
});
|
|
59213
|
+
new Set([
|
|
59214
|
+
"temperature",
|
|
59215
|
+
"humidity",
|
|
59216
|
+
"pressure",
|
|
59217
|
+
"illuminance",
|
|
59218
|
+
"reading",
|
|
59219
|
+
"air-quality",
|
|
59220
|
+
"valve"
|
|
59221
|
+
]);
|
|
59222
|
+
DeviceType.Climate;
|
|
59223
|
+
DeviceType.Climate, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer;
|
|
58185
59224
|
/**
|
|
58186
59225
|
* hero-layout — what a device's hero says (D1023). Read-only: the FIXED
|
|
58187
59226
|
* layouts live in `@camstack/types` and nothing here stores or edits one.
|
|
@@ -58213,7 +59252,10 @@ method(heroInput, HeroModelSchema.nullable(), {
|
|
|
58213
59252
|
}), method(rowModelsInput, array(HeroRowModelSchema.nullable()), {
|
|
58214
59253
|
auth: "protected",
|
|
58215
59254
|
role: "viewer"
|
|
58216
|
-
}), method(object({
|
|
59255
|
+
}), method(object({
|
|
59256
|
+
deviceType: string(),
|
|
59257
|
+
role: string().nullable().optional()
|
|
59258
|
+
}), HeroLayoutRowsSchema, {
|
|
58217
59259
|
auth: "protected",
|
|
58218
59260
|
role: "viewer"
|
|
58219
59261
|
});
|
|
@@ -58260,6 +59302,11 @@ var humidifierCapability = {
|
|
|
58260
59302
|
deviceNative: true,
|
|
58261
59303
|
mode: "singleton",
|
|
58262
59304
|
deviceTypes: [DeviceType.Humidifier],
|
|
59305
|
+
power: {
|
|
59306
|
+
kind: "field",
|
|
59307
|
+
field: "on",
|
|
59308
|
+
set: "setOn"
|
|
59309
|
+
},
|
|
58263
59310
|
methods: {
|
|
58264
59311
|
setOn: method(object({
|
|
58265
59312
|
deviceId: number$1().int().nonnegative(),
|
|
@@ -58333,7 +59380,7 @@ var humiditySensorCapability = {
|
|
|
58333
59380
|
scope: "device",
|
|
58334
59381
|
deviceNative: true,
|
|
58335
59382
|
mode: "singleton",
|
|
58336
|
-
deviceTypes: [DeviceType.Sensor, DeviceType.
|
|
59383
|
+
deviceTypes: [DeviceType.Sensor, DeviceType.Climate],
|
|
58337
59384
|
methods: {},
|
|
58338
59385
|
status: {
|
|
58339
59386
|
schema: HumiditySensorStatusSchema,
|
|
@@ -58627,6 +59674,12 @@ var IntegrationLiteSchema = object({
|
|
|
58627
59674
|
var AvailableIntegrationTypeSchema = object({
|
|
58628
59675
|
addonId: string(),
|
|
58629
59676
|
name: string(),
|
|
59677
|
+
/**
|
|
59678
|
+
* Product name a new integration is prefilled from (`<label> - Home`).
|
|
59679
|
+
* Absent: the client uses `name`. Set when the catalog title is not the
|
|
59680
|
+
* product (Bosch SHC, MQTT).
|
|
59681
|
+
*/
|
|
59682
|
+
providerLabel: string().optional(),
|
|
58630
59683
|
description: string(),
|
|
58631
59684
|
iconUrl: string().nullable(),
|
|
58632
59685
|
color: string(),
|
|
@@ -59254,13 +60307,32 @@ var IrSendResultSchema = object({
|
|
|
59254
60307
|
var IrTransmitterInfoSchema = object({
|
|
59255
60308
|
media: array(IrMediumSchema),
|
|
59256
60309
|
settable: array(TransmitSettingKeySchema),
|
|
59257
|
-
maxTimings: number$1().int().positive()
|
|
60310
|
+
maxTimings: number$1().int().positive(),
|
|
60311
|
+
/** The transmitter can learn a code (`learn`). */
|
|
60312
|
+
learn: boolean().default(false),
|
|
60313
|
+
/** Native code formats the transmitter replays verbatim (`send.native`). */
|
|
60314
|
+
formats: array(IrNativeFormatSchema).default([])
|
|
60315
|
+
});
|
|
60316
|
+
var IR_LEARN_TIMEOUT_DEFAULT_MS = 2e4;
|
|
60317
|
+
var IR_LEARN_TIMEOUT_MAX_MS = 6e4;
|
|
60318
|
+
var IrLearnResultSchema = object({
|
|
60319
|
+
/** The code in the device's own format, when it reports one. */
|
|
60320
|
+
native: IrNativeCodeSchema.nullable(),
|
|
60321
|
+
timings: IrTimingsSchema,
|
|
60322
|
+
carrierHz: number$1().int().positive().nullable()
|
|
59258
60323
|
});
|
|
59259
60324
|
DeviceType.Generic, method(object({
|
|
59260
60325
|
deviceId: number$1().int().nonnegative(),
|
|
59261
|
-
timings: IrTimingsSchema,
|
|
60326
|
+
timings: IrTimingsSchema.optional(),
|
|
60327
|
+
native: IrNativeCodeSchema.optional(),
|
|
59262
60328
|
settings: TransmitSettingsSchema.optional()
|
|
59263
|
-
}), IrSendResultSchema, {
|
|
60329
|
+
}).refine((input) => input.timings === void 0 !== (input.native === void 0), { message: "send takes exactly one of timings or native" }), IrSendResultSchema, {
|
|
60330
|
+
kind: "mutation",
|
|
60331
|
+
auth: "protected"
|
|
60332
|
+
}), method(object({
|
|
60333
|
+
deviceId: number$1().int().nonnegative(),
|
|
60334
|
+
timeoutMs: number$1().int().positive().max(IR_LEARN_TIMEOUT_MAX_MS).default(IR_LEARN_TIMEOUT_DEFAULT_MS)
|
|
60335
|
+
}), IrLearnResultSchema, {
|
|
59264
60336
|
kind: "mutation",
|
|
59265
60337
|
auth: "protected"
|
|
59266
60338
|
}), method(object({ deviceId: number$1().int().nonnegative() }), IrTransmitterInfoSchema, {
|
|
@@ -69046,6 +70118,7 @@ var NcSystemEventKindSchema = _enum([
|
|
|
69046
70118
|
"recording-degraded",
|
|
69047
70119
|
"device-attention-required",
|
|
69048
70120
|
"device-attention-resolved",
|
|
70121
|
+
"device-command",
|
|
69049
70122
|
"storage-location-low",
|
|
69050
70123
|
"storage-location-critical",
|
|
69051
70124
|
"storage-location-ok",
|
|
@@ -69108,11 +70181,6 @@ var NC_ALARM_SYSTEM_EVENT_KINDS = [
|
|
|
69108
70181
|
"alarm-sensor-bypass",
|
|
69109
70182
|
"alarm-disarmed"
|
|
69110
70183
|
];
|
|
69111
|
-
/**
|
|
69112
|
-
* One coherent system-event condition. `kinds` is the required opt-in safety
|
|
69113
|
-
* gate; the remaining lists are optional narrowing filters relevant to the
|
|
69114
|
-
* selected kinds.
|
|
69115
|
-
*/
|
|
69116
70184
|
var NcSystemEventConditionSchema = object({
|
|
69117
70185
|
kinds: array(NcSystemEventKindSchema).min(1),
|
|
69118
70186
|
deviceIds: array(number$1().int()).min(1).optional(),
|
|
@@ -69144,6 +70212,28 @@ var NcSystemEventConditionSchema = object({
|
|
|
69144
70212
|
nodeIds: array(string().min(1)).min(1).optional(),
|
|
69145
70213
|
packageNames: array(string().min(1)).min(1).optional(),
|
|
69146
70214
|
/**
|
|
70215
|
+
* `device-command` only: the vocabulary ACTION IDS (D983, `powerSource.forceRestart`)
|
|
70216
|
+
* this rule speaks about - the same dictionary the automation action blocks
|
|
70217
|
+
* pick from. The hub refuses an id the action index does not know.
|
|
70218
|
+
*/
|
|
70219
|
+
commandActions: array(string().min(1)).min(1).optional(),
|
|
70220
|
+
/** `device-command` only: how the command settled. A device's own change is not a command. */
|
|
70221
|
+
commandOutcomes: array(_enum([
|
|
70222
|
+
"confirmed",
|
|
70223
|
+
"already",
|
|
70224
|
+
"unconfirmed",
|
|
70225
|
+
"failed"
|
|
70226
|
+
])).min(1).optional(),
|
|
70227
|
+
/** `device-command` only: who asked (`user`, `addon`, `action`, `scheduler`, `composition`, `node`). */
|
|
70228
|
+
commandActorKinds: array(_enum([
|
|
70229
|
+
"user",
|
|
70230
|
+
"addon",
|
|
70231
|
+
"action",
|
|
70232
|
+
"scheduler",
|
|
70233
|
+
"composition",
|
|
70234
|
+
"node"
|
|
70235
|
+
])).min(1).optional(),
|
|
70236
|
+
/**
|
|
69147
70237
|
* At or below this remaining-life %, a consumable is LOW for THIS rule
|
|
69148
70238
|
* (D642). Absent = {@link NC_CONSUMABLE_LOW_PERCENT_DEFAULT}; at most
|
|
69149
70239
|
* {@link NC_CONSUMABLE_LOW_PERCENT_MAX}, so every episode can recover. Scoped to the
|
|
@@ -70124,6 +71214,13 @@ var NcRuleInputSchema = object({
|
|
|
70124
71214
|
*/
|
|
70125
71215
|
groupingSec: number$1().int().min(5).max(60).optional(),
|
|
70126
71216
|
/**
|
|
71217
|
+
* Hold deliveries of this rule to one target and, when two or more arrive
|
|
71218
|
+
* before the window ends, send them as one summary. Absent = the kind's
|
|
71219
|
+
* default (`groupWithinSecOf`): 5 s for a system-event rule, 0 for every
|
|
71220
|
+
* other kind. `0` sends each delivery as it does today.
|
|
71221
|
+
*/
|
|
71222
|
+
groupWithinSec: number$1().int().min(0).max(300).optional(),
|
|
71223
|
+
/**
|
|
70127
71224
|
* ASK ME TO CONFIRM DETECTIONS (#243): a DETECTION notification of this rule
|
|
70128
71225
|
* carries "Correct" / "Wrong" buttons (and, where the channel has room, a
|
|
70129
71226
|
* "Wrong, it is a <class>" choice). A tap marks the track for retraining, the
|
|
@@ -70560,7 +71657,12 @@ var NcHistoryEntrySchema = object({
|
|
|
70560
71657
|
* Assistant export) must render as "no image right now", never as a
|
|
70561
71658
|
* broken link.
|
|
70562
71659
|
*/
|
|
70563
|
-
artifactIds: array(string().min(1)).optional()
|
|
71660
|
+
artifactIds: array(string().min(1)).optional(),
|
|
71661
|
+
/**
|
|
71662
|
+
* When this row was folded into a group-window summary, the outbox id of
|
|
71663
|
+
* that one notification. Absent on a row that was sent as itself.
|
|
71664
|
+
*/
|
|
71665
|
+
groupedInto: string().min(1).optional()
|
|
70564
71666
|
});
|
|
70565
71667
|
/**
|
|
70566
71668
|
* Query filter for `getHistory` (spec §4.2). Every field is a narrowing
|
|
@@ -71515,7 +72617,18 @@ var OauthIntegrationDescriptorSchema = object({
|
|
|
71515
72617
|
* The value is baked into the authorization code at `/authorize` and travels
|
|
71516
72618
|
* on the tokens, so editing this field changes FUTURE links only.
|
|
71517
72619
|
*/
|
|
71518
|
-
refreshTokenTtlSec: union([number$1().int().positive(), literal("never")]).optional()
|
|
72620
|
+
refreshTokenTtlSec: union([number$1().int().positive(), literal("never")]).optional(),
|
|
72621
|
+
/**
|
|
72622
|
+
* The integration instances an account can be linked to (planning#415),
|
|
72623
|
+
* beside the default one. `/api/oauth2/authorize?instance=<id>` is accepted
|
|
72624
|
+
* only for an id listed here; with no `instance=` the link is made to the
|
|
72625
|
+
* default instance, which is never listed and always accepted — it is the
|
|
72626
|
+
* address third parties already hold. Omit for a single-instance integration.
|
|
72627
|
+
*/
|
|
72628
|
+
instances: array(object({
|
|
72629
|
+
id: string(),
|
|
72630
|
+
displayName: string()
|
|
72631
|
+
})).optional()
|
|
71519
72632
|
});
|
|
71520
72633
|
method(_void(), OauthIntegrationDescriptorSchema, { auth: "admin" });
|
|
71521
72634
|
/**
|
|
@@ -71938,9 +73051,13 @@ var petFeederCapability = {
|
|
|
71938
73051
|
*/
|
|
71939
73052
|
durability: "session",
|
|
71940
73053
|
/**
|
|
71941
|
-
* History (D850): recorded series.
|
|
73054
|
+
* History (D850): recorded series. `feeding` / `eating` are the meals the
|
|
73055
|
+
* device hero's "Meals" button names — change-only booleans, so a dispense
|
|
73056
|
+
* or a pet at the bowl is one span on the history.
|
|
71942
73057
|
*/
|
|
71943
73058
|
history: { fields: {
|
|
73059
|
+
feeding: { kind: "boolean" },
|
|
73060
|
+
eating: { kind: "boolean" },
|
|
71944
73061
|
foodLevel: { kind: "gauge" },
|
|
71945
73062
|
bowlFood: { kind: "gauge" },
|
|
71946
73063
|
food1: { kind: "gauge" },
|
|
@@ -72001,6 +73118,11 @@ var petFountainCapability = {
|
|
|
72001
73118
|
deviceNative: true,
|
|
72002
73119
|
mode: "singleton",
|
|
72003
73120
|
deviceTypes: [DeviceType.PetFountain],
|
|
73121
|
+
power: {
|
|
73122
|
+
kind: "field",
|
|
73123
|
+
field: "pumpRunning",
|
|
73124
|
+
set: "setPump"
|
|
73125
|
+
},
|
|
72004
73126
|
methods: {
|
|
72005
73127
|
/** Start or stop the pump. */
|
|
72006
73128
|
setPump: method(object({
|
|
@@ -73576,7 +74698,6 @@ var POWER_METER_DEVICE_TYPES = [
|
|
|
73576
74698
|
DeviceType.Light,
|
|
73577
74699
|
DeviceType.Fan,
|
|
73578
74700
|
DeviceType.Climate,
|
|
73579
|
-
DeviceType.Thermostat,
|
|
73580
74701
|
DeviceType.Humidifier,
|
|
73581
74702
|
DeviceType.WaterHeater,
|
|
73582
74703
|
DeviceType.MediaPlayer,
|
|
@@ -73585,8 +74706,7 @@ var POWER_METER_DEVICE_TYPES = [
|
|
|
73585
74706
|
DeviceType.Dishwasher,
|
|
73586
74707
|
DeviceType.Vacuum,
|
|
73587
74708
|
DeviceType.LawnMower,
|
|
73588
|
-
DeviceType.PetFeeder
|
|
73589
|
-
DeviceType.Heater
|
|
74709
|
+
DeviceType.PetFeeder
|
|
73590
74710
|
];
|
|
73591
74711
|
/** Everything but a bare `Sensor`: a sensor that takes its reading from another sensor adds nothing. */
|
|
73592
74712
|
var POWER_METER_CUSTOMIZE_DEVICE_TYPES = POWER_METER_DEVICE_TYPES.filter((type) => type !== DeviceType.Sensor);
|
|
@@ -73699,118 +74819,6 @@ var powerMeterCapability = {
|
|
|
73699
74819
|
volatileStateFields: ["lastFetchedAt"],
|
|
73700
74820
|
customize: powerMeterCustomize
|
|
73701
74821
|
};
|
|
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
74822
|
/**
|
|
73815
74823
|
* Presence cap. Models HA `person.*` and `device_tracker.*` entities
|
|
73816
74824
|
* on `DeviceType.Presence`. Read-only — no setters: presence is
|
|
@@ -77277,9 +78285,7 @@ var RUNNING_CUSTOMIZE_DEVICE_TYPES = [
|
|
|
77277
78285
|
DeviceType.Humidifier,
|
|
77278
78286
|
DeviceType.Fan,
|
|
77279
78287
|
DeviceType.Climate,
|
|
77280
|
-
DeviceType.
|
|
77281
|
-
DeviceType.PetFeeder,
|
|
77282
|
-
DeviceType.Heater
|
|
78288
|
+
DeviceType.PetFeeder
|
|
77283
78289
|
];
|
|
77284
78290
|
var runningStateCapability = {
|
|
77285
78291
|
name: "running-state",
|
|
@@ -78551,6 +79557,13 @@ var SsoBridgeClaimsSchema = object({
|
|
|
78551
79557
|
/** OAuth authorization-code binding — set only on `oauth-code` tokens. */
|
|
78552
79558
|
redirectUri: string().optional(),
|
|
78553
79559
|
integrationId: string().optional(),
|
|
79560
|
+
/**
|
|
79561
|
+
* The integration INSTANCE an OAuth link was made for (planning#415). Set on
|
|
79562
|
+
* `oauth-code`, `oauth-access` and `oauth-refresh` tokens of a link made
|
|
79563
|
+
* with `instance=`; absent on a link to the default instance. A cloud relay
|
|
79564
|
+
* reads it off the access token to route; the hub answers from the session.
|
|
79565
|
+
*/
|
|
79566
|
+
instanceId: string().optional(),
|
|
78554
79567
|
/** JWT ID — unique per issued code; consumed-set enforces single-use. */
|
|
78555
79568
|
jti: string().optional(),
|
|
78556
79569
|
/** PKCE S256 challenge — set only on `oauth-code` tokens issued to a public
|
|
@@ -79367,8 +80380,7 @@ var switchCapability = {
|
|
|
79367
80380
|
DeviceType.Switch,
|
|
79368
80381
|
DeviceType.Siren,
|
|
79369
80382
|
DeviceType.Light,
|
|
79370
|
-
DeviceType.Fan
|
|
79371
|
-
DeviceType.Heater
|
|
80383
|
+
DeviceType.Fan
|
|
79372
80384
|
],
|
|
79373
80385
|
methods: { setState: method(object({
|
|
79374
80386
|
deviceId: number$1(),
|
|
@@ -80062,7 +81074,7 @@ var temperatureSensorCapability = {
|
|
|
80062
81074
|
scope: "device",
|
|
80063
81075
|
deviceNative: true,
|
|
80064
81076
|
mode: "singleton",
|
|
80065
|
-
deviceTypes: [DeviceType.Sensor, DeviceType.
|
|
81077
|
+
deviceTypes: [DeviceType.Sensor, DeviceType.Climate],
|
|
80066
81078
|
methods: {},
|
|
80067
81079
|
status: {
|
|
80068
81080
|
schema: TemperatureSensorStatusSchema,
|
|
@@ -80930,6 +81942,8 @@ var OauthSessionSummarySchema = object({
|
|
|
80930
81942
|
userId: string(),
|
|
80931
81943
|
username: string(),
|
|
80932
81944
|
integrationId: string(),
|
|
81945
|
+
/** The instance the link was made for; `null` = the default instance. */
|
|
81946
|
+
instanceId: string().nullable().optional(),
|
|
80933
81947
|
scopes: array(TokenScopeSchema),
|
|
80934
81948
|
createdAt: number$1(),
|
|
80935
81949
|
lastUsedAt: number$1(),
|
|
@@ -81029,6 +82043,9 @@ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(_void(), ob
|
|
|
81029
82043
|
access: "view"
|
|
81030
82044
|
}), method(object({
|
|
81031
82045
|
integrationId: string(),
|
|
82046
|
+
/** The integration instance the link is for. Omit for the default
|
|
82047
|
+
* instance — never pass the addon id to mean it. */
|
|
82048
|
+
instanceId: string().optional(),
|
|
81032
82049
|
userId: string(),
|
|
81033
82050
|
username: string(),
|
|
81034
82051
|
scopes: array(TokenScopeSchema),
|
|
@@ -81067,7 +82084,13 @@ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(_void(), ob
|
|
|
81067
82084
|
}), method(object({ token: string() }), object({
|
|
81068
82085
|
userId: string(),
|
|
81069
82086
|
username: string(),
|
|
81070
|
-
scopes: array(TokenScopeSchema)
|
|
82087
|
+
scopes: array(TokenScopeSchema),
|
|
82088
|
+
/** The instance the link was made for, read from the SESSION.
|
|
82089
|
+
* Absent = the default instance. `oauthLinkServesInstance` is the
|
|
82090
|
+
* helper a per-instance route calls to compare it with the instance
|
|
82091
|
+
* of its path; no route declares `perInstance` yet (the Alexa
|
|
82092
|
+
* per-instance route, T3, is the first consumer). */
|
|
82093
|
+
instanceId: string().optional()
|
|
81071
82094
|
}).nullable(), { access: "view" }), method(object({ userId: string() }), array(ExternalIdentitySchema), { auth: "admin" }), method(ExternalIdentityKeySchema, object({
|
|
81072
82095
|
userId: string(),
|
|
81073
82096
|
issuer: string()
|
|
@@ -81709,6 +82732,13 @@ var waterHeaterCapability = {
|
|
|
81709
82732
|
deviceNative: true,
|
|
81710
82733
|
mode: "singleton",
|
|
81711
82734
|
deviceTypes: [DeviceType.WaterHeater],
|
|
82735
|
+
power: {
|
|
82736
|
+
kind: "mode",
|
|
82737
|
+
field: "operationMode",
|
|
82738
|
+
offValue: "off",
|
|
82739
|
+
set: "setOperationMode",
|
|
82740
|
+
lastField: "lastActiveMode"
|
|
82741
|
+
},
|
|
81712
82742
|
methods: {
|
|
81713
82743
|
setTargetTemp: method(object({
|
|
81714
82744
|
deviceId: number$1().int().nonnegative(),
|
|
@@ -83367,7 +84397,6 @@ var DEVICE_LOCAL_STATE_CAPS = {
|
|
|
83367
84397
|
control: controlCapability,
|
|
83368
84398
|
cover: coverCapability,
|
|
83369
84399
|
dayNight: dayNightCapability,
|
|
83370
|
-
deviceDiscovery: deviceDiscoveryCapability,
|
|
83371
84400
|
deviceStatus: deviceStatusCapability,
|
|
83372
84401
|
dishwasher: dishwasherCapability,
|
|
83373
84402
|
doNotDisturb: doNotDisturbCapability,
|
|
@@ -84462,15 +85491,16 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
84462
85491
|
});
|
|
84463
85492
|
const meta = await this.ctx.api.deviceManager.loadMeta.query({ deviceId: id });
|
|
84464
85493
|
if (meta === null) throw new Error(`${this.providerName}: no persisted meta for "${stableId}" (id ${id}) — cannot reload`);
|
|
84465
|
-
const
|
|
84466
|
-
|
|
85494
|
+
const Class = this.deviceClassForRow({
|
|
85495
|
+
type: meta.type,
|
|
85496
|
+
stableId
|
|
85497
|
+
});
|
|
84467
85498
|
if (!Class) throw new Error(`${this.providerName}: no device class for type "${meta.type}" — "${stableId}" is an accessory child; reload its parent instead`);
|
|
84468
85499
|
await devices.create(stableId, Class, {}, meta.parentDeviceId ?? null);
|
|
84469
85500
|
const rows = await this.ctx.api.deviceManager.listPersistedByAddon.query({ addonId: this.addonId });
|
|
84470
85501
|
for (const row of rows) {
|
|
84471
85502
|
if (row.parentDeviceId !== id) continue;
|
|
84472
|
-
const
|
|
84473
|
-
const ChildClass = childType !== void 0 ? this.deviceClasses[childType] : void 0;
|
|
85503
|
+
const ChildClass = this.deviceClassForRow(row);
|
|
84474
85504
|
if (!ChildClass) continue;
|
|
84475
85505
|
try {
|
|
84476
85506
|
await devices.create(row.stableId, ChildClass, {}, id);
|
|
@@ -84497,6 +85527,16 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
84497
85527
|
return { deviceId: id };
|
|
84498
85528
|
}
|
|
84499
85529
|
/**
|
|
85530
|
+
* The class a PERSISTED row is rebuilt with. Default: by type, from
|
|
85531
|
+
* `deviceClasses`. A provider whose distinct devices share one type (D1033:
|
|
85532
|
+
* a radiator valve and a room climate are both `climate`) overrides this
|
|
85533
|
+
* and tells them apart by the row's stable id or config.
|
|
85534
|
+
*/
|
|
85535
|
+
deviceClassForRow(row) {
|
|
85536
|
+
const type = Object.values(DeviceType).find((t) => t === row.type);
|
|
85537
|
+
return type !== void 0 ? this.deviceClasses[type] : void 0;
|
|
85538
|
+
}
|
|
85539
|
+
/**
|
|
84500
85540
|
* Restore devices from persisted state. Two-pass:
|
|
84501
85541
|
*
|
|
84502
85542
|
* 1. **Top-level pass** — invokes `kernel.devices.create()` for every
|
|
@@ -84557,7 +85597,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
84557
85597
|
restored.add(saved.id);
|
|
84558
85598
|
return;
|
|
84559
85599
|
}
|
|
84560
|
-
const Class = this.
|
|
85600
|
+
const Class = this.deviceClassForRow(saved);
|
|
84561
85601
|
if (!Class) throw new Error(`no device class registered for type "${saved.type}"`);
|
|
84562
85602
|
if (saved.parentDeviceId !== null && !restored.has(saved.parentDeviceId)) throw new Error(`parent device ${saved.parentDeviceId} not restored`);
|
|
84563
85603
|
await this.healSavedConfig(saved, savedDevices);
|
|
@@ -84574,7 +85614,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
84574
85614
|
};
|
|
84575
85615
|
const topLevel = savedDevices.filter((saved) => saved.parentDeviceId === null);
|
|
84576
85616
|
const restoreOne = async (saved) => {
|
|
84577
|
-
if (!this.
|
|
85617
|
+
if (!this.deviceClassForRow(saved)) {
|
|
84578
85618
|
this.ctx.logger.warn("No device class registered for restored type — skipping", {
|
|
84579
85619
|
tags: {
|
|
84580
85620
|
deviceId: saved.id,
|
|
@@ -84613,7 +85653,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
84613
85653
|
const failedTopLevelIds = new Set(failures.map((failure) => failure.saved.id));
|
|
84614
85654
|
const childRows = savedDevices.filter((s) => s.parentDeviceId !== null);
|
|
84615
85655
|
for (const saved of childRows) {
|
|
84616
|
-
if (!this.
|
|
85656
|
+
if (!this.deviceClassForRow(saved)) continue;
|
|
84617
85657
|
if (saved.parentDeviceId === null) continue;
|
|
84618
85658
|
if (restored.has(saved.parentDeviceId)) {
|
|
84619
85659
|
try {
|
|
@@ -84667,7 +85707,9 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
84667
85707
|
}
|
|
84668
85708
|
};
|
|
84669
85709
|
DeviceType.HeatPump;
|
|
84670
|
-
DeviceType.
|
|
85710
|
+
DeviceType.Climate;
|
|
85711
|
+
DeviceRole.ClimateThermostat, DeviceRole.ClimateAc, DeviceRole.ClimateRadiator, DeviceRole.ClimateFloor, DeviceRole.ClimateHeater, DeviceRole.ClimateHeatPumpCircuit;
|
|
85712
|
+
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
85713
|
new Set(Object.values(DeviceType));
|
|
84672
85714
|
/** Parses the raw slice with the cap's own schema; `undefined` when it does not parse. */
|
|
84673
85715
|
function contribution(cap, schema, build) {
|
|
@@ -86673,7 +87715,7 @@ var HEAT_PUMP_FAMILY_CONTRIBUTIONS = { "heat-pump-status": contribution("heat-pu
|
|
|
86673
87715
|
...Object.values(CLIMATE_FAMILY_CONTRIBUTIONS),
|
|
86674
87716
|
...Object.values(HEAT_PUMP_FAMILY_CONTRIBUTIONS)
|
|
86675
87717
|
];
|
|
86676
|
-
DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.
|
|
87718
|
+
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
87719
|
/**
|
|
86678
87720
|
* Who looks like what (D1023). One look per {@link DeviceType}, refined by a
|
|
86679
87721
|
* {@link DeviceRole} where the role changes how the device reads (a leak
|
|
@@ -87317,9 +88359,9 @@ lookOf({
|
|
|
87317
88359
|
idle: INFO,
|
|
87318
88360
|
standby: MUTED
|
|
87319
88361
|
});
|
|
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;
|
|
88362
|
+
DeviceType.Climate;
|
|
88363
|
+
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;
|
|
88364
|
+
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
88365
|
Object.freeze({
|
|
87324
88366
|
"accessories.getStatus": {
|
|
87325
88367
|
capName: "accessories",
|
|
@@ -89169,41 +90211,6 @@ Object.freeze({
|
|
|
89169
90211
|
access: "create",
|
|
89170
90212
|
role: "admin"
|
|
89171
90213
|
},
|
|
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
90214
|
"deviceExport.applyDeviceSettingsPatch": {
|
|
89208
90215
|
capName: "device-export",
|
|
89209
90216
|
capScope: "system",
|
|
@@ -90751,6 +91758,13 @@ Object.freeze({
|
|
|
90751
91758
|
access: "create",
|
|
90752
91759
|
role: "viewer"
|
|
90753
91760
|
},
|
|
91761
|
+
"fanControl.setOscillationAxis": {
|
|
91762
|
+
capName: "fan-control",
|
|
91763
|
+
capScope: "device",
|
|
91764
|
+
addonId: null,
|
|
91765
|
+
access: "create",
|
|
91766
|
+
role: "viewer"
|
|
91767
|
+
},
|
|
90754
91768
|
"fanControl.setPercentage": {
|
|
90755
91769
|
capName: "fan-control",
|
|
90756
91770
|
capScope: "device",
|
|
@@ -91213,6 +92227,13 @@ Object.freeze({
|
|
|
91213
92227
|
access: "view",
|
|
91214
92228
|
role: "viewer"
|
|
91215
92229
|
},
|
|
92230
|
+
"irTransmitter.learn": {
|
|
92231
|
+
capName: "ir-transmitter",
|
|
92232
|
+
capScope: "device",
|
|
92233
|
+
addonId: null,
|
|
92234
|
+
access: "create",
|
|
92235
|
+
role: "viewer"
|
|
92236
|
+
},
|
|
91216
92237
|
"irTransmitter.send": {
|
|
91217
92238
|
capName: "ir-transmitter",
|
|
91218
92239
|
capScope: "device",
|
|
@@ -97772,7 +98793,7 @@ function projectAction(cap, action) {
|
|
|
97772
98793
|
};
|
|
97773
98794
|
}
|
|
97774
98795
|
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.
|
|
98796
|
+
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
98797
|
DeviceFeature.BatteryOperated;
|
|
97777
98798
|
function bound(cap, methods = [], authority = "reported") {
|
|
97778
98799
|
return {
|
|
@@ -98261,35 +99282,6 @@ Object.freeze({
|
|
|
98261
99282
|
form: "single",
|
|
98262
99283
|
optional: false
|
|
98263
99284
|
}],
|
|
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
99285
|
"deviceExport.applyDeviceSettingsPatch": [{
|
|
98294
99286
|
name: "deviceId",
|
|
98295
99287
|
form: "single",
|
|
@@ -98853,6 +99845,11 @@ Object.freeze({
|
|
|
98853
99845
|
form: "single",
|
|
98854
99846
|
optional: false
|
|
98855
99847
|
}],
|
|
99848
|
+
"fanControl.setOscillationAxis": [{
|
|
99849
|
+
name: "deviceId",
|
|
99850
|
+
form: "single",
|
|
99851
|
+
optional: false
|
|
99852
|
+
}],
|
|
98856
99853
|
"fanControl.setPercentage": [{
|
|
98857
99854
|
name: "deviceId",
|
|
98858
99855
|
form: "single",
|
|
@@ -99088,6 +100085,11 @@ Object.freeze({
|
|
|
99088
100085
|
form: "single",
|
|
99089
100086
|
optional: false
|
|
99090
100087
|
}],
|
|
100088
|
+
"irTransmitter.learn": [{
|
|
100089
|
+
name: "deviceId",
|
|
100090
|
+
form: "single",
|
|
100091
|
+
optional: false
|
|
100092
|
+
}],
|
|
99091
100093
|
"irTransmitter.send": [{
|
|
99092
100094
|
name: "deviceId",
|
|
99093
100095
|
form: "single",
|
|
@@ -101027,7 +102029,10 @@ var NC_SYSTEM_EVENT_FILTER_KEYS = [
|
|
|
101027
102029
|
"deviceTypes",
|
|
101028
102030
|
"nodeIds",
|
|
101029
102031
|
"packageNames",
|
|
101030
|
-
"consumableLowPercent"
|
|
102032
|
+
"consumableLowPercent",
|
|
102033
|
+
"commandActions",
|
|
102034
|
+
"commandOutcomes",
|
|
102035
|
+
"commandActorKinds"
|
|
101031
102036
|
];
|
|
101032
102037
|
new Map(Object.entries({
|
|
101033
102038
|
"device-online": "device-events",
|
|
@@ -101050,6 +102055,7 @@ new Map(Object.entries({
|
|
|
101050
102055
|
"recording-degraded": "device-events",
|
|
101051
102056
|
"device-attention-required": "device-events",
|
|
101052
102057
|
"device-attention-resolved": "device-events",
|
|
102058
|
+
"device-command": "device-events",
|
|
101053
102059
|
"node-online": "system",
|
|
101054
102060
|
"node-offline": "system",
|
|
101055
102061
|
"node-restarting": "system",
|
|
@@ -101150,6 +102156,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
|
|
|
101150
102156
|
label: "Needs attention",
|
|
101151
102157
|
kinds: ["device-attention-required", "device-attention-resolved"]
|
|
101152
102158
|
},
|
|
102159
|
+
{
|
|
102160
|
+
id: "device-command",
|
|
102161
|
+
family: "device-events",
|
|
102162
|
+
label: "Commands given to devices",
|
|
102163
|
+
kinds: ["device-command"]
|
|
102164
|
+
},
|
|
101153
102165
|
{
|
|
101154
102166
|
id: "nodes",
|
|
101155
102167
|
family: "system",
|
|
@@ -101422,6 +102434,7 @@ var DEVICE_EVENTS = [
|
|
|
101422
102434
|
"recording-healthy",
|
|
101423
102435
|
"recording-degraded",
|
|
101424
102436
|
...DEVICE_ATTENTION,
|
|
102437
|
+
"device-command",
|
|
101425
102438
|
...ALARM
|
|
101426
102439
|
];
|
|
101427
102440
|
/** Every kind a summary may speak about — the two summarisable families. */
|
|
@@ -101575,6 +102588,9 @@ var STATE_SUMMARY_VARS = [
|
|
|
101575
102588
|
[...RULE_BODY_ONLY],
|
|
101576
102589
|
[...RULE_TEXT],
|
|
101577
102590
|
([...RULE_TEXT], [...DEVICE_EVENTS]),
|
|
102591
|
+
[...RULE_TEXT],
|
|
102592
|
+
[...RULE_TEXT],
|
|
102593
|
+
([...RULE_TEXT], [...DEVICE_EVENTS]),
|
|
101578
102594
|
([...RULE_TEXT], [...COMBINED_ALARM]),
|
|
101579
102595
|
([...RULE_TEXT], [...COMBINED_ALARM]),
|
|
101580
102596
|
([...RULE_TEXT], [...DEVICE_EVENTS]),
|
|
@@ -102327,6 +103343,27 @@ function ingestReading(input) {
|
|
|
102327
103343
|
value: converted
|
|
102328
103344
|
};
|
|
102329
103345
|
}
|
|
103346
|
+
function collapse(value) {
|
|
103347
|
+
return value.trim().replace(/\s+/g, " ");
|
|
103348
|
+
}
|
|
103349
|
+
/** "KLF-200" → "KLF200". A hyphen between words ("Home Hub") stays. */
|
|
103350
|
+
function compactModelToken(value) {
|
|
103351
|
+
return value.replace(/([A-Za-z])-(\d)/g, "$1$2");
|
|
103352
|
+
}
|
|
103353
|
+
/**
|
|
103354
|
+
* A Gateway device: "<Brand> <Model>". An unknown model is the brand alone
|
|
103355
|
+
* (the caller passes the brand-level fallback it already has, e.g. "Velux KLF-200").
|
|
103356
|
+
*/
|
|
103357
|
+
function gatewayDeviceName(brand, model) {
|
|
103358
|
+
const brandPart = compactModelToken(collapse(brand));
|
|
103359
|
+
const raw = collapse(model ?? "");
|
|
103360
|
+
if (raw.length === 0) return brandPart;
|
|
103361
|
+
const modelPart = compactModelToken(raw);
|
|
103362
|
+
const prefix = `${brandPart.toLowerCase()} `;
|
|
103363
|
+
if (modelPart.toLowerCase() === brandPart.toLowerCase()) return brandPart;
|
|
103364
|
+
if (modelPart.toLowerCase().startsWith(prefix)) return modelPart;
|
|
103365
|
+
return `${brandPart} ${modelPart}`;
|
|
103366
|
+
}
|
|
102330
103367
|
/**
|
|
102331
103368
|
* Closes the gap D49 leaves: an integration deleted while its provider was down never emits an
|
|
102332
103369
|
* event the provider sees. An integration id absent from an ANSWERED list is only a suspect; its
|
|
@@ -103713,7 +104750,7 @@ function buildEnpalCandidates(input) {
|
|
|
103713
104750
|
const boxChildren = box === void 0 ? [] : reportedChildren(box, snapshot);
|
|
103714
104751
|
const out = [{
|
|
103715
104752
|
childNativeId: "box",
|
|
103716
|
-
name:
|
|
104753
|
+
name: gatewayDeviceName("Enpal", "Box"),
|
|
103717
104754
|
type: DeviceType.Gateway,
|
|
103718
104755
|
status: input.box.online ? "online" : "offline",
|
|
103719
104756
|
metadata: METADATA,
|
|
@@ -103837,7 +104874,7 @@ function buildEnpalAdoptionProvider(deps) {
|
|
|
103837
104874
|
const specIds = reportedSpecIds(equipment, candidate);
|
|
103838
104875
|
const { deviceId, accessoryDeviceIds } = await deps.adoptGateway({
|
|
103839
104876
|
integrationId,
|
|
103840
|
-
name: perCandidate?.[equipmentId]?.name ??
|
|
104877
|
+
name: perCandidate?.[equipmentId]?.name ?? gatewayDeviceName("Enpal", "Box"),
|
|
103841
104878
|
specIds,
|
|
103842
104879
|
childLayout: childLayoutOf(equipment, specIds)
|
|
103843
104880
|
});
|
|
@@ -104636,7 +105673,39 @@ var enpalConnectionsEntryKind = defineEntryKind("connections", {
|
|
|
104636
105673
|
});
|
|
104637
105674
|
/** Every kind this addon declares, keyed by its name. */
|
|
104638
105675
|
var enpalRecordKinds = { connections: enpalConnectionsEntryKind };
|
|
105676
|
+
var REJECTED_KINDS = new Set([
|
|
105677
|
+
"unauthorized",
|
|
105678
|
+
"token-missing",
|
|
105679
|
+
"empty"
|
|
105680
|
+
]);
|
|
105681
|
+
function connectionForTest(values) {
|
|
105682
|
+
const host = values["host"];
|
|
105683
|
+
if (typeof host !== "string" || host.trim().length === 0) return null;
|
|
105684
|
+
const parsed = enpalConnectionSchema.safeParse({ ...values });
|
|
105685
|
+
return parsed.success ? parsed.data : null;
|
|
105686
|
+
}
|
|
105687
|
+
function testFailure(error) {
|
|
105688
|
+
if (error instanceof EnpalError) return {
|
|
105689
|
+
success: false,
|
|
105690
|
+
status: REJECTED_KINDS.has(error.kind) ? "rejected" : "inconclusive",
|
|
105691
|
+
error: error.message
|
|
105692
|
+
};
|
|
105693
|
+
return {
|
|
105694
|
+
success: false,
|
|
105695
|
+
status: "inconclusive",
|
|
105696
|
+
error: "The Enpal box did not answer"
|
|
105697
|
+
};
|
|
105698
|
+
}
|
|
105699
|
+
function liveProbe(blazor) {
|
|
105700
|
+
return (connection) => readEnpalSnapshot({
|
|
105701
|
+
connection,
|
|
105702
|
+
fetch: globalThis.fetch,
|
|
105703
|
+
now: () => Date.now(),
|
|
105704
|
+
blazor: blazor?.()
|
|
105705
|
+
});
|
|
105706
|
+
}
|
|
104639
105707
|
function buildEnpalInstances(records, deps) {
|
|
105708
|
+
const probe = deps.probe ?? liveProbe(deps.blazor);
|
|
104640
105709
|
return new RecordIntegrationInstances(records, enpalConnectionsEntryKind, {
|
|
104641
105710
|
description: {
|
|
104642
105711
|
role: "devices",
|
|
@@ -104646,18 +105715,36 @@ function buildEnpalInstances(records, deps) {
|
|
|
104646
105715
|
},
|
|
104647
105716
|
instanceMode: "multiple",
|
|
104648
105717
|
kind: "server",
|
|
104649
|
-
canTest:
|
|
105718
|
+
canTest: true,
|
|
104650
105719
|
kinds: [{
|
|
104651
105720
|
id: "connection",
|
|
104652
105721
|
label: "Enpal",
|
|
104653
105722
|
functionCap: "device-provider",
|
|
104654
|
-
role: "devices"
|
|
105723
|
+
role: "devices",
|
|
105724
|
+
canTest: true
|
|
104655
105725
|
}]
|
|
104656
105726
|
},
|
|
104657
105727
|
formSchema: () => buildConnectionFormSchema(),
|
|
104658
105728
|
initialBody: (name) => ({ name }),
|
|
104659
105729
|
configured: (body) => (body.host ?? "").length > 0,
|
|
104660
|
-
onChanged: () => deps.onChanged()
|
|
105730
|
+
onChanged: () => deps.onChanged(),
|
|
105731
|
+
test: async ({ values }) => {
|
|
105732
|
+
const connection = connectionForTest(values);
|
|
105733
|
+
if (connection === null) return {
|
|
105734
|
+
success: false,
|
|
105735
|
+
status: "rejected",
|
|
105736
|
+
error: "Host is required"
|
|
105737
|
+
};
|
|
105738
|
+
try {
|
|
105739
|
+
await probe(connection);
|
|
105740
|
+
return {
|
|
105741
|
+
success: true,
|
|
105742
|
+
status: "validated"
|
|
105743
|
+
};
|
|
105744
|
+
} catch (error) {
|
|
105745
|
+
return testFailure(error);
|
|
105746
|
+
}
|
|
105747
|
+
}
|
|
104661
105748
|
});
|
|
104662
105749
|
}
|
|
104663
105750
|
/**
|
|
@@ -104950,7 +106037,10 @@ var EnpalProviderAddon = class extends BaseDeviceProvider {
|
|
|
104950
106037
|
...rowsDepsOver(api),
|
|
104951
106038
|
kind: enpalConnectionsEntryKind
|
|
104952
106039
|
}));
|
|
104953
|
-
this.instances = buildEnpalInstances(records, {
|
|
106040
|
+
this.instances = buildEnpalInstances(records, {
|
|
106041
|
+
onChanged: () => this.reconcileIntegrations(),
|
|
106042
|
+
blazor: () => this.blazorReader ?? void 0
|
|
106043
|
+
});
|
|
104954
106044
|
this.blazorReader = await this.createBlazorBridge();
|
|
104955
106045
|
this.clients = new EnpalClientRegistry(this.ctx.logger, {
|
|
104956
106046
|
blazor: () => this.blazorReader ?? void 0,
|