@camstack/addon-provider-velux 0.2.153 → 0.2.155
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/dist/addon.js +584 -162
- package/dist/addon.mjs +584 -162
- package/package.json +2 -2
package/dist/addon.mjs
CHANGED
|
@@ -34,7 +34,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
34
34
|
}) : target, mod));
|
|
35
35
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
36
36
|
//#endregion
|
|
37
|
-
//#region ../types/dist/event-category-
|
|
37
|
+
//#region ../types/dist/event-category-BmPm98n0.mjs
|
|
38
38
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
39
39
|
EventCategory["SystemBoot"] = "system.boot";
|
|
40
40
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -518,6 +518,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
518
518
|
EventCategory["RunningStarted"] = "running.started";
|
|
519
519
|
/** Mirrors `runningStateCapability.events.onFinished`. */
|
|
520
520
|
EventCategory["RunningFinished"] = "running.finished";
|
|
521
|
+
/** Mirrors `deviceRunsCapability.events.onRunOpened` - a durable run record opened. */
|
|
522
|
+
EventCategory["DeviceRunOpened"] = "device-run.opened";
|
|
523
|
+
/** Mirrors `deviceRunsCapability.events.onRunClosed` - a durable run record closed. */
|
|
524
|
+
EventCategory["DeviceRunClosed"] = "device-run.closed";
|
|
521
525
|
/**
|
|
522
526
|
* Cap event fired by every device that registers the `event-emitter`
|
|
523
527
|
* capability. Mirrors `eventEmitterCapability.events.onEvent`. Carries
|
|
@@ -5866,7 +5870,7 @@ var ZodIssueCode = {
|
|
|
5866
5870
|
var ZodFirstPartyTypeKind;
|
|
5867
5871
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5868
5872
|
//#endregion
|
|
5869
|
-
//#region ../types/dist/sleep-
|
|
5873
|
+
//#region ../types/dist/sleep-BKzhQTSm.mjs
|
|
5870
5874
|
/**
|
|
5871
5875
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5872
5876
|
* window to float samples (D455).
|
|
@@ -9379,6 +9383,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
9379
9383
|
"device-ops",
|
|
9380
9384
|
"device-provider",
|
|
9381
9385
|
"device-removal-hook",
|
|
9386
|
+
"device-runs",
|
|
9382
9387
|
"device-state",
|
|
9383
9388
|
"device-status",
|
|
9384
9389
|
"dishwasher",
|
|
@@ -11323,7 +11328,7 @@ object({
|
|
|
11323
11328
|
params: record(string(), union([string(), number()])).optional()
|
|
11324
11329
|
});
|
|
11325
11330
|
//#endregion
|
|
11326
|
-
//#region ../types/dist/composition-
|
|
11331
|
+
//#region ../types/dist/composition-BiKWlUCc.mjs
|
|
11327
11332
|
/**
|
|
11328
11333
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11329
11334
|
*
|
|
@@ -12256,7 +12261,15 @@ var HelperSpecSchema = discriminatedUnion("kind", [
|
|
|
12256
12261
|
thresholdWatts: number().positive(),
|
|
12257
12262
|
startDwellMs: number().int().nonnegative(),
|
|
12258
12263
|
stopDwellMs: number().int().nonnegative(),
|
|
12259
|
-
member: HelperMemberSchema
|
|
12264
|
+
member: HelperMemberSchema,
|
|
12265
|
+
/**
|
|
12266
|
+
* The plug that powers the appliance (`switch.on`). Optional: when set, the
|
|
12267
|
+
* composition carries a `power-source` feature, so the hub can cut and restore
|
|
12268
|
+
* that plug (power-source link of a NEW-target composition).
|
|
12269
|
+
*/
|
|
12270
|
+
powerSwitch: HelperMemberSchema.optional(),
|
|
12271
|
+
/** How long a forced restart keeps the plug off; the cap's default when unset. */
|
|
12272
|
+
offSeconds: number().optional()
|
|
12260
12273
|
}),
|
|
12261
12274
|
object({
|
|
12262
12275
|
kind: literal("statistic"),
|
|
@@ -13654,7 +13667,13 @@ var MigrationLedgerEntrySchema = object({
|
|
|
13654
13667
|
/** Why it is `blocked` or `held` (`dependency-failed`, `snapshot-refused`, …). */
|
|
13655
13668
|
reason: string().nullable(),
|
|
13656
13669
|
nodeId: string().nullable(),
|
|
13657
|
-
bootId: string().nullable()
|
|
13670
|
+
bootId: string().nullable(),
|
|
13671
|
+
/**
|
|
13672
|
+
* The catalogued meta of this id (description, body kind, store, `dependsOn`…),
|
|
13673
|
+
* joined by the engine from the catalogues it knows this boot. Absent when no
|
|
13674
|
+
* catalogue lists the id (a newer database).
|
|
13675
|
+
*/
|
|
13676
|
+
meta: MigrationMetaSchema.optional()
|
|
13658
13677
|
});
|
|
13659
13678
|
/** A catalogued migration that is not applied, with where it stands. */
|
|
13660
13679
|
var MigrationPendingEntrySchema = object({
|
|
@@ -19461,6 +19480,14 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
19461
19480
|
}
|
|
19462
19481
|
} }
|
|
19463
19482
|
},
|
|
19483
|
+
"device-runs": {
|
|
19484
|
+
scope: "device",
|
|
19485
|
+
noun: t("Runs", "Esecuzioni"),
|
|
19486
|
+
events: {
|
|
19487
|
+
onRunOpened: { notOperatorFacing: "the durable record of a run that the running-state start flip already announces" },
|
|
19488
|
+
onRunClosed: { notOperatorFacing: "the durable record of a run that the running-state finish flip already announces" }
|
|
19489
|
+
}
|
|
19490
|
+
},
|
|
19464
19491
|
update: {
|
|
19465
19492
|
scope: "device",
|
|
19466
19493
|
noun: t("Firmware update", "Aggiornamento firmware"),
|
|
@@ -46043,6 +46070,117 @@ function actuator(input) {
|
|
|
46043
46070
|
};
|
|
46044
46071
|
}
|
|
46045
46072
|
new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
|
|
46073
|
+
var PowerSourceRefusalReasonSchema = _enum([
|
|
46074
|
+
"no-power-source",
|
|
46075
|
+
"switch-missing",
|
|
46076
|
+
"switch-offline",
|
|
46077
|
+
"switch-unknown",
|
|
46078
|
+
"restart-in-progress",
|
|
46079
|
+
"switch-call-failed"
|
|
46080
|
+
]);
|
|
46081
|
+
var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
|
|
46082
|
+
accepted: literal(false),
|
|
46083
|
+
reason: PowerSourceRefusalReasonSchema,
|
|
46084
|
+
/** One line for the operator, naming the switch. */
|
|
46085
|
+
message: string()
|
|
46086
|
+
})]);
|
|
46087
|
+
var PowerSourceInfoSchema = object({
|
|
46088
|
+
/** False when the device has no power source linked; the rest is then empty. */
|
|
46089
|
+
linked: boolean(),
|
|
46090
|
+
switchDeviceId: number().nullable(),
|
|
46091
|
+
switchName: string().nullable(),
|
|
46092
|
+
/** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
|
|
46093
|
+
switchOn: boolean().nullable(),
|
|
46094
|
+
switchOnline: boolean().nullable(),
|
|
46095
|
+
restartInProgress: boolean(),
|
|
46096
|
+
offSeconds: number().int()
|
|
46097
|
+
});
|
|
46098
|
+
/** The composed slice: the switch state the link mirrors, plus the stored off time. */
|
|
46099
|
+
var PowerSourceStateSchema = object({
|
|
46100
|
+
/** Null while the switch has not reported: unknown is never `false` (D393). */
|
|
46101
|
+
switchOn: boolean().nullable(),
|
|
46102
|
+
offSeconds: number().int(),
|
|
46103
|
+
lastFetchedAt: number()
|
|
46104
|
+
});
|
|
46105
|
+
var POWER_SOURCE_DEVICE_TYPES = [
|
|
46106
|
+
DeviceType.Gateway,
|
|
46107
|
+
DeviceType.Hub,
|
|
46108
|
+
DeviceType.Camera,
|
|
46109
|
+
DeviceType.Sensor,
|
|
46110
|
+
DeviceType.MediaPlayer,
|
|
46111
|
+
DeviceType.Vacuum,
|
|
46112
|
+
DeviceType.Generic,
|
|
46113
|
+
DeviceType.Washer,
|
|
46114
|
+
DeviceType.Dryer,
|
|
46115
|
+
DeviceType.Dishwasher,
|
|
46116
|
+
DeviceType.PetFountain,
|
|
46117
|
+
DeviceType.Fan,
|
|
46118
|
+
DeviceType.Humidifier,
|
|
46119
|
+
DeviceType.Climate,
|
|
46120
|
+
DeviceType.WaterHeater
|
|
46121
|
+
];
|
|
46122
|
+
var powerSourceCustomize = {
|
|
46123
|
+
version: 1,
|
|
46124
|
+
targets: [{
|
|
46125
|
+
id: "switch",
|
|
46126
|
+
cardinality: "scalar",
|
|
46127
|
+
vocabulary: {
|
|
46128
|
+
cap: "power-source",
|
|
46129
|
+
stateField: "switchOn"
|
|
46130
|
+
},
|
|
46131
|
+
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
46132
|
+
apply: "add",
|
|
46133
|
+
path: "switchOn",
|
|
46134
|
+
sources: [{
|
|
46135
|
+
kind: "switch",
|
|
46136
|
+
transforms: [{ kind: "identity" }]
|
|
46137
|
+
}],
|
|
46138
|
+
companions: [{
|
|
46139
|
+
kind: "constant",
|
|
46140
|
+
path: "offSeconds",
|
|
46141
|
+
value: 10,
|
|
46142
|
+
when: "add"
|
|
46143
|
+
}, {
|
|
46144
|
+
kind: "clock",
|
|
46145
|
+
path: "lastFetchedAt",
|
|
46146
|
+
when: "always"
|
|
46147
|
+
}],
|
|
46148
|
+
sourceReport: { status: "assignment-status" }
|
|
46149
|
+
}]
|
|
46150
|
+
};
|
|
46151
|
+
var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
|
|
46152
|
+
var powerSourceCapability = {
|
|
46153
|
+
name: "power-source",
|
|
46154
|
+
scope: "device",
|
|
46155
|
+
mode: "singleton",
|
|
46156
|
+
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
46157
|
+
methods: {
|
|
46158
|
+
getStatus: systemMethod(deviceInput$1, PowerSourceInfoSchema, { auth: "admin" }),
|
|
46159
|
+
powerOn: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
46160
|
+
kind: "mutation",
|
|
46161
|
+
auth: "admin"
|
|
46162
|
+
}),
|
|
46163
|
+
powerOff: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
46164
|
+
kind: "mutation",
|
|
46165
|
+
auth: "admin"
|
|
46166
|
+
}),
|
|
46167
|
+
forceRestart: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
|
|
46168
|
+
kind: "mutation",
|
|
46169
|
+
auth: "admin"
|
|
46170
|
+
})
|
|
46171
|
+
},
|
|
46172
|
+
runtimeState: PowerSourceStateSchema,
|
|
46173
|
+
customize: powerSourceCustomize,
|
|
46174
|
+
/**
|
|
46175
|
+
* Runtime-state durability: **session** — mirrored from the linked switch on
|
|
46176
|
+
* its next report; a restored value would outlive the reading it names.
|
|
46177
|
+
*
|
|
46178
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
46179
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
46180
|
+
*/
|
|
46181
|
+
durability: "session",
|
|
46182
|
+
history: "none"
|
|
46183
|
+
};
|
|
46046
46184
|
DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
|
|
46047
46185
|
DeviceType.Camera;
|
|
46048
46186
|
`${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
|
|
@@ -47468,6 +47606,101 @@ method(object({ deviceId: number().int() }), array(SeriesInfoSchema).readonly())
|
|
|
47468
47606
|
kind: "mutation",
|
|
47469
47607
|
auth: "admin"
|
|
47470
47608
|
});
|
|
47609
|
+
/**
|
|
47610
|
+
* device-runs - the durable record of ONE run of a device: a washing cycle, a
|
|
47611
|
+
* dishwasher cycle, later a mow or a clean (D992).
|
|
47612
|
+
*
|
|
47613
|
+
* A run is a DOCUMENT (opened at one instant, closed at another, carrying what
|
|
47614
|
+
* was measured over it), never a sample - device-history keeps the numbers over
|
|
47615
|
+
* time and its `device-runs` provider keeps these documents beside them, because
|
|
47616
|
+
* the energy of a run is a question about device-history's own series. The
|
|
47617
|
+
* record is written from the `running-state` flip on `DeviceStateChanged`; it
|
|
47618
|
+
* survives a restart (the open row is on disk) and a missed flip (boot compares
|
|
47619
|
+
* the open rows against the live snapshots).
|
|
47620
|
+
*
|
|
47621
|
+
* Every measurement is `number | null` and `null` means "could not be
|
|
47622
|
+
* measured", never `0` (D393). A run closed by the boot reconcile because the
|
|
47623
|
+
* device stopped while nobody was watching has `outcome: 'unknown'`.
|
|
47624
|
+
*/
|
|
47625
|
+
var DeviceRunKindSchema = _enum([
|
|
47626
|
+
"appliance",
|
|
47627
|
+
"mower",
|
|
47628
|
+
"vacuum"
|
|
47629
|
+
]);
|
|
47630
|
+
var DeviceRunStateSchema = _enum(["open", "closed"]);
|
|
47631
|
+
var DeviceRunOutcomeSchema = _enum([
|
|
47632
|
+
"completed",
|
|
47633
|
+
"aborted",
|
|
47634
|
+
"error",
|
|
47635
|
+
"unknown"
|
|
47636
|
+
]);
|
|
47637
|
+
/** What opened or closed the run. `reconcile` is the boot comparison against live state. */
|
|
47638
|
+
var DeviceRunSourceSchema = _enum([
|
|
47639
|
+
"running-state",
|
|
47640
|
+
"activity",
|
|
47641
|
+
"reconcile"
|
|
47642
|
+
]);
|
|
47643
|
+
/** How the energy of a run was obtained; `null` when it could not be. */
|
|
47644
|
+
var DeviceRunEnergyMethodSchema = _enum(["counter-delta", "history-integral"]);
|
|
47645
|
+
var DeviceRunPhaseSchema = object({
|
|
47646
|
+
phase: string(),
|
|
47647
|
+
at: number()
|
|
47648
|
+
});
|
|
47649
|
+
/** The kind-specific block; a later phase adds a branch, nothing else changes. */
|
|
47650
|
+
var DeviceRunDetailSchema = discriminatedUnion("kind", [object({
|
|
47651
|
+
kind: literal("appliance"),
|
|
47652
|
+
/** kWh used over the run. */
|
|
47653
|
+
energyKwh: number().nullable().meta(readingMeta("energy")),
|
|
47654
|
+
energyMethod: DeviceRunEnergyMethodSchema.nullable(),
|
|
47655
|
+
/** Highest power seen while the run was open. */
|
|
47656
|
+
peakW: number().nullable().meta(readingMeta("power")),
|
|
47657
|
+
/** Time-weighted mean power while the run was open. */
|
|
47658
|
+
avgW: number().nullable().meta(readingMeta("power")),
|
|
47659
|
+
/** The program the appliance said it ran when the run opened. */
|
|
47660
|
+
program: string().nullable(),
|
|
47661
|
+
remainingAtStartSec: number().nullable().meta(readingMeta("duration")),
|
|
47662
|
+
/** The last phase the device reported before the run closed. */
|
|
47663
|
+
finalPhase: string().nullable(),
|
|
47664
|
+
/**
|
|
47665
|
+
* The meter's energy counter when the run opened; `null` when the meter had
|
|
47666
|
+
* not said (or the run was opened late, by the boot reconcile). It lives in
|
|
47667
|
+
* the row so a run that outlives a restart still ends with `end - start`.
|
|
47668
|
+
*/
|
|
47669
|
+
energyStartKwh: number().nullable().meta(readingMeta("energy")),
|
|
47670
|
+
/** Milliseconds of power readings behind `avgW` - the weight a restart resumes the mean with. */
|
|
47671
|
+
powerObservedMs: number()
|
|
47672
|
+
})]);
|
|
47673
|
+
var DeviceRunSchema = object({
|
|
47674
|
+
/** `${deviceId}:${startedAt}`. */
|
|
47675
|
+
runId: string(),
|
|
47676
|
+
deviceId: number().int(),
|
|
47677
|
+
kind: DeviceRunKindSchema,
|
|
47678
|
+
state: DeviceRunStateSchema,
|
|
47679
|
+
startedAt: number(),
|
|
47680
|
+
endedAt: number().nullable(),
|
|
47681
|
+
durationMs: number().nullable(),
|
|
47682
|
+
outcome: DeviceRunOutcomeSchema.nullable(),
|
|
47683
|
+
startSource: DeviceRunSourceSchema,
|
|
47684
|
+
endSource: DeviceRunSourceSchema.nullable(),
|
|
47685
|
+
/** Where the device's running bit came from (`running-state.source`); `null` when it did not say. */
|
|
47686
|
+
runningSource: _enum(["native", "assigned"]).nullable(),
|
|
47687
|
+
phases: array(DeviceRunPhaseSchema),
|
|
47688
|
+
detail: DeviceRunDetailSchema
|
|
47689
|
+
});
|
|
47690
|
+
DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, method(object({
|
|
47691
|
+
deviceId: number().int(),
|
|
47692
|
+
outcome: DeviceRunOutcomeSchema.optional(),
|
|
47693
|
+
atFrom: number().optional(),
|
|
47694
|
+
atTo: number().optional(),
|
|
47695
|
+
limit: number().int().positive().max(200).default(50),
|
|
47696
|
+
cursor: string().optional()
|
|
47697
|
+
}), object({
|
|
47698
|
+
runs: array(DeviceRunSchema).readonly(),
|
|
47699
|
+
nextCursor: string().nullable()
|
|
47700
|
+
})), method(object({
|
|
47701
|
+
deviceId: number().int(),
|
|
47702
|
+
runId: string()
|
|
47703
|
+
}), DeviceRunSchema.nullable()), method(object({ deviceId: number().int() }), DeviceRunSchema.nullable());
|
|
47471
47704
|
var DeviceCaStatusSchema = discriminatedUnion("state", [
|
|
47472
47705
|
object({ state: literal("absent") }),
|
|
47473
47706
|
object({
|
|
@@ -47603,8 +47836,8 @@ method(object({}), object({
|
|
|
47603
47836
|
});
|
|
47604
47837
|
var KeychainKeySourceSchema = object({
|
|
47605
47838
|
kind: _enum([
|
|
47606
|
-
"
|
|
47607
|
-
"previous-
|
|
47839
|
+
"mounted-file",
|
|
47840
|
+
"previous-mounted-file",
|
|
47608
47841
|
"platform",
|
|
47609
47842
|
"default-file"
|
|
47610
47843
|
]),
|
|
@@ -52100,7 +52333,8 @@ var PALETTE_ENTRY_IDS = [
|
|
|
52100
52333
|
"switch",
|
|
52101
52334
|
"siren",
|
|
52102
52335
|
"button",
|
|
52103
|
-
"doorbell"
|
|
52336
|
+
"doorbell",
|
|
52337
|
+
"appliance-cycle"
|
|
52104
52338
|
];
|
|
52105
52339
|
var AUDIO_ENTRY_ID = "audio";
|
|
52106
52340
|
/** Six rungs, quiet → loud. The opaque end is the entry's hex. */
|
|
@@ -52171,7 +52405,8 @@ var CAMSTACK_HEX = {
|
|
|
52171
52405
|
switch: "#7a812a",
|
|
52172
52406
|
siren: "#ac5d7f",
|
|
52173
52407
|
button: "#b276cd",
|
|
52174
|
-
doorbell: "#0087ab"
|
|
52408
|
+
doorbell: "#0087ab",
|
|
52409
|
+
"appliance-cycle": "#2a8c8c"
|
|
52175
52410
|
};
|
|
52176
52411
|
var CAMSTACK_FALLBACK = [
|
|
52177
52412
|
"#b34dc7",
|
|
@@ -52236,7 +52471,8 @@ var HIGH_CONTRAST_HEX = {
|
|
|
52236
52471
|
switch: "#d05259",
|
|
52237
52472
|
siren: "#cd67b3",
|
|
52238
52473
|
button: "#c9746a",
|
|
52239
|
-
doorbell: "#338eeb"
|
|
52474
|
+
doorbell: "#338eeb",
|
|
52475
|
+
"appliance-cycle": "#00848a"
|
|
52240
52476
|
};
|
|
52241
52477
|
var HIGH_CONTRAST_FALLBACK = [
|
|
52242
52478
|
"#817f3b",
|
|
@@ -52301,7 +52537,8 @@ var OKABE_ITO_HEX = {
|
|
|
52301
52537
|
switch: "#ad2c4e",
|
|
52302
52538
|
siren: "#348e08",
|
|
52303
52539
|
button: "#d27303",
|
|
52304
|
-
doorbell: "#55770f"
|
|
52540
|
+
doorbell: "#55770f",
|
|
52541
|
+
"appliance-cycle": "#00737d"
|
|
52305
52542
|
};
|
|
52306
52543
|
var OKABE_ITO_FALLBACK = [
|
|
52307
52544
|
"#9a3784",
|
|
@@ -52510,6 +52747,7 @@ sub("switch", "control", "control", "switch", "Switch");
|
|
|
52510
52747
|
sub("siren", "control", "control", "siren", "Siren");
|
|
52511
52748
|
sub("button", "control", "control", "button", "Button");
|
|
52512
52749
|
sub("doorbell", "control", "control", "doorbell", "Doorbell");
|
|
52750
|
+
sub("appliance-cycle", "control", "control", "switch", "Appliance cycle");
|
|
52513
52751
|
for (const l of AUDIO_MACRO_LABELS) {
|
|
52514
52752
|
const kind = `audio-${l.id}`;
|
|
52515
52753
|
if (entries.has(kind)) continue;
|
|
@@ -61141,7 +61379,8 @@ var CAP_TO_KIND = {
|
|
|
61141
61379
|
cover: "cover",
|
|
61142
61380
|
switch: "switch",
|
|
61143
61381
|
button: "button",
|
|
61144
|
-
doorbell: "doorbell"
|
|
61382
|
+
doorbell: "doorbell",
|
|
61383
|
+
"running-state": "appliance-cycle"
|
|
61145
61384
|
};
|
|
61146
61385
|
function buildDescriptor(capName, kind) {
|
|
61147
61386
|
const t = EVENT_TAXONOMY[kind];
|
|
@@ -64271,10 +64510,7 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
|
|
|
64271
64510
|
}), InstallPackageResultSchema, {
|
|
64272
64511
|
kind: "mutation",
|
|
64273
64512
|
auth: "admin"
|
|
64274
|
-
}), method(object({
|
|
64275
|
-
kind: "mutation",
|
|
64276
|
-
auth: "admin"
|
|
64277
|
-
}), method(_void(), boolean()), method(_void(), array(unknown()).readonly()), method(object({
|
|
64513
|
+
}), method(object({
|
|
64278
64514
|
packageName: string().min(1),
|
|
64279
64515
|
nodeId: string().optional(),
|
|
64280
64516
|
everywhere: boolean().optional()
|
|
@@ -65535,19 +65771,19 @@ var RemoteSetConfigResultSchema = discriminatedUnion("status", [object({ status:
|
|
|
65535
65771
|
reason: _enum(["unknown-template", "not-a-media-player"]),
|
|
65536
65772
|
detail: string()
|
|
65537
65773
|
})]);
|
|
65538
|
-
var deviceInput
|
|
65774
|
+
var deviceInput = object({ deviceId: number().int().nonnegative() });
|
|
65539
65775
|
DeviceType.MediaPlayer;
|
|
65540
|
-
method(deviceInput
|
|
65776
|
+
method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
|
|
65541
65777
|
kind: "mutation",
|
|
65542
65778
|
auth: "admin"
|
|
65543
|
-
}), method(deviceInput
|
|
65779
|
+
}), method(deviceInput, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput.extend({
|
|
65544
65780
|
role: RemoteRoleSchema,
|
|
65545
65781
|
/** Sends in a row (volume steps). */
|
|
65546
65782
|
repeat: number().int().min(1).max(50).default(1)
|
|
65547
65783
|
}), RemoteSendResultSchema, {
|
|
65548
65784
|
kind: "mutation",
|
|
65549
65785
|
auth: "protected"
|
|
65550
|
-
}), method(deviceInput
|
|
65786
|
+
}), method(deviceInput.extend({
|
|
65551
65787
|
command: RemoteMediaCommandSchema,
|
|
65552
65788
|
muted: boolean().optional(),
|
|
65553
65789
|
source: string().optional(),
|
|
@@ -66415,6 +66651,60 @@ onColorChanged: { data: object({
|
|
|
66415
66651
|
*/
|
|
66416
66652
|
history: "none"
|
|
66417
66653
|
};
|
|
66654
|
+
/**
|
|
66655
|
+
* `connection-test` — pre-creation validation of an integration's settings,
|
|
66656
|
+
* system-scoped collection (one provider per integration addon).
|
|
66657
|
+
*
|
|
66658
|
+
* ── Why this cap exists ─────────────────────────────────────────────────────
|
|
66659
|
+
*
|
|
66660
|
+
* Before it, `integrations.testConnection` had exactly two behaviours: a broker
|
|
66661
|
+
* branch (`settings.brokerId` → `broker.testConnection`) and a DEFAULT branch
|
|
66662
|
+
* that probed `settings.main_stream_url ?? settings.url` with ffprobe. Every
|
|
66663
|
+
* account-based integration — Dreo, Dreame, Tuya, Petkit, Wyze — fell into that
|
|
66664
|
+
* default and was told `{"success":false,"error":"No stream URL provided"}`.
|
|
66665
|
+
* That answer neither validated nor refused anything, so:
|
|
66666
|
+
*
|
|
66667
|
+
* - the Test button had NEVER worked for an account integration, and
|
|
66668
|
+
* - `integrations.create` had nothing to gate on, so an integration with a
|
|
66669
|
+
* wrong password was created happily and failed later, silently, at
|
|
66670
|
+
* reconcile time.
|
|
66671
|
+
*
|
|
66672
|
+
* The structural gap was that at CREATE time the integration does not exist
|
|
66673
|
+
* yet: there is no `integrationId`, no `brokerId`, no live client — and every
|
|
66674
|
+
* existing provider surface (`device-provider`, `device-adoption`) is keyed by
|
|
66675
|
+
* one of those. There was no way to ask "are these settings valid?" before the
|
|
66676
|
+
* row existed.
|
|
66677
|
+
*
|
|
66678
|
+
* ── The contract ────────────────────────────────────────────────────────────
|
|
66679
|
+
*
|
|
66680
|
+
* The provider knows how to validate its OWN settings. The framework does not
|
|
66681
|
+
* guess from the shape of the settings — a stream-URL probe is one provider's
|
|
66682
|
+
* implementation of this cap, not the universal fallback. A provider that does
|
|
66683
|
+
* not register this cap is a KNOWN "cannot validate", never a silent pass.
|
|
66684
|
+
*
|
|
66685
|
+
* `testSettings` takes the candidate settings blob exactly as the wizard's
|
|
66686
|
+
* config step collected it (the same blob that would be handed to
|
|
66687
|
+
* `integrations.create`), and MUST NOT persist anything, mint an integration,
|
|
66688
|
+
* or mutate live state. It opens a throwaway session, asks, and closes it.
|
|
66689
|
+
*
|
|
66690
|
+
* ── The three outcomes are NOT interchangeable ──────────────────────────────
|
|
66691
|
+
*
|
|
66692
|
+
* This is the whole point of the discriminated union: a `null` from a timeout
|
|
66693
|
+
* must never look like a `null` from a refusal.
|
|
66694
|
+
*
|
|
66695
|
+
* - `validated` — the remote ACCEPTED these credentials. Observed.
|
|
66696
|
+
* - `rejected` — the remote REFUSED these credentials. Observed.
|
|
66697
|
+
* This is the ONLY outcome that blocks creation.
|
|
66698
|
+
* - `inconclusive` — the check could not complete (DNS, timeout, 5xx, an
|
|
66699
|
+
* unexpected shape). NOTHING was observed about the
|
|
66700
|
+
* credentials. Never rendered, or counted, as a failure.
|
|
66701
|
+
*
|
|
66702
|
+
* A provider that cannot tell a refusal from a transport fault must return
|
|
66703
|
+
* `inconclusive`. Guessing `rejected` would block creation on a flaky network;
|
|
66704
|
+
* guessing `validated` would wave a wrong password through.
|
|
66705
|
+
*/
|
|
66706
|
+
/** Ceiling on how long a pre-creation probe may hold the wizard. */
|
|
66707
|
+
var CONNECTION_TEST_TIMEOUT_MS = 2e4;
|
|
66418
66708
|
var ConnectionTestOutcomeSchema = discriminatedUnion("outcome", [
|
|
66419
66709
|
object({
|
|
66420
66710
|
outcome: literal("validated"),
|
|
@@ -69867,117 +70157,6 @@ var lockControlCapability = {
|
|
|
69867
70157
|
* whether persisting is worth a SQLite commit. */
|
|
69868
70158
|
volatileStateFields: ["lastChangedAt"]
|
|
69869
70159
|
};
|
|
69870
|
-
var PowerSourceRefusalReasonSchema = _enum([
|
|
69871
|
-
"no-power-source",
|
|
69872
|
-
"switch-missing",
|
|
69873
|
-
"switch-offline",
|
|
69874
|
-
"switch-unknown",
|
|
69875
|
-
"restart-in-progress",
|
|
69876
|
-
"switch-call-failed"
|
|
69877
|
-
]);
|
|
69878
|
-
var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
|
|
69879
|
-
accepted: literal(false),
|
|
69880
|
-
reason: PowerSourceRefusalReasonSchema,
|
|
69881
|
-
/** One line for the operator, naming the switch. */
|
|
69882
|
-
message: string()
|
|
69883
|
-
})]);
|
|
69884
|
-
var PowerSourceInfoSchema = object({
|
|
69885
|
-
/** False when the device has no power source linked; the rest is then empty. */
|
|
69886
|
-
linked: boolean(),
|
|
69887
|
-
switchDeviceId: number().nullable(),
|
|
69888
|
-
switchName: string().nullable(),
|
|
69889
|
-
/** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
|
|
69890
|
-
switchOn: boolean().nullable(),
|
|
69891
|
-
switchOnline: boolean().nullable(),
|
|
69892
|
-
restartInProgress: boolean(),
|
|
69893
|
-
offSeconds: number().int()
|
|
69894
|
-
});
|
|
69895
|
-
/** The composed slice: the switch state the link mirrors, plus the stored off time. */
|
|
69896
|
-
var PowerSourceStateSchema = object({
|
|
69897
|
-
/** Null while the switch has not reported: unknown is never `false` (D393). */
|
|
69898
|
-
switchOn: boolean().nullable(),
|
|
69899
|
-
offSeconds: number().int(),
|
|
69900
|
-
lastFetchedAt: number()
|
|
69901
|
-
});
|
|
69902
|
-
var POWER_SOURCE_DEVICE_TYPES = [
|
|
69903
|
-
DeviceType.Gateway,
|
|
69904
|
-
DeviceType.Hub,
|
|
69905
|
-
DeviceType.Camera,
|
|
69906
|
-
DeviceType.Sensor,
|
|
69907
|
-
DeviceType.MediaPlayer,
|
|
69908
|
-
DeviceType.Vacuum,
|
|
69909
|
-
DeviceType.Generic,
|
|
69910
|
-
DeviceType.Washer,
|
|
69911
|
-
DeviceType.Dryer,
|
|
69912
|
-
DeviceType.Dishwasher,
|
|
69913
|
-
DeviceType.PetFountain,
|
|
69914
|
-
DeviceType.Fan,
|
|
69915
|
-
DeviceType.Humidifier,
|
|
69916
|
-
DeviceType.Climate,
|
|
69917
|
-
DeviceType.WaterHeater
|
|
69918
|
-
];
|
|
69919
|
-
var powerSourceCustomize = {
|
|
69920
|
-
version: 1,
|
|
69921
|
-
targets: [{
|
|
69922
|
-
id: "switch",
|
|
69923
|
-
cardinality: "scalar",
|
|
69924
|
-
vocabulary: {
|
|
69925
|
-
cap: "power-source",
|
|
69926
|
-
stateField: "switchOn"
|
|
69927
|
-
},
|
|
69928
|
-
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
69929
|
-
apply: "add",
|
|
69930
|
-
path: "switchOn",
|
|
69931
|
-
sources: [{
|
|
69932
|
-
kind: "switch",
|
|
69933
|
-
transforms: [{ kind: "identity" }]
|
|
69934
|
-
}],
|
|
69935
|
-
companions: [{
|
|
69936
|
-
kind: "constant",
|
|
69937
|
-
path: "offSeconds",
|
|
69938
|
-
value: 10,
|
|
69939
|
-
when: "add"
|
|
69940
|
-
}, {
|
|
69941
|
-
kind: "clock",
|
|
69942
|
-
path: "lastFetchedAt",
|
|
69943
|
-
when: "always"
|
|
69944
|
-
}],
|
|
69945
|
-
sourceReport: { status: "assignment-status" }
|
|
69946
|
-
}]
|
|
69947
|
-
};
|
|
69948
|
-
var deviceInput = object({ deviceId: number().int().nonnegative() });
|
|
69949
|
-
var powerSourceCapability = {
|
|
69950
|
-
name: "power-source",
|
|
69951
|
-
scope: "device",
|
|
69952
|
-
mode: "singleton",
|
|
69953
|
-
deviceTypes: POWER_SOURCE_DEVICE_TYPES,
|
|
69954
|
-
methods: {
|
|
69955
|
-
getStatus: systemMethod(deviceInput, PowerSourceInfoSchema, { auth: "admin" }),
|
|
69956
|
-
powerOn: systemMethod(deviceInput, PowerSourceActionResultSchema, {
|
|
69957
|
-
kind: "mutation",
|
|
69958
|
-
auth: "admin"
|
|
69959
|
-
}),
|
|
69960
|
-
powerOff: systemMethod(deviceInput, PowerSourceActionResultSchema, {
|
|
69961
|
-
kind: "mutation",
|
|
69962
|
-
auth: "admin"
|
|
69963
|
-
}),
|
|
69964
|
-
forceRestart: systemMethod(deviceInput, PowerSourceActionResultSchema, {
|
|
69965
|
-
kind: "mutation",
|
|
69966
|
-
auth: "admin"
|
|
69967
|
-
})
|
|
69968
|
-
},
|
|
69969
|
-
runtimeState: PowerSourceStateSchema,
|
|
69970
|
-
customize: powerSourceCustomize,
|
|
69971
|
-
/**
|
|
69972
|
-
* Runtime-state durability: **session** — mirrored from the linked switch on
|
|
69973
|
-
* its next report; a restored value would outlive the reading it names.
|
|
69974
|
-
*
|
|
69975
|
-
* See `RuntimeStateDurability`. Enforced by
|
|
69976
|
-
* `scripts/check-runtime-state-durability.ts`.
|
|
69977
|
-
*/
|
|
69978
|
-
durability: "session",
|
|
69979
|
-
history: "none"
|
|
69980
|
-
};
|
|
69981
70160
|
/**
|
|
69982
70161
|
* A monotonic total between resets (D944). Energy uses `power-meter.energy`
|
|
69983
70162
|
* so dashboards keep one series; every other instantaneous reading lands here.
|
|
@@ -73519,6 +73698,10 @@ var RUNNING_DEVICE_TYPES = Object.values(DeviceType);
|
|
|
73519
73698
|
/** Generic Customize target for the running boolean (D936). */
|
|
73520
73699
|
var runningCustomize = {
|
|
73521
73700
|
version: 1,
|
|
73701
|
+
overridable: [{
|
|
73702
|
+
path: "running",
|
|
73703
|
+
by: "running"
|
|
73704
|
+
}],
|
|
73522
73705
|
targets: [{
|
|
73523
73706
|
id: "running",
|
|
73524
73707
|
cardinality: "scalar",
|
|
@@ -73576,7 +73759,7 @@ var runningCustomize = {
|
|
|
73576
73759
|
kind: "constant",
|
|
73577
73760
|
path: "phase",
|
|
73578
73761
|
value: null,
|
|
73579
|
-
when: "
|
|
73762
|
+
when: "add"
|
|
73580
73763
|
},
|
|
73581
73764
|
{
|
|
73582
73765
|
kind: "clock",
|
|
@@ -76886,7 +77069,7 @@ var ExternalIdentityKeySchema = object({
|
|
|
76886
77069
|
provider: string().min(1),
|
|
76887
77070
|
subject: string().min(1)
|
|
76888
77071
|
});
|
|
76889
|
-
method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserInputSchema, UserSummarySchema, {
|
|
77072
|
+
method(_void(), array(UserSummarySchema), { auth: "admin" }), method(_void(), object({ defaultPasswordInUse: boolean() }), { auth: "admin" }), method(CreateUserInputSchema, UserSummarySchema, {
|
|
76890
77073
|
kind: "mutation",
|
|
76891
77074
|
auth: "admin",
|
|
76892
77075
|
access: "create"
|
|
@@ -80225,13 +80408,6 @@ Object.freeze({
|
|
|
80225
80408
|
access: "view",
|
|
80226
80409
|
role: "admin"
|
|
80227
80410
|
},
|
|
80228
|
-
"addons.installFromWorkspace": {
|
|
80229
|
-
capName: "addons",
|
|
80230
|
-
capScope: "system",
|
|
80231
|
-
addonId: null,
|
|
80232
|
-
access: "create",
|
|
80233
|
-
role: "admin"
|
|
80234
|
-
},
|
|
80235
80411
|
"addons.installPackage": {
|
|
80236
80412
|
capName: "addons",
|
|
80237
80413
|
capScope: "system",
|
|
@@ -80239,13 +80415,6 @@ Object.freeze({
|
|
|
80239
80415
|
access: "create",
|
|
80240
80416
|
role: "admin"
|
|
80241
80417
|
},
|
|
80242
|
-
"addons.isWorkspaceAvailable": {
|
|
80243
|
-
capName: "addons",
|
|
80244
|
-
capScope: "system",
|
|
80245
|
-
addonId: null,
|
|
80246
|
-
access: "view",
|
|
80247
|
-
role: "admin"
|
|
80248
|
-
},
|
|
80249
80418
|
"addons.list": {
|
|
80250
80419
|
capName: "addons",
|
|
80251
80420
|
capScope: "system",
|
|
@@ -80295,13 +80464,6 @@ Object.freeze({
|
|
|
80295
80464
|
access: "view",
|
|
80296
80465
|
role: "admin"
|
|
80297
80466
|
},
|
|
80298
|
-
"addons.listWorkspacePackages": {
|
|
80299
|
-
capName: "addons",
|
|
80300
|
-
capScope: "system",
|
|
80301
|
-
addonId: null,
|
|
80302
|
-
access: "view",
|
|
80303
|
-
role: "admin"
|
|
80304
|
-
},
|
|
80305
80467
|
"addons.onAddonLogs": {
|
|
80306
80468
|
capName: "addons",
|
|
80307
80469
|
capScope: "system",
|
|
@@ -82969,6 +83131,27 @@ Object.freeze({
|
|
|
82969
83131
|
access: "create",
|
|
82970
83132
|
role: "admin"
|
|
82971
83133
|
},
|
|
83134
|
+
"deviceRuns.getCurrentRun": {
|
|
83135
|
+
capName: "device-runs",
|
|
83136
|
+
capScope: "device",
|
|
83137
|
+
addonId: null,
|
|
83138
|
+
access: "view",
|
|
83139
|
+
role: "viewer"
|
|
83140
|
+
},
|
|
83141
|
+
"deviceRuns.getRun": {
|
|
83142
|
+
capName: "device-runs",
|
|
83143
|
+
capScope: "device",
|
|
83144
|
+
addonId: null,
|
|
83145
|
+
access: "view",
|
|
83146
|
+
role: "viewer"
|
|
83147
|
+
},
|
|
83148
|
+
"deviceRuns.listRuns": {
|
|
83149
|
+
capName: "device-runs",
|
|
83150
|
+
capScope: "device",
|
|
83151
|
+
addonId: null,
|
|
83152
|
+
access: "view",
|
|
83153
|
+
role: "viewer"
|
|
83154
|
+
},
|
|
82972
83155
|
"deviceState.claimFields": {
|
|
82973
83156
|
capName: "device-state",
|
|
82974
83157
|
capScope: "system",
|
|
@@ -89682,6 +89865,13 @@ Object.freeze({
|
|
|
89682
89865
|
access: "view",
|
|
89683
89866
|
role: "admin"
|
|
89684
89867
|
},
|
|
89868
|
+
"userManagement.getAdminBootstrapStatus": {
|
|
89869
|
+
capName: "user-management",
|
|
89870
|
+
capScope: "system",
|
|
89871
|
+
addonId: null,
|
|
89872
|
+
access: "view",
|
|
89873
|
+
role: "admin"
|
|
89874
|
+
},
|
|
89685
89875
|
"userManagement.getTotpStatus": {
|
|
89686
89876
|
capName: "user-management",
|
|
89687
89877
|
capScope: "system",
|
|
@@ -91167,6 +91357,21 @@ Object.freeze({
|
|
|
91167
91357
|
form: "single",
|
|
91168
91358
|
optional: false
|
|
91169
91359
|
}],
|
|
91360
|
+
"deviceRuns.getCurrentRun": [{
|
|
91361
|
+
name: "deviceId",
|
|
91362
|
+
form: "single",
|
|
91363
|
+
optional: false
|
|
91364
|
+
}],
|
|
91365
|
+
"deviceRuns.getRun": [{
|
|
91366
|
+
name: "deviceId",
|
|
91367
|
+
form: "single",
|
|
91368
|
+
optional: false
|
|
91369
|
+
}],
|
|
91370
|
+
"deviceRuns.listRuns": [{
|
|
91371
|
+
name: "deviceId",
|
|
91372
|
+
form: "single",
|
|
91373
|
+
optional: false
|
|
91374
|
+
}],
|
|
91170
91375
|
"deviceState.claimFields": [{
|
|
91171
91376
|
name: "deviceId",
|
|
91172
91377
|
form: "single",
|
|
@@ -93649,13 +93854,114 @@ NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
|
|
|
93649
93854
|
*
|
|
93650
93855
|
* Pure, RN-safe: the viewer imports the support rule from here.
|
|
93651
93856
|
*/
|
|
93857
|
+
/**
|
|
93858
|
+
* Control caps that still have a raise and a clear an operator names: an
|
|
93859
|
+
* appliance cycle raises when it STARTS and clears when it FINISHES. Kept out
|
|
93860
|
+
* of `SOURCE_CAP_ACTIVE_FIELD` on purpose — that table also makes a device a
|
|
93861
|
+
* camera-sensor / recorder-trigger source, and a dishwasher is neither.
|
|
93862
|
+
*/
|
|
93863
|
+
var CYCLE_RAISED_FIELD_BY_CAP = { "running-state": "running" };
|
|
93652
93864
|
/** Event kind → the boolean field of its cap slice that means "raised". */
|
|
93653
93865
|
var NC_SENSOR_RAISED_FIELD_BY_KIND = Object.freeze(Object.fromEntries(Object.entries(EVENT_KIND_BY_CAP).flatMap(([capName, descriptor]) => {
|
|
93866
|
+
const cycleField = CYCLE_RAISED_FIELD_BY_CAP[capName];
|
|
93867
|
+
if (cycleField !== void 0) return [[descriptor.kind, cycleField]];
|
|
93654
93868
|
const field = SOURCE_CAP_ACTIVE_FIELD[capName];
|
|
93655
93869
|
if (field === void 0 || descriptor.category !== "sensor") return [];
|
|
93656
93870
|
return [[descriptor.kind, field]];
|
|
93657
93871
|
})));
|
|
93658
93872
|
Object.freeze(Object.keys(NC_SENSOR_RAISED_FIELD_BY_KIND));
|
|
93873
|
+
Object.freeze({
|
|
93874
|
+
contact: {
|
|
93875
|
+
starts: {
|
|
93876
|
+
en: "opened",
|
|
93877
|
+
it: "si apre"
|
|
93878
|
+
},
|
|
93879
|
+
finishes: {
|
|
93880
|
+
en: "closed",
|
|
93881
|
+
it: "si chiude"
|
|
93882
|
+
}
|
|
93883
|
+
},
|
|
93884
|
+
"motion-sensor": {
|
|
93885
|
+
starts: {
|
|
93886
|
+
en: "motion detected",
|
|
93887
|
+
it: "movimento rilevato"
|
|
93888
|
+
},
|
|
93889
|
+
finishes: {
|
|
93890
|
+
en: "motion stopped",
|
|
93891
|
+
it: "movimento terminato"
|
|
93892
|
+
}
|
|
93893
|
+
},
|
|
93894
|
+
smoke: {
|
|
93895
|
+
starts: {
|
|
93896
|
+
en: "smoke detected",
|
|
93897
|
+
it: "fumo rilevato"
|
|
93898
|
+
},
|
|
93899
|
+
finishes: {
|
|
93900
|
+
en: "smoke gone",
|
|
93901
|
+
it: "fumo scomparso"
|
|
93902
|
+
}
|
|
93903
|
+
},
|
|
93904
|
+
flood: {
|
|
93905
|
+
starts: {
|
|
93906
|
+
en: "water detected",
|
|
93907
|
+
it: "acqua rilevata"
|
|
93908
|
+
},
|
|
93909
|
+
finishes: {
|
|
93910
|
+
en: "dry again",
|
|
93911
|
+
it: "di nuovo asciutto"
|
|
93912
|
+
}
|
|
93913
|
+
},
|
|
93914
|
+
gas: {
|
|
93915
|
+
starts: {
|
|
93916
|
+
en: "gas detected",
|
|
93917
|
+
it: "gas rilevato"
|
|
93918
|
+
},
|
|
93919
|
+
finishes: {
|
|
93920
|
+
en: "gas gone",
|
|
93921
|
+
it: "gas scomparso"
|
|
93922
|
+
}
|
|
93923
|
+
},
|
|
93924
|
+
"carbon-monoxide": {
|
|
93925
|
+
starts: {
|
|
93926
|
+
en: "carbon monoxide detected",
|
|
93927
|
+
it: "monossido rilevato"
|
|
93928
|
+
},
|
|
93929
|
+
finishes: {
|
|
93930
|
+
en: "carbon monoxide gone",
|
|
93931
|
+
it: "monossido scomparso"
|
|
93932
|
+
}
|
|
93933
|
+
},
|
|
93934
|
+
vibration: {
|
|
93935
|
+
starts: {
|
|
93936
|
+
en: "vibration detected",
|
|
93937
|
+
it: "vibrazione rilevata"
|
|
93938
|
+
},
|
|
93939
|
+
finishes: {
|
|
93940
|
+
en: "vibration stopped",
|
|
93941
|
+
it: "vibrazione terminata"
|
|
93942
|
+
}
|
|
93943
|
+
},
|
|
93944
|
+
tamper: {
|
|
93945
|
+
starts: {
|
|
93946
|
+
en: "tampered with",
|
|
93947
|
+
it: "manomesso"
|
|
93948
|
+
},
|
|
93949
|
+
finishes: {
|
|
93950
|
+
en: "tamper cleared",
|
|
93951
|
+
it: "manomissione rientrata"
|
|
93952
|
+
}
|
|
93953
|
+
},
|
|
93954
|
+
"appliance-cycle": {
|
|
93955
|
+
starts: {
|
|
93956
|
+
en: "the cycle started",
|
|
93957
|
+
it: "il ciclo parte"
|
|
93958
|
+
},
|
|
93959
|
+
finishes: {
|
|
93960
|
+
en: "the cycle finished",
|
|
93961
|
+
it: "il ciclo è terminato"
|
|
93962
|
+
}
|
|
93963
|
+
}
|
|
93964
|
+
});
|
|
93659
93965
|
object({
|
|
93660
93966
|
phase: _enum(["raised", "resolved"]),
|
|
93661
93967
|
episodeId: string().min(1),
|
|
@@ -93862,6 +94168,10 @@ var STATE_SUMMARY_VARS = [
|
|
|
93862
94168
|
[...RULE_TEXT],
|
|
93863
94169
|
[...RULE_TEXT],
|
|
93864
94170
|
[...RULE_TEXT],
|
|
94171
|
+
[...RULE_TEXT],
|
|
94172
|
+
[...RULE_TEXT],
|
|
94173
|
+
[...RULE_TEXT],
|
|
94174
|
+
[...RULE_TEXT],
|
|
93865
94175
|
[...RULE_BODY_ONLY],
|
|
93866
94176
|
[...RULE_TEXT],
|
|
93867
94177
|
([...RULE_TEXT], [...DEVICE_EVENTS]),
|
|
@@ -93879,6 +94189,7 @@ var STATE_SUMMARY_VARS = [
|
|
|
93879
94189
|
[...RULE_TEXT],
|
|
93880
94190
|
[...RULE_TEXT],
|
|
93881
94191
|
[...RULE_TEXT],
|
|
94192
|
+
[...RULE_TEXT],
|
|
93882
94193
|
([...RULE_TEXT], [...DEVICE_ATTENTION, ...ALERT_EPISODE]),
|
|
93883
94194
|
[...RULE_TEXT],
|
|
93884
94195
|
[...RULE_TEXT],
|
|
@@ -94123,6 +94434,7 @@ new Set([
|
|
|
94123
94434
|
"siren",
|
|
94124
94435
|
"button",
|
|
94125
94436
|
"doorbell",
|
|
94437
|
+
"appliance-cycle",
|
|
94126
94438
|
"audio-speech",
|
|
94127
94439
|
"audio-scream",
|
|
94128
94440
|
"audio-crying",
|
|
@@ -103432,8 +103744,90 @@ var VeluxClientRegistry = class {
|
|
|
103432
103744
|
};
|
|
103433
103745
|
//#endregion
|
|
103434
103746
|
//#region src/klf-probe.ts
|
|
103435
|
-
/**
|
|
103436
|
-
|
|
103747
|
+
/**
|
|
103748
|
+
* Defaults add up, worst case, to 12 + 3 + 1.5 + 1.5 = 18 s, which is the
|
|
103749
|
+
* wizard's `CONNECTION_TEST_TIMEOUT_MS` ceiling (20 s) minus the 2 s margin of
|
|
103750
|
+
* `DEFAULT_TEST_DEADLINE_MS`, so a stuck gateway is named here instead of the
|
|
103751
|
+
* wizard giving up unnamed. The handshake gets 12 s because a real KLF-200 needs
|
|
103752
|
+
* about 4.2 s for ONE handshake (TLSv1.2, DHE-RSA-AES256-SHA256 on a weak CPU).
|
|
103753
|
+
*
|
|
103754
|
+
* A probe opens exactly ONE connection: the gateway frees a closed slot very
|
|
103755
|
+
* slowly (measured 2026-11-01: a bare TCP connect and close before the TLS connect
|
|
103756
|
+
* left the real session without a handshake for 20 s), so no pre-check connects first.
|
|
103757
|
+
*/
|
|
103758
|
+
var DEFAULT_KLF_PROBE_TIMEOUTS_MS = {
|
|
103759
|
+
tls: 12e3,
|
|
103760
|
+
password: 3e3,
|
|
103761
|
+
version: 1500,
|
|
103762
|
+
logout: 1500
|
|
103763
|
+
};
|
|
103764
|
+
var PHASE_TEXT = {
|
|
103765
|
+
tls: "did not accept a connection or complete the TLS handshake on port 51200",
|
|
103766
|
+
password: "did not answer the password",
|
|
103767
|
+
version: "did not answer the version request",
|
|
103768
|
+
logout: "did not close the session"
|
|
103769
|
+
};
|
|
103770
|
+
/** A probe step ran out of time. Names the step, so a bound is never a silent hang. */
|
|
103771
|
+
var KlfProbeTimeoutError = class extends Error {
|
|
103772
|
+
phase;
|
|
103773
|
+
host;
|
|
103774
|
+
limitMs;
|
|
103775
|
+
constructor(phase, host, limitMs) {
|
|
103776
|
+
super(`The gateway ${host} ${PHASE_TEXT[phase]} within ${Math.round(limitMs / 100) / 10}s (timed out)`);
|
|
103777
|
+
this.phase = phase;
|
|
103778
|
+
this.host = host;
|
|
103779
|
+
this.limitMs = limitMs;
|
|
103780
|
+
this.name = "KlfProbeTimeoutError";
|
|
103781
|
+
}
|
|
103782
|
+
};
|
|
103783
|
+
async function withDeadline(work, ms, onTimeout) {
|
|
103784
|
+
let timer;
|
|
103785
|
+
try {
|
|
103786
|
+
return await Promise.race([work, new Promise((_resolve, reject) => {
|
|
103787
|
+
timer = setTimeout(() => reject(onTimeout()), ms);
|
|
103788
|
+
})]);
|
|
103789
|
+
} finally {
|
|
103790
|
+
clearTimeout(timer);
|
|
103791
|
+
}
|
|
103792
|
+
}
|
|
103793
|
+
/** How often the login watcher checks whether the handshake has finished. */
|
|
103794
|
+
var PHASE_POLL_MS = 20;
|
|
103795
|
+
/**
|
|
103796
|
+
* The library's `loginAsync` runs the TLS handshake and the password answer as one
|
|
103797
|
+
* call. `KLF200SocketProtocol` appears when the handshake is done and the password
|
|
103798
|
+
* is on its way, so watching it splits the call into two bounded, named steps.
|
|
103799
|
+
*/
|
|
103800
|
+
async function loginInPhases(connection, host, password, limits) {
|
|
103801
|
+
let handshakeDone = false;
|
|
103802
|
+
let passwordTimer;
|
|
103803
|
+
let tlsTimer;
|
|
103804
|
+
let poll;
|
|
103805
|
+
const tlsError = () => new KlfProbeTimeoutError("tls", host, limits.tls);
|
|
103806
|
+
const passwordError = () => new KlfProbeTimeoutError("password", host, limits.password);
|
|
103807
|
+
const bounds = new Promise((_resolve, reject) => {
|
|
103808
|
+
tlsTimer = setTimeout(() => reject(tlsError()), limits.tls);
|
|
103809
|
+
poll = setInterval(() => {
|
|
103810
|
+
if (handshakeDone || connection.KLF200SocketProtocol === void 0) return;
|
|
103811
|
+
handshakeDone = true;
|
|
103812
|
+
clearTimeout(tlsTimer);
|
|
103813
|
+
clearInterval(poll);
|
|
103814
|
+
passwordTimer = setTimeout(() => reject(passwordError()), limits.password);
|
|
103815
|
+
}, PHASE_POLL_MS);
|
|
103816
|
+
});
|
|
103817
|
+
const libraryS = (limits.tls + limits.password) / 1e3;
|
|
103818
|
+
try {
|
|
103819
|
+
await Promise.race([connection.loginAsync(password, libraryS), bounds]);
|
|
103820
|
+
} catch (err) {
|
|
103821
|
+
if (err instanceof KlfProbeTimeoutError) throw err;
|
|
103822
|
+
if (/^timeout$/i.test(errMsg(err)) || err instanceof Error && err.name === "TimeoutError") throw handshakeDone ? passwordError() : tlsError();
|
|
103823
|
+
throw err;
|
|
103824
|
+
} finally {
|
|
103825
|
+
clearTimeout(tlsTimer);
|
|
103826
|
+
clearTimeout(passwordTimer);
|
|
103827
|
+
clearInterval(poll);
|
|
103828
|
+
bounds.catch(() => void 0);
|
|
103829
|
+
}
|
|
103830
|
+
}
|
|
103437
103831
|
/**
|
|
103438
103832
|
* Opens ONE session, authenticates, reads the firmware version and closes the
|
|
103439
103833
|
* session again, whatever happens. The gateway accepts two sessions in total, so
|
|
@@ -103441,28 +103835,44 @@ var PROBE_LOGIN_TIMEOUT_S = 15;
|
|
|
103441
103835
|
* list. A refused password rejects with `VeluxLoginRefusedError`.
|
|
103442
103836
|
*/
|
|
103443
103837
|
async function probeKlfGateway(options) {
|
|
103838
|
+
const limits = {
|
|
103839
|
+
...DEFAULT_KLF_PROBE_TIMEOUTS_MS,
|
|
103840
|
+
...options.timeoutsMs
|
|
103841
|
+
};
|
|
103444
103842
|
const connection = options.tls === void 0 ? new Connection(options.host) : new Connection(options.host, options.tls);
|
|
103445
103843
|
try {
|
|
103446
103844
|
try {
|
|
103447
|
-
await connection.
|
|
103845
|
+
await loginInPhases(connection, options.host, options.password, limits);
|
|
103448
103846
|
} catch (err) {
|
|
103847
|
+
if (err instanceof KlfProbeTimeoutError) throw err;
|
|
103848
|
+
if (err instanceof Error && typeof Reflect.get(err, "code") === "string") throw err;
|
|
103449
103849
|
throw loginFailure(err);
|
|
103450
103850
|
}
|
|
103451
103851
|
try {
|
|
103452
|
-
const { SoftwareVersion } = await new Gateway(connection).getVersionAsync();
|
|
103852
|
+
const { SoftwareVersion } = await withDeadline(new Gateway(connection).getVersionAsync(), limits.version, () => new KlfProbeTimeoutError("version", options.host, limits.version));
|
|
103453
103853
|
return { firmware: SoftwareVersion.toString() };
|
|
103454
103854
|
} catch {
|
|
103455
103855
|
return { firmware: null };
|
|
103456
103856
|
}
|
|
103457
103857
|
} finally {
|
|
103458
103858
|
try {
|
|
103459
|
-
await connection.logoutAsync();
|
|
103859
|
+
await withDeadline(connection.logoutAsync(limits.logout / 1e3), limits.logout + 500, () => new KlfProbeTimeoutError("logout", options.host, limits.logout));
|
|
103460
103860
|
} catch (err) {
|
|
103461
103861
|
errMsg(err);
|
|
103862
|
+
await connection[Symbol.asyncDispose]().catch(() => void 0);
|
|
103462
103863
|
}
|
|
103463
103864
|
}
|
|
103464
103865
|
}
|
|
103465
103866
|
var TEST_LABEL = "Logs in to the gateway with this password, then closes the session";
|
|
103867
|
+
/** Under `CONNECTION_TEST_TIMEOUT_MS`, so the reason reaches the operator first. */
|
|
103868
|
+
var DEFAULT_TEST_DEADLINE_MS = CONNECTION_TEST_TIMEOUT_MS - 2e3;
|
|
103869
|
+
/** The whole test outlived its ceiling; no step of the probe answered in time. */
|
|
103870
|
+
var KlfTestDeadlineError = class extends Error {
|
|
103871
|
+
constructor(host, limitMs) {
|
|
103872
|
+
super(`The gateway ${host} did not answer within ${Math.round(limitMs / 1e3)}s (timed out)`);
|
|
103873
|
+
this.name = "KlfTestDeadlineError";
|
|
103874
|
+
}
|
|
103875
|
+
};
|
|
103466
103876
|
var UNREACHABLE_CODES = new Set([
|
|
103467
103877
|
"ECONNREFUSED",
|
|
103468
103878
|
"EHOSTUNREACH",
|
|
@@ -103484,6 +103894,10 @@ function classifyProbeFailure(err) {
|
|
|
103484
103894
|
kind: "wrong-password",
|
|
103485
103895
|
text: err.message
|
|
103486
103896
|
};
|
|
103897
|
+
if (err instanceof KlfProbeTimeoutError || err instanceof KlfTestDeadlineError) return {
|
|
103898
|
+
kind: "unreachable",
|
|
103899
|
+
text: err.message
|
|
103900
|
+
};
|
|
103487
103901
|
const text = typeof err === "string" ? err : errMsg(err);
|
|
103488
103902
|
const code = typeof err === "object" && err !== null ? Reflect.get(err, "code") : void 0;
|
|
103489
103903
|
if (typeof code === "string" && UNREACHABLE_CODES.has(code)) return {
|
|
@@ -103508,7 +103922,7 @@ function classifyProbeFailure(err) {
|
|
|
103508
103922
|
};
|
|
103509
103923
|
}
|
|
103510
103924
|
function buildVeluxConnectionTestProvider(deps) {
|
|
103511
|
-
const { logger, probe = probeKlfGateway, liveSessions = () => 0 } = deps;
|
|
103925
|
+
const { logger, probe = probeKlfGateway, liveSessions = () => 0, deadlineMs = DEFAULT_TEST_DEADLINE_MS } = deps;
|
|
103512
103926
|
/** The tail of the test queue per host: a second test waits for the first to log out. */
|
|
103513
103927
|
const queues = /* @__PURE__ */ new Map();
|
|
103514
103928
|
async function run(host, password) {
|
|
@@ -103524,10 +103938,13 @@ function buildVeluxConnectionTestProvider(deps) {
|
|
|
103524
103938
|
}
|
|
103525
103939
|
const startedAt = Date.now();
|
|
103526
103940
|
try {
|
|
103527
|
-
|
|
103941
|
+
let timer;
|
|
103942
|
+
const result = await Promise.race([probe({
|
|
103528
103943
|
host,
|
|
103529
103944
|
password
|
|
103530
|
-
})
|
|
103945
|
+
}), new Promise((_resolve, reject) => {
|
|
103946
|
+
timer = setTimeout(() => reject(new KlfTestDeadlineError(host, deadlineMs)), deadlineMs);
|
|
103947
|
+
})]).finally(() => clearTimeout(timer));
|
|
103531
103948
|
return {
|
|
103532
103949
|
outcome: "validated",
|
|
103533
103950
|
latencyMs: Date.now() - startedAt,
|
|
@@ -103762,7 +104179,12 @@ var VeluxProviderAddon = class extends BaseDeviceProvider {
|
|
|
103762
104179
|
],
|
|
103763
104180
|
customActions: veluxActions,
|
|
103764
104181
|
actionHandlers: { discoverGateway: async () => {
|
|
104182
|
+
const startedAt = Date.now();
|
|
103765
104183
|
const { candidates } = await this.ctx.api.deviceManager.discoverProvider.mutate({ addonId: this.ctx.id });
|
|
104184
|
+
this.ctx.logger.info("Velux gateway scan finished", { meta: {
|
|
104185
|
+
found: candidates.length,
|
|
104186
|
+
ms: Date.now() - startedAt
|
|
104187
|
+
} });
|
|
103766
104188
|
const hosts = candidates.map((candidate) => ({
|
|
103767
104189
|
host: String(candidate.prefilledConfig["host"] ?? ""),
|
|
103768
104190
|
name: candidate.suggestedName,
|