@camstack/addon-provider-enpal 0.1.17 → 0.1.19
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 +13 -0
- package/assets/icon.svg +7 -10
- package/dist/addon.js +959 -422
- package/dist/addon.mjs +959 -422
- 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
|
|
@@ -6047,7 +6056,7 @@ var ZodIssueCode = {
|
|
|
6047
6056
|
var ZodFirstPartyTypeKind;
|
|
6048
6057
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
6049
6058
|
//#endregion
|
|
6050
|
-
//#region ../types/dist/sleep-
|
|
6059
|
+
//#region ../types/dist/sleep-DyIxjZZt.mjs
|
|
6051
6060
|
/**
|
|
6052
6061
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
6053
6062
|
* window to float samples (D455).
|
|
@@ -6730,6 +6739,13 @@ async function runAddonMigrations(host, migrations, logger, options = {}) {
|
|
|
6730
6739
|
else skipped.push(id);
|
|
6731
6740
|
};
|
|
6732
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
|
+
};
|
|
6733
6749
|
for (const step of declaration.steps) {
|
|
6734
6750
|
if (selected !== null && !selected.has(step.id)) continue;
|
|
6735
6751
|
const bound = byId.get(step.id);
|
|
@@ -6741,6 +6757,19 @@ async function runAddonMigrations(host, migrations, logger, options = {}) {
|
|
|
6741
6757
|
try {
|
|
6742
6758
|
const gate = await host.startMigration({ id: step.id });
|
|
6743
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
|
+
}
|
|
6744
6773
|
log.warn("migration not run", {
|
|
6745
6774
|
tags,
|
|
6746
6775
|
meta: {
|
|
@@ -10278,6 +10307,13 @@ var DeviceTransitionSchema = object({
|
|
|
10278
10307
|
actor: TransitionActorSchema,
|
|
10279
10308
|
command: TransitionCommandSchema.optional()
|
|
10280
10309
|
});
|
|
10310
|
+
object({
|
|
10311
|
+
deviceId: number$1().int(),
|
|
10312
|
+
action: string(),
|
|
10313
|
+
outcome: TransitionOutcomeSchema,
|
|
10314
|
+
actor: TransitionActorSchema,
|
|
10315
|
+
at: number$1()
|
|
10316
|
+
});
|
|
10281
10317
|
var READING_QUANTITIES = [
|
|
10282
10318
|
"temperature",
|
|
10283
10319
|
"pressure",
|
|
@@ -12301,7 +12337,10 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
12301
12337
|
"ir-transmitter": {
|
|
12302
12338
|
scope: "device",
|
|
12303
12339
|
noun: t("IR / RF transmitter", "Trasmettitore IR / RF"),
|
|
12304
|
-
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
|
+
}
|
|
12305
12344
|
},
|
|
12306
12345
|
control: {
|
|
12307
12346
|
scope: "device",
|
|
@@ -15222,6 +15261,32 @@ var CAP_VOCABULARY_BY_NAME = {
|
|
|
15222
15261
|
...SYSTEM_CAP_VOCABULARY
|
|
15223
15262
|
};
|
|
15224
15263
|
/**
|
|
15264
|
+
* Vocabulary actions by id (D983). An action step names a vocabulary action
|
|
15265
|
+
* (`cover.open`), never a raw cap method; this is the one lookup that turns the
|
|
15266
|
+
* id into its cap, method and effect — and a persisted legacy `cap` + `method`
|
|
15267
|
+
* pair back into the action id when exactly one action matches.
|
|
15268
|
+
*/
|
|
15269
|
+
function buildIndex() {
|
|
15270
|
+
const byId = /* @__PURE__ */ new Map();
|
|
15271
|
+
for (const [cap, entry] of Object.entries(CAP_VOCABULARY_BY_NAME)) {
|
|
15272
|
+
if (entry.scope !== "device") continue;
|
|
15273
|
+
for (const action of entry.actions ?? []) byId.set(action.id, {
|
|
15274
|
+
cap,
|
|
15275
|
+
action
|
|
15276
|
+
});
|
|
15277
|
+
}
|
|
15278
|
+
return byId;
|
|
15279
|
+
}
|
|
15280
|
+
var index;
|
|
15281
|
+
function actionIndex() {
|
|
15282
|
+
index ??= buildIndex();
|
|
15283
|
+
return index;
|
|
15284
|
+
}
|
|
15285
|
+
/** The vocabulary action with this id, or `undefined`. */
|
|
15286
|
+
function findVocabAction(actionId) {
|
|
15287
|
+
return actionIndex().get(actionId);
|
|
15288
|
+
}
|
|
15289
|
+
/**
|
|
15225
15290
|
* `addon-pages-source` — collection cap exposing per-provider raw page
|
|
15226
15291
|
* declarations. Every addon that contributes a UI page registers a
|
|
15227
15292
|
* provider here. The hub-side singleton aggregator (`addon-pages` cap,
|
|
@@ -44907,21 +44972,23 @@ function convertLegacyWhen(half) {
|
|
|
44907
44972
|
* (instants) and state leaves (durations). It replaces the flat trigger list
|
|
44908
44973
|
* plus the separate condition tree; the firing rule is in that file and in
|
|
44909
44974
|
* ADR "one tree".
|
|
44910
|
-
* - **Actions** — the `wait` and `
|
|
44975
|
+
* - **Actions** — the `wait` and device `command` steps, plus the `{kind:'code'}`
|
|
44911
44976
|
* fragment of §3.2.2. See {@link AutomationActionSchema}.
|
|
44912
44977
|
*
|
|
44913
44978
|
* A recipe stored in the old shape (`triggers` + `conditions`) still LOADS:
|
|
44914
44979
|
* {@link AutomationRecipeSchema} (the parse-anywhere schema) converts it on parse. Nothing writes it.
|
|
44915
44980
|
*/
|
|
44916
44981
|
/**
|
|
44917
|
-
* One action step: `wait`, or a `
|
|
44982
|
+
* One action step: `wait`, or a device `command`.
|
|
44918
44983
|
*
|
|
44919
|
-
* A
|
|
44920
|
-
* `{ $var }` resolved from the run's
|
|
44921
|
-
*
|
|
44922
|
-
*
|
|
44923
|
-
*
|
|
44924
|
-
*
|
|
44984
|
+
* A command step names a VOCABULARY action (`cover.open`) and who it acts on
|
|
44985
|
+
* (D983: `devices`, a `domain`, or a `{ $var }` resolved from the run's
|
|
44986
|
+
* variables). It never names a cap method: the step runner turns the action
|
|
44987
|
+
* id into its cap + method through the vocabulary index when the step runs
|
|
44988
|
+
* (`vocabCall`), the one place that mapping happens. The step runner
|
|
44989
|
+
* (`runSteps`) owns the device-scope check, the stop-at-first-failure and the
|
|
44990
|
+
* variable substitution, once, for every caller — automations, Actions, and
|
|
44991
|
+
* the notification centre, which runs only Actions (planning#473).
|
|
44925
44992
|
*/
|
|
44926
44993
|
/** The longest one `wait` step lasts. It is cut by a restart of the run and does not survive a hub restart. */
|
|
44927
44994
|
var WAIT_MAX_SECONDS = 3600;
|
|
@@ -44930,39 +44997,22 @@ var AutomationWaitStepSchema = object({
|
|
|
44930
44997
|
kind: literal("wait"),
|
|
44931
44998
|
seconds: number$1().min(0).max(WAIT_MAX_SECONDS, WAIT_TOO_LONG)
|
|
44932
44999
|
});
|
|
44933
|
-
/** The legacy `deviceId` of a cap step: still READ, never written (D983). */
|
|
44934
|
-
var LegacyStepDeviceSchema = union([
|
|
44935
|
-
number$1().int(),
|
|
44936
|
-
array(number$1().int()).min(1),
|
|
44937
|
-
object({ $var: string().min(1) })
|
|
44938
|
-
]);
|
|
44939
45000
|
/**
|
|
44940
|
-
* The fields of a
|
|
44941
|
-
*
|
|
44942
|
-
* device); a step is {@link AutomationCapStepSchema}.
|
|
45001
|
+
* The fields of a command step, before the vocabulary check. A step is
|
|
45002
|
+
* {@link AutomationCommandStepSchema}.
|
|
44943
45003
|
*/
|
|
44944
|
-
var
|
|
44945
|
-
kind: literal("
|
|
44946
|
-
/**
|
|
44947
|
-
|
|
44948
|
-
|
|
44949
|
-
|
|
44950
|
-
* cap + method match exactly one vocabulary action.
|
|
44951
|
-
*/
|
|
44952
|
-
actionId: string().min(1).optional(),
|
|
44953
|
-
/** WHO the step acts on. The only shape that is written. */
|
|
44954
|
-
target: ActionTargetSchema.optional(),
|
|
45004
|
+
var AutomationCommandStepBaseSchema = object({
|
|
45005
|
+
kind: literal("command"),
|
|
45006
|
+
/** WHO the step acts on. */
|
|
45007
|
+
target: ActionTargetSchema,
|
|
45008
|
+
/** The vocabulary action this step performs (`cover.open`). */
|
|
45009
|
+
actionId: string().min(1),
|
|
44955
45010
|
/**
|
|
44956
|
-
*
|
|
44957
|
-
*
|
|
44958
|
-
*
|
|
45011
|
+
* The values of the action's OPEN args (`position`, `presetId`); the args
|
|
45012
|
+
* the vocabulary bakes into the action are added when the step runs. Values
|
|
45013
|
+
* may carry `{{vars.x}}` slots, or be exactly `{ $var: 'name' }`, which
|
|
45014
|
+
* SUBSTITUTE and do not evaluate (§3.2.3).
|
|
44959
45015
|
*/
|
|
44960
|
-
deviceId: LegacyStepDeviceSchema.optional(),
|
|
44961
|
-
cap: string().min(1),
|
|
44962
|
-
method: string().min(1),
|
|
44963
|
-
/** Values may carry `{{vars.x}}` slots, or be exactly `{ $var: 'name' }`, which
|
|
44964
|
-
* SUBSTITUTE and do not evaluate (§3.2.3). Anything beyond substitution is
|
|
44965
|
-
* the expression leaf. */
|
|
44966
45016
|
args: record(string(), unknown()).optional(),
|
|
44967
45017
|
/**
|
|
44968
45018
|
* The unit the person picked per arg (display only). `args` always hold the
|
|
@@ -44971,18 +45021,20 @@ var AutomationCapStepBaseSchema = object({
|
|
|
44971
45021
|
*/
|
|
44972
45022
|
argUnits: record(string(), string().min(1)).optional()
|
|
44973
45023
|
});
|
|
45024
|
+
/** The vocabulary check every command step passes: the action exists. */
|
|
45025
|
+
function refineKnownAction(step, ctx) {
|
|
45026
|
+
if (findVocabAction(step.actionId) === void 0) ctx.addIssue({
|
|
45027
|
+
code: "custom",
|
|
45028
|
+
message: `"${step.actionId}" is not a vocabulary action id`,
|
|
45029
|
+
path: ["actionId"]
|
|
45030
|
+
});
|
|
45031
|
+
}
|
|
44974
45032
|
/**
|
|
44975
|
-
* The steps an internal Action may contain:
|
|
45033
|
+
* The steps an internal Action may contain: command and wait, and nothing that can
|
|
44976
45034
|
* call back into an Action — so an Action can never reach itself (no cycles).
|
|
44977
45035
|
* This is the LEAF shape: no condition of its own.
|
|
44978
45036
|
*/
|
|
44979
|
-
var LeafActionStepSchema = discriminatedUnion("kind", [AutomationWaitStepSchema,
|
|
44980
|
-
message: "a cap step needs a target",
|
|
44981
|
-
path: ["target"]
|
|
44982
|
-
}).refine((step) => step.target?.kind !== "domain" || step.actionId !== void 0, {
|
|
44983
|
-
message: "a domain target needs the vocabulary action it applies to (actionId)",
|
|
44984
|
-
path: ["actionId"]
|
|
44985
|
-
})]);
|
|
45037
|
+
var LeafActionStepSchema = discriminatedUnion("kind", [AutomationWaitStepSchema, AutomationCommandStepBaseSchema.superRefine(refineKnownAction)]);
|
|
44986
45038
|
/**
|
|
44987
45039
|
* The optional guard of a step (#472): `if` is the ONE `when` tree (D980), read
|
|
44988
45040
|
* on the state mirror when the step is reached; `else` runs when it is false.
|
|
@@ -45003,13 +45055,7 @@ var GuardedWaitStepSchema = AutomationWaitStepSchema.extend(stepGuardShape).refi
|
|
|
45003
45055
|
* same two step kinds an automation runs, minus a guard that reads the
|
|
45004
45056
|
* automation's trigger, which such a list does not have.
|
|
45005
45057
|
*/
|
|
45006
|
-
var ActionStepSchema = discriminatedUnion("kind", [GuardedWaitStepSchema,
|
|
45007
|
-
message: "a cap step needs a target",
|
|
45008
|
-
path: ["target"]
|
|
45009
|
-
}).refine((step) => step.target?.kind !== "domain" || step.actionId !== void 0, {
|
|
45010
|
-
message: "a domain target needs the vocabulary action it applies to (actionId)",
|
|
45011
|
-
path: ["actionId"]
|
|
45012
|
-
}).refine((step) => step.else === void 0 || step.if !== void 0, elseNeedsIf)]).superRefine((step, ctx) => {
|
|
45058
|
+
var ActionStepSchema = discriminatedUnion("kind", [GuardedWaitStepSchema, AutomationCommandStepBaseSchema.extend(stepGuardShape).superRefine(refineKnownAction).refine((step) => step.else === void 0 || step.if !== void 0, elseNeedsIf)]).superRefine((step, ctx) => {
|
|
45013
45059
|
if (step.if !== void 0 && hasTriggeredByLeaves(step.if)) ctx.addIssue({
|
|
45014
45060
|
code: "custom",
|
|
45015
45061
|
message: TRIGGERED_BY_NEEDS_AUTOMATION,
|
|
@@ -45024,17 +45070,11 @@ var ActionStepSchema = discriminatedUnion("kind", [GuardedWaitStepSchema, Automa
|
|
|
45024
45070
|
var continueOnErrorShape = {
|
|
45025
45071
|
/** A failure of this step is traced `failed-continued` and the next step runs. */
|
|
45026
45072
|
continueOnError: literal(true).optional() };
|
|
45027
|
-
/** An automation's
|
|
45028
|
-
var
|
|
45073
|
+
/** An automation's command step: the shared guarded command step, plus `continueOnError`. */
|
|
45074
|
+
var AutomationGuardedCommandStepSchema = AutomationCommandStepBaseSchema.extend({
|
|
45029
45075
|
...stepGuardShape,
|
|
45030
45076
|
...continueOnErrorShape
|
|
45031
|
-
}).refine((step) => step.
|
|
45032
|
-
message: "a cap step needs a target",
|
|
45033
|
-
path: ["target"]
|
|
45034
|
-
}).refine((step) => step.target?.kind !== "domain" || step.actionId !== void 0, {
|
|
45035
|
-
message: "a domain target needs the vocabulary action it applies to (actionId)",
|
|
45036
|
-
path: ["actionId"]
|
|
45037
|
-
}).refine((step) => step.else === void 0 || step.if !== void 0, elseNeedsIf);
|
|
45077
|
+
}).superRefine(refineKnownAction).refine((step) => step.else === void 0 || step.if !== void 0, elseNeedsIf);
|
|
45038
45078
|
/** End the run here, on purpose. `reason` may carry `{{vars.x}}` slots. */
|
|
45039
45079
|
var AutomationStopStepSchema = object({
|
|
45040
45080
|
kind: literal("stop"),
|
|
@@ -45114,13 +45154,7 @@ function nestTooDeep(issue) {
|
|
|
45114
45154
|
}
|
|
45115
45155
|
var plainStepSchemas = [
|
|
45116
45156
|
AutomationWaitStepSchema.strict(),
|
|
45117
|
-
|
|
45118
|
-
message: "a cap step needs a target",
|
|
45119
|
-
path: ["target"]
|
|
45120
|
-
}).refine((step) => step.target?.kind !== "domain" || step.actionId !== void 0, {
|
|
45121
|
-
message: "a domain target needs the vocabulary action it applies to (actionId)",
|
|
45122
|
-
path: ["actionId"]
|
|
45123
|
-
}),
|
|
45157
|
+
AutomationCommandStepBaseSchema.extend(continueOnErrorShape).strict().superRefine(refineKnownAction),
|
|
45124
45158
|
AutomationStopStepSchema.omit({ if: true }).strict(),
|
|
45125
45159
|
AutomationSetStepSchema.omit({ if: true }).strict(),
|
|
45126
45160
|
AutomationRunActionStepSchema.strict()
|
|
@@ -45131,7 +45165,7 @@ var plainStepSchemas = [
|
|
|
45131
45165
|
*/
|
|
45132
45166
|
var AutomationBranchStepSchema = discriminatedUnion("kind", plainStepSchemas, { error: nestTooDeep });
|
|
45133
45167
|
/** A step that can run beside others: it writes no variable and does not end the run. */
|
|
45134
|
-
var AutomationParallelMemberSchema = AutomationBranchStepSchema.refine((step) => step.kind === "
|
|
45168
|
+
var AutomationParallelMemberSchema = AutomationBranchStepSchema.refine((step) => step.kind === "command" || step.kind === "action" || step.kind === "wait", "a parallel holds device commands, actions and waits only: a set or a stop beside other steps has no order");
|
|
45135
45169
|
/**
|
|
45136
45170
|
* Several steps at once (#500): all are started together and all are awaited.
|
|
45137
45171
|
* A failure fails the run once the others finished, as in a sequence; a member
|
|
@@ -45211,7 +45245,7 @@ var AutomationBodyStepSchema = discriminatedUnion("kind", [
|
|
|
45211
45245
|
var AutomationRepeatStepSchema = repeatSchemaOf(AutomationBodyStepSchema);
|
|
45212
45246
|
var AutomationActionSchema = discriminatedUnion("kind", [
|
|
45213
45247
|
GuardedWaitStepSchema,
|
|
45214
|
-
|
|
45248
|
+
AutomationGuardedCommandStepSchema,
|
|
45215
45249
|
AutomationStopStepSchema,
|
|
45216
45250
|
AutomationSetStepSchema,
|
|
45217
45251
|
chooseSchemaOf(AutomationBodyStepSchema),
|
|
@@ -45282,110 +45316,6 @@ function convertLegacyRecipe(legacy) {
|
|
|
45282
45316
|
};
|
|
45283
45317
|
}
|
|
45284
45318
|
var AutomationRecipeSchema = union([CurrentAutomationRecipeSchema, LegacyAutomationRecipeSchema.transform(convertLegacyRecipe)]);
|
|
45285
|
-
var DETECTION_DESCRIPTIONS = {
|
|
45286
|
-
deviceId: "the camera that saw it",
|
|
45287
|
-
className: "what it saw",
|
|
45288
|
-
trackId: "the track",
|
|
45289
|
-
score: "how sure the detector was",
|
|
45290
|
-
label: "the label it matched (a name, a plate), empty when none"
|
|
45291
|
-
};
|
|
45292
|
-
var STATIONARY_DESCRIPTIONS = {
|
|
45293
|
-
deviceId: "the camera that saw it",
|
|
45294
|
-
className: "what it saw",
|
|
45295
|
-
zones: "the zones it is in",
|
|
45296
|
-
phase: "appeared or departed",
|
|
45297
|
-
entryId: "the stopped object"
|
|
45298
|
-
};
|
|
45299
|
-
var BECOMES_DESCRIPTIONS = {
|
|
45300
|
-
deviceId: "the device that changed",
|
|
45301
|
-
from: "the value before the change, empty when it is not known",
|
|
45302
|
-
to: "the value it changed to"
|
|
45303
|
-
};
|
|
45304
|
-
/**
|
|
45305
|
-
* A fire's payload is offered too, as `data.<path>` (`{{vars.data.channel}}`):
|
|
45306
|
-
* those names depend on the device, so they are not in this list.
|
|
45307
|
-
*/
|
|
45308
|
-
var FIRED_DESCRIPTIONS = {
|
|
45309
|
-
deviceId: "the device that fired",
|
|
45310
|
-
eventType: "the event, as the device names it",
|
|
45311
|
-
sourceId: "the part of the device that fired (a button), empty when it names none",
|
|
45312
|
-
eventKind: "the kind of press (press, long-press …), empty when the device declares none"
|
|
45313
|
-
};
|
|
45314
|
-
function describe$1(descriptions) {
|
|
45315
|
-
return Object.entries(descriptions).map(([name, description]) => ({
|
|
45316
|
-
name,
|
|
45317
|
-
description
|
|
45318
|
-
}));
|
|
45319
|
-
}
|
|
45320
|
-
describe$1(DETECTION_DESCRIPTIONS);
|
|
45321
|
-
describe$1(STATIONARY_DESCRIPTIONS);
|
|
45322
|
-
describe$1(FIRED_DESCRIPTIONS).map((v) => v.name === "deviceId" ? {
|
|
45323
|
-
...v,
|
|
45324
|
-
hintKey: "firedDeviceId"
|
|
45325
|
-
} : v);
|
|
45326
|
-
describe$1(BECOMES_DESCRIPTIONS).map((v) => v.name === "deviceId" ? {
|
|
45327
|
-
...v,
|
|
45328
|
-
hintKey: "becomesDeviceId"
|
|
45329
|
-
} : v);
|
|
45330
|
-
new Set(Object.values(COCO_TO_MACRO.mapping));
|
|
45331
|
-
/**
|
|
45332
|
-
* Generic stateless event emitter. Installed on a `DeviceType.EventEmitter`
|
|
45333
|
-
* device. Carries the device's EXACT declared event vocabulary verbatim
|
|
45334
|
-
* (NO normalization). Two shapes:
|
|
45335
|
-
* - structured source (HA `event.*` entity) → `vocabulary` is the fixed
|
|
45336
|
-
* declared set (e.g. press_short, press_long, …).
|
|
45337
|
-
* - generic/legacy source (HA bus events, e.g. zha_event) → `vocabulary`
|
|
45338
|
-
* is [] (open) and `lastEvent.data` carries the complex payload.
|
|
45339
|
-
* `vocabulary` (D1044) names each event for a person and types it for a
|
|
45340
|
-
* rule; `eventTypes` is its list of native tokens, kept for older readers.
|
|
45341
|
-
* A fire names the part of the device it came from in `sourceId` when the
|
|
45342
|
-
* vocabulary declares sources (a remote's buttons).
|
|
45343
|
-
* `seq` is monotonic so two identical events still produce distinct slice writes.
|
|
45344
|
-
*/
|
|
45345
|
-
var EventFireSchema = object({
|
|
45346
|
-
deviceId: number$1(),
|
|
45347
|
-
eventType: string(),
|
|
45348
|
-
sourceId: string().optional(),
|
|
45349
|
-
data: record(string(), unknown()).nullable(),
|
|
45350
|
-
timestamp: number$1().meta(readingMeta("none")),
|
|
45351
|
-
seq: number$1().meta(readingMeta("count"))
|
|
45352
|
-
});
|
|
45353
|
-
var EventEmitterStatusSchema = object({
|
|
45354
|
-
eventTypes: array(string()),
|
|
45355
|
-
/**
|
|
45356
|
-
* Required of a provider by the type. A status written before the field
|
|
45357
|
-
* existed still parses, as an empty vocabulary: read it through
|
|
45358
|
-
* `readEventVocabulary`, which then answers with the plain tokens.
|
|
45359
|
-
*/
|
|
45360
|
-
vocabulary: array(EventVocabularyEntrySchema).default([]),
|
|
45361
|
-
lastEvent: EventFireSchema.nullable(),
|
|
45362
|
-
eventCountSinceStart: number$1().meta(readingMeta("count"))
|
|
45363
|
-
});
|
|
45364
|
-
var eventEmitterCapability = {
|
|
45365
|
-
name: "event-emitter",
|
|
45366
|
-
scope: "device",
|
|
45367
|
-
deviceNative: true,
|
|
45368
|
-
mode: "singleton",
|
|
45369
|
-
deviceTypes: [DeviceType.EventEmitter],
|
|
45370
|
-
methods: {},
|
|
45371
|
-
events: { onEvent: { data: EventFireSchema } },
|
|
45372
|
-
status: {
|
|
45373
|
-
schema: EventEmitterStatusSchema,
|
|
45374
|
-
kind: "push"
|
|
45375
|
-
},
|
|
45376
|
-
runtimeState: EventEmitterStatusSchema,
|
|
45377
|
-
/**
|
|
45378
|
-
* Runtime-state durability: **session** — `eventCountSinceStart` names its own scope.
|
|
45379
|
-
*
|
|
45380
|
-
* See `RuntimeStateDurability`. Enforced by
|
|
45381
|
-
* `scripts/check-runtime-state-durability.ts`.
|
|
45382
|
-
*/
|
|
45383
|
-
durability: "session",
|
|
45384
|
-
/**
|
|
45385
|
-
* History (D850): none — `eventCountSinceStart` resets with the addon; events live in ledgers.
|
|
45386
|
-
*/
|
|
45387
|
-
history: "none"
|
|
45388
|
-
};
|
|
45389
45319
|
/**
|
|
45390
45320
|
* Schedulers (D983) — an entity that performs actions over the day.
|
|
45391
45321
|
*
|
|
@@ -45784,6 +45714,110 @@ var AutomationStateDocSchema = object({
|
|
|
45784
45714
|
lastRunStartedAt: number$1().nullable(),
|
|
45785
45715
|
traces: array(AutomationRunTraceSchema)
|
|
45786
45716
|
});
|
|
45717
|
+
var DETECTION_DESCRIPTIONS = {
|
|
45718
|
+
deviceId: "the camera that saw it",
|
|
45719
|
+
className: "what it saw",
|
|
45720
|
+
trackId: "the track",
|
|
45721
|
+
score: "how sure the detector was",
|
|
45722
|
+
label: "the label it matched (a name, a plate), empty when none"
|
|
45723
|
+
};
|
|
45724
|
+
var STATIONARY_DESCRIPTIONS = {
|
|
45725
|
+
deviceId: "the camera that saw it",
|
|
45726
|
+
className: "what it saw",
|
|
45727
|
+
zones: "the zones it is in",
|
|
45728
|
+
phase: "appeared or departed",
|
|
45729
|
+
entryId: "the stopped object"
|
|
45730
|
+
};
|
|
45731
|
+
var BECOMES_DESCRIPTIONS = {
|
|
45732
|
+
deviceId: "the device that changed",
|
|
45733
|
+
from: "the value before the change, empty when it is not known",
|
|
45734
|
+
to: "the value it changed to"
|
|
45735
|
+
};
|
|
45736
|
+
/**
|
|
45737
|
+
* A fire's payload is offered too, as `data.<path>` (`{{vars.data.channel}}`):
|
|
45738
|
+
* those names depend on the device, so they are not in this list.
|
|
45739
|
+
*/
|
|
45740
|
+
var FIRED_DESCRIPTIONS = {
|
|
45741
|
+
deviceId: "the device that fired",
|
|
45742
|
+
eventType: "the event, as the device names it",
|
|
45743
|
+
sourceId: "the part of the device that fired (a button), empty when it names none",
|
|
45744
|
+
eventKind: "the kind of press (press, long-press …), empty when the device declares none"
|
|
45745
|
+
};
|
|
45746
|
+
function describe$1(descriptions) {
|
|
45747
|
+
return Object.entries(descriptions).map(([name, description]) => ({
|
|
45748
|
+
name,
|
|
45749
|
+
description
|
|
45750
|
+
}));
|
|
45751
|
+
}
|
|
45752
|
+
describe$1(DETECTION_DESCRIPTIONS);
|
|
45753
|
+
describe$1(STATIONARY_DESCRIPTIONS);
|
|
45754
|
+
describe$1(FIRED_DESCRIPTIONS).map((v) => v.name === "deviceId" ? {
|
|
45755
|
+
...v,
|
|
45756
|
+
hintKey: "firedDeviceId"
|
|
45757
|
+
} : v);
|
|
45758
|
+
describe$1(BECOMES_DESCRIPTIONS).map((v) => v.name === "deviceId" ? {
|
|
45759
|
+
...v,
|
|
45760
|
+
hintKey: "becomesDeviceId"
|
|
45761
|
+
} : v);
|
|
45762
|
+
new Set(Object.values(COCO_TO_MACRO.mapping));
|
|
45763
|
+
/**
|
|
45764
|
+
* Generic stateless event emitter. Installed on a `DeviceType.EventEmitter`
|
|
45765
|
+
* device. Carries the device's EXACT declared event vocabulary verbatim
|
|
45766
|
+
* (NO normalization). Two shapes:
|
|
45767
|
+
* - structured source (HA `event.*` entity) → `vocabulary` is the fixed
|
|
45768
|
+
* declared set (e.g. press_short, press_long, …).
|
|
45769
|
+
* - generic/legacy source (HA bus events, e.g. zha_event) → `vocabulary`
|
|
45770
|
+
* is [] (open) and `lastEvent.data` carries the complex payload.
|
|
45771
|
+
* `vocabulary` (D1044) names each event for a person and types it for a
|
|
45772
|
+
* rule; `eventTypes` is its list of native tokens, kept for older readers.
|
|
45773
|
+
* A fire names the part of the device it came from in `sourceId` when the
|
|
45774
|
+
* vocabulary declares sources (a remote's buttons).
|
|
45775
|
+
* `seq` is monotonic so two identical events still produce distinct slice writes.
|
|
45776
|
+
*/
|
|
45777
|
+
var EventFireSchema = object({
|
|
45778
|
+
deviceId: number$1(),
|
|
45779
|
+
eventType: string(),
|
|
45780
|
+
sourceId: string().optional(),
|
|
45781
|
+
data: record(string(), unknown()).nullable(),
|
|
45782
|
+
timestamp: number$1().meta(readingMeta("none")),
|
|
45783
|
+
seq: number$1().meta(readingMeta("count"))
|
|
45784
|
+
});
|
|
45785
|
+
var EventEmitterStatusSchema = object({
|
|
45786
|
+
eventTypes: array(string()),
|
|
45787
|
+
/**
|
|
45788
|
+
* Required of a provider by the type. A status written before the field
|
|
45789
|
+
* existed still parses, as an empty vocabulary: read it through
|
|
45790
|
+
* `readEventVocabulary`, which then answers with the plain tokens.
|
|
45791
|
+
*/
|
|
45792
|
+
vocabulary: array(EventVocabularyEntrySchema).default([]),
|
|
45793
|
+
lastEvent: EventFireSchema.nullable(),
|
|
45794
|
+
eventCountSinceStart: number$1().meta(readingMeta("count"))
|
|
45795
|
+
});
|
|
45796
|
+
var eventEmitterCapability = {
|
|
45797
|
+
name: "event-emitter",
|
|
45798
|
+
scope: "device",
|
|
45799
|
+
deviceNative: true,
|
|
45800
|
+
mode: "singleton",
|
|
45801
|
+
deviceTypes: [DeviceType.EventEmitter],
|
|
45802
|
+
methods: {},
|
|
45803
|
+
events: { onEvent: { data: EventFireSchema } },
|
|
45804
|
+
status: {
|
|
45805
|
+
schema: EventEmitterStatusSchema,
|
|
45806
|
+
kind: "push"
|
|
45807
|
+
},
|
|
45808
|
+
runtimeState: EventEmitterStatusSchema,
|
|
45809
|
+
/**
|
|
45810
|
+
* Runtime-state durability: **session** — `eventCountSinceStart` names its own scope.
|
|
45811
|
+
*
|
|
45812
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
45813
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
45814
|
+
*/
|
|
45815
|
+
durability: "session",
|
|
45816
|
+
/**
|
|
45817
|
+
* History (D850): none — `eventCountSinceStart` resets with the addon; events live in ledgers.
|
|
45818
|
+
*/
|
|
45819
|
+
history: "none"
|
|
45820
|
+
};
|
|
45787
45821
|
/**
|
|
45788
45822
|
* Actions — named, runnable things an operator (or an automation) can start,
|
|
45789
45823
|
* declared as LIST ENTRIES, never as devices.
|
|
@@ -45963,6 +45997,177 @@ var UnitWarningSchema = object({
|
|
|
45963
45997
|
assumedUnit: string(),
|
|
45964
45998
|
message: string()
|
|
45965
45999
|
});
|
|
46000
|
+
/**
|
|
46001
|
+
* `device-export` — collection cap for addons that export camstack
|
|
46002
|
+
* devices to external ecosystems (HomeAssistant via MQTT discovery,
|
|
46003
|
+
* HomeKit/HAP, Alexa Smart Home, …).
|
|
46004
|
+
*
|
|
46005
|
+
* No `ecosystem` enum — the addon id identifies the export target.
|
|
46006
|
+
* Each addon owns its mapping logic in its own settings UI. The cap
|
|
46007
|
+
* exposes only the COMMON surface: link state, supported device
|
|
46008
|
+
* kinds (so the UI can filter the device picker), and per-device
|
|
46009
|
+
* expose/unexpose.
|
|
46010
|
+
*
|
|
46011
|
+
* INSTANCES. One export addon can serve several independent targets (a
|
|
46012
|
+
* HomeKit bridge, a Home Assistant broker). `listInstances` names them.
|
|
46013
|
+
* `instanceId` on a READ is optional: absent = the whole addon (linked if any
|
|
46014
|
+
* enabled instance is, `exposedDeviceCount` = distinct devices, `setup` = the
|
|
46015
|
+
* default instance's); present = that instance; an id the provider does not
|
|
46016
|
+
* serve is refused with `ExportInstanceUnknownError` (`export-instance-unknown`),
|
|
46017
|
+
* never answered empty. `instanceId` on a WRITE is REQUIRED: a membership
|
|
46018
|
+
* change always names the target it changes. A single-instance provider
|
|
46019
|
+
* accepts an absent id on a read or its own id, and refuses any other.
|
|
46020
|
+
*/
|
|
46021
|
+
/** Which instance of an export addon a call addresses. Absent on a read = the whole addon. */
|
|
46022
|
+
var InstanceIdSchema$1 = string().min(1);
|
|
46023
|
+
var InstancePinSchema = object({ instanceId: InstanceIdSchema$1.optional() });
|
|
46024
|
+
/** One independent export target of an addon. */
|
|
46025
|
+
var ExportInstanceSchema = object({
|
|
46026
|
+
instanceId: InstanceIdSchema$1,
|
|
46027
|
+
name: string(),
|
|
46028
|
+
enabled: boolean(),
|
|
46029
|
+
kind: string().optional(),
|
|
46030
|
+
/**
|
|
46031
|
+
* What this instance's platform accepts as a device name, in one operator-facing sentence
|
|
46032
|
+
* (D1049). The derived "Name on <instance>" field shows it; absent = the field says nothing
|
|
46033
|
+
* about the rule.
|
|
46034
|
+
*/
|
|
46035
|
+
nameRule: string().optional()
|
|
46036
|
+
});
|
|
46037
|
+
var LinkStateSchema = _enum([
|
|
46038
|
+
"unlinked",
|
|
46039
|
+
"linked",
|
|
46040
|
+
"error"
|
|
46041
|
+
]);
|
|
46042
|
+
/**
|
|
46043
|
+
* A single label/value row surfaced in the export panel's "Setup"
|
|
46044
|
+
* section. Rendered with a copy-to-clipboard button. `secret: true`
|
|
46045
|
+
* rows are masked by default with a reveal toggle (client ids,
|
|
46046
|
+
* secrets, …).
|
|
46047
|
+
*/
|
|
46048
|
+
var ExportSetupFieldSchema = object({
|
|
46049
|
+
label: string(),
|
|
46050
|
+
value: string(),
|
|
46051
|
+
/** Mask the value by default + render a reveal toggle (client id, secrets). */
|
|
46052
|
+
secret: boolean().optional()
|
|
46053
|
+
});
|
|
46054
|
+
/**
|
|
46055
|
+
* Generic, addon-agnostic pairing/account block. Any export addon can
|
|
46056
|
+
* surface a scannable QR, a set of copyable label/value rows, and a
|
|
46057
|
+
* free-form operator note — the `DeviceExportPanel` renders whatever
|
|
46058
|
+
* the provider supplies and skips the section entirely when `setup`
|
|
46059
|
+
* is unset.
|
|
46060
|
+
*/
|
|
46061
|
+
var ExportSetupSchema = object({
|
|
46062
|
+
/** A string to render as a scannable QR — HAP `X-HM://…` URI, a pairing URL, etc. Omitted when there's nothing to scan. */
|
|
46063
|
+
qr: string().optional(),
|
|
46064
|
+
/** Label/value rows shown with a copy button (HAP setup code, OAuth URLs, client id, linked-account count, …). */
|
|
46065
|
+
fields: array(ExportSetupFieldSchema).readonly().optional(),
|
|
46066
|
+
/** Free-form operator instructions rendered above the fields. */
|
|
46067
|
+
note: string().optional()
|
|
46068
|
+
});
|
|
46069
|
+
var DeviceExportStatusSchema = object({
|
|
46070
|
+
linkState: LinkStateSchema,
|
|
46071
|
+
exposedDeviceCount: number$1(),
|
|
46072
|
+
error: string().optional(),
|
|
46073
|
+
/**
|
|
46074
|
+
* Optional pairing/account info the panel renders in a generic
|
|
46075
|
+
* "Setup" section. Addon-agnostic — the addon id identifies the
|
|
46076
|
+
* export target, never an `ecosystem` key here.
|
|
46077
|
+
*/
|
|
46078
|
+
setup: ExportSetupSchema.optional()
|
|
46079
|
+
});
|
|
46080
|
+
var DeviceKindSchema = string();
|
|
46081
|
+
/**
|
|
46082
|
+
* EXPOSED NAME. What one instance calls a device on its platform, when it should not be the
|
|
46083
|
+
* device's own name. The exporter owns it: it is stored per instance in the exporter's per-device
|
|
46084
|
+
* store (D865), validated by the exporter against its platform's naming rules, and never reaches
|
|
46085
|
+
* the exported id — a rename updates the SAME device on the platform, never a second one.
|
|
46086
|
+
* Blank or `null` = the device's own name. A name the platform would refuse is refused with
|
|
46087
|
+
* `ExportNameRefusedError` (`export-name-refused`), whose message says why.
|
|
46088
|
+
*/
|
|
46089
|
+
var ExposedDeviceSchema = object({
|
|
46090
|
+
deviceId: string(),
|
|
46091
|
+
exposedAs: string().optional(),
|
|
46092
|
+
/** The operator's name for this device on this instance; absent = the device's own name. */
|
|
46093
|
+
exposedName: string().optional(),
|
|
46094
|
+
capabilities: array(string()).optional()
|
|
46095
|
+
});
|
|
46096
|
+
var ExposeInputSchema = object({
|
|
46097
|
+
instanceId: InstanceIdSchema$1,
|
|
46098
|
+
deviceId: string(),
|
|
46099
|
+
capabilities: array(string()).optional(),
|
|
46100
|
+
/** Set the exposed name in the same call; absent = leave it as it is, blank/null = clear it. */
|
|
46101
|
+
exposedName: string().nullable().optional()
|
|
46102
|
+
});
|
|
46103
|
+
var SetExposedNameInputSchema = object({
|
|
46104
|
+
instanceId: InstanceIdSchema$1,
|
|
46105
|
+
deviceId: string(),
|
|
46106
|
+
/** Blank or null = the device's own name. */
|
|
46107
|
+
name: string().nullable(),
|
|
46108
|
+
/**
|
|
46109
|
+
* Validate only: refuse exactly as a real call would, write nothing. A caller saving several
|
|
46110
|
+
* names at once checks every one first, so one refusal writes none (D1049).
|
|
46111
|
+
*/
|
|
46112
|
+
dryRun: boolean().optional()
|
|
46113
|
+
});
|
|
46114
|
+
var UnexposeInputSchema = object({
|
|
46115
|
+
instanceId: InstanceIdSchema$1,
|
|
46116
|
+
deviceId: string()
|
|
46117
|
+
});
|
|
46118
|
+
/**
|
|
46119
|
+
* What an exporter may expose besides devices (D968). An action and a
|
|
46120
|
+
* notification target are LIST ENTRIES, never devices (D954): they enter an
|
|
46121
|
+
* exporter's own selection, not the device registry.
|
|
46122
|
+
*/
|
|
46123
|
+
var ExportEntityKindSchema = _enum([
|
|
46124
|
+
"device",
|
|
46125
|
+
"action",
|
|
46126
|
+
"notifier"
|
|
46127
|
+
]);
|
|
46128
|
+
var ExportEntityRefSchema = discriminatedUnion("kind", [
|
|
46129
|
+
object({
|
|
46130
|
+
kind: literal("device"),
|
|
46131
|
+
deviceId: string()
|
|
46132
|
+
}),
|
|
46133
|
+
object({
|
|
46134
|
+
kind: literal("action"),
|
|
46135
|
+
actionId: string().min(1)
|
|
46136
|
+
}),
|
|
46137
|
+
object({
|
|
46138
|
+
kind: literal("notifier"),
|
|
46139
|
+
targetId: string().min(1)
|
|
46140
|
+
})
|
|
46141
|
+
]);
|
|
46142
|
+
var ExposedEntitySchema = object({
|
|
46143
|
+
entity: ExportEntityRefSchema,
|
|
46144
|
+
exposedAs: string().optional()
|
|
46145
|
+
});
|
|
46146
|
+
var ExposeEntityInputSchema = object({
|
|
46147
|
+
instanceId: InstanceIdSchema$1,
|
|
46148
|
+
entity: ExportEntityRefSchema
|
|
46149
|
+
});
|
|
46150
|
+
var UnexposeEntityInputSchema = object({
|
|
46151
|
+
instanceId: InstanceIdSchema$1,
|
|
46152
|
+
entity: ExportEntityRefSchema
|
|
46153
|
+
});
|
|
46154
|
+
method(InstancePinSchema, DeviceExportStatusSchema), method(_void(), array(ExportInstanceSchema)), method(InstancePinSchema, array(DeviceKindSchema)), method(InstancePinSchema, array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
|
|
46155
|
+
kind: "mutation",
|
|
46156
|
+
auth: "admin"
|
|
46157
|
+
}), method(UnexposeInputSchema, _void(), {
|
|
46158
|
+
kind: "mutation",
|
|
46159
|
+
auth: "admin"
|
|
46160
|
+
}), method(SetExposedNameInputSchema, _void(), {
|
|
46161
|
+
kind: "mutation",
|
|
46162
|
+
auth: "admin"
|
|
46163
|
+
}), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(InstancePinSchema, array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
|
|
46164
|
+
kind: "mutation",
|
|
46165
|
+
auth: "admin"
|
|
46166
|
+
}), optionalMethod(UnexposeEntityInputSchema, _void(), {
|
|
46167
|
+
kind: "mutation",
|
|
46168
|
+
auth: "admin"
|
|
46169
|
+
});
|
|
46170
|
+
record(string(), string());
|
|
45966
46171
|
_enum([
|
|
45967
46172
|
"automation",
|
|
45968
46173
|
"action",
|
|
@@ -51166,11 +51371,13 @@ var IrCodecError = class extends Error {
|
|
|
51166
51371
|
this.code = code;
|
|
51167
51372
|
}
|
|
51168
51373
|
};
|
|
51374
|
+
/** Maximum number of mark/space entries one pulse train may carry. */
|
|
51375
|
+
var IR_TIMINGS_MAX = 1024;
|
|
51169
51376
|
/**
|
|
51170
51377
|
* A pulse train in signed microseconds: mark (+) first, then space (-), strictly
|
|
51171
51378
|
* alternating, no zeros, 2..1024 entries.
|
|
51172
51379
|
*/
|
|
51173
|
-
var IrTimingsSchema = array(number$1().int()).min(2).max(
|
|
51380
|
+
var IrTimingsSchema = array(number$1().int()).min(2).max(IR_TIMINGS_MAX).superRefine((timings, ctx) => {
|
|
51174
51381
|
timings.forEach((value, index) => {
|
|
51175
51382
|
const expectMark = index % 2 === 0;
|
|
51176
51383
|
if (value === 0 || value > 0 !== expectMark) ctx.addIssue({
|
|
@@ -51217,15 +51424,186 @@ function decodePronto(code) {
|
|
|
51217
51424
|
carrierHz: Math.round(1e6 / unitUs)
|
|
51218
51425
|
};
|
|
51219
51426
|
}
|
|
51220
|
-
/**
|
|
51221
|
-
|
|
51222
|
-
|
|
51223
|
-
|
|
51224
|
-
|
|
51225
|
-
|
|
51226
|
-
|
|
51227
|
-
|
|
51228
|
-
|
|
51427
|
+
/**
|
|
51428
|
+
* FastLZ level 1: a byte-oriented LZ77 stream. A control byte below 32 starts a
|
|
51429
|
+
* literal run of `ctrl + 1` bytes; otherwise it is a back-reference whose length
|
|
51430
|
+
* is `ctrl >> 5` (extended by one byte when 7) and whose distance is split
|
|
51431
|
+
* between the low 5 bits and the next byte.
|
|
51432
|
+
*/
|
|
51433
|
+
var MAX_DISTANCE = 8192;
|
|
51434
|
+
var MAX_LITERAL_RUN = 32;
|
|
51435
|
+
var MIN_MATCH = 3;
|
|
51436
|
+
var MAX_MATCH = 264;
|
|
51437
|
+
/**
|
|
51438
|
+
* Most bytes a decoded IR code may hold: two bytes per timing. A level-1 stream
|
|
51439
|
+
* can expand ~130x, so the decoder checks this bound before every write.
|
|
51440
|
+
*/
|
|
51441
|
+
var IR_DECOMPRESSED_MAX = IR_TIMINGS_MAX * 2;
|
|
51442
|
+
function fastlzDecompress(input) {
|
|
51443
|
+
const out = [];
|
|
51444
|
+
let ip = 0;
|
|
51445
|
+
const room = (count) => {
|
|
51446
|
+
if (out.length + count > IR_DECOMPRESSED_MAX) throw new IrCodecError("tuya-malformed", "decompressed code is larger than any IR train");
|
|
51447
|
+
};
|
|
51448
|
+
const next = () => {
|
|
51449
|
+
const byte = input[ip++];
|
|
51450
|
+
if (byte === void 0) throw new IrCodecError("tuya-malformed", "compressed stream is truncated");
|
|
51451
|
+
return byte;
|
|
51452
|
+
};
|
|
51453
|
+
while (ip < input.length) {
|
|
51454
|
+
const ctrl = next();
|
|
51455
|
+
if (ctrl < 32) {
|
|
51456
|
+
room(ctrl + 1);
|
|
51457
|
+
for (let i = 0; i <= ctrl; i++) out.push(next());
|
|
51458
|
+
continue;
|
|
51459
|
+
}
|
|
51460
|
+
let len = (ctrl >> 5) - 1;
|
|
51461
|
+
if (len === 6) len += next();
|
|
51462
|
+
const distance = ((ctrl & 31) << 8) + next() + 1;
|
|
51463
|
+
const start = out.length - distance;
|
|
51464
|
+
if (start < 0) throw new IrCodecError("tuya-malformed", "back-reference points before the stream");
|
|
51465
|
+
room(len + 3);
|
|
51466
|
+
for (let i = 0; i < len + 3; i++) {
|
|
51467
|
+
const byte = out[start + i];
|
|
51468
|
+
if (byte === void 0) throw new IrCodecError("tuya-malformed", "back-reference is invalid");
|
|
51469
|
+
out.push(byte);
|
|
51470
|
+
}
|
|
51471
|
+
}
|
|
51472
|
+
return Uint8Array.from(out);
|
|
51473
|
+
}
|
|
51474
|
+
function fastlzCompress(input) {
|
|
51475
|
+
const out = [];
|
|
51476
|
+
const lastSeen = /* @__PURE__ */ new Map();
|
|
51477
|
+
let literalStart = 0;
|
|
51478
|
+
const flushLiterals = (end) => {
|
|
51479
|
+
for (let from = literalStart; from < end; from += MAX_LITERAL_RUN) {
|
|
51480
|
+
const to = Math.min(from + MAX_LITERAL_RUN, end);
|
|
51481
|
+
out.push(to - from - 1);
|
|
51482
|
+
for (let i = from; i < to; i++) out.push(input[i] ?? 0);
|
|
51483
|
+
}
|
|
51484
|
+
};
|
|
51485
|
+
let pos = 0;
|
|
51486
|
+
while (pos < input.length) {
|
|
51487
|
+
let matchLen = 0;
|
|
51488
|
+
let matchDistance = 0;
|
|
51489
|
+
if (pos + MIN_MATCH <= input.length) {
|
|
51490
|
+
const key = (input[pos] ?? 0) << 16 | (input[pos + 1] ?? 0) << 8 | (input[pos + 2] ?? 0);
|
|
51491
|
+
const candidate = lastSeen.get(key);
|
|
51492
|
+
lastSeen.set(key, pos);
|
|
51493
|
+
if (candidate !== void 0 && pos - candidate <= MAX_DISTANCE) {
|
|
51494
|
+
let len = 0;
|
|
51495
|
+
while (pos + len < input.length && len < MAX_MATCH && input[candidate + len] === input[pos + len]) len++;
|
|
51496
|
+
if (len >= MIN_MATCH) {
|
|
51497
|
+
matchLen = len;
|
|
51498
|
+
matchDistance = pos - candidate - 1;
|
|
51499
|
+
}
|
|
51500
|
+
}
|
|
51501
|
+
}
|
|
51502
|
+
if (matchLen === 0) {
|
|
51503
|
+
pos++;
|
|
51504
|
+
continue;
|
|
51505
|
+
}
|
|
51506
|
+
flushLiterals(pos);
|
|
51507
|
+
const enc = matchLen - 2;
|
|
51508
|
+
if (enc < 7) out.push(enc << 5 | matchDistance >> 8);
|
|
51509
|
+
else out.push(224 | matchDistance >> 8, enc - 7);
|
|
51510
|
+
out.push(matchDistance & 255);
|
|
51511
|
+
pos += matchLen;
|
|
51512
|
+
literalStart = pos;
|
|
51513
|
+
}
|
|
51514
|
+
flushLiterals(input.length);
|
|
51515
|
+
return Uint8Array.from(out);
|
|
51516
|
+
}
|
|
51517
|
+
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
51518
|
+
var MAX_DURATION_US = 65535;
|
|
51519
|
+
function toBase64(bytes) {
|
|
51520
|
+
let out = "";
|
|
51521
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
51522
|
+
const a = bytes[i] ?? 0;
|
|
51523
|
+
const b = bytes[i + 1] ?? 0;
|
|
51524
|
+
const c = bytes[i + 2] ?? 0;
|
|
51525
|
+
const word = a << 16 | b << 8 | c;
|
|
51526
|
+
out += ALPHABET[word >> 18 & 63];
|
|
51527
|
+
out += ALPHABET[word >> 12 & 63];
|
|
51528
|
+
out += i + 1 < bytes.length ? ALPHABET[word >> 6 & 63] : "=";
|
|
51529
|
+
out += i + 2 < bytes.length ? ALPHABET[word & 63] : "=";
|
|
51530
|
+
}
|
|
51531
|
+
return out;
|
|
51532
|
+
}
|
|
51533
|
+
function fromBase64(text) {
|
|
51534
|
+
const clean = text.trim().replace(/=+$/, "");
|
|
51535
|
+
if (clean.length === 0 || clean.length % 4 === 1) throw new IrCodecError("tuya-malformed", "not a base64 string");
|
|
51536
|
+
const out = [];
|
|
51537
|
+
let bits = 0;
|
|
51538
|
+
let acc = 0;
|
|
51539
|
+
for (const ch of clean) {
|
|
51540
|
+
const value = ALPHABET.indexOf(ch);
|
|
51541
|
+
if (value < 0) throw new IrCodecError("tuya-malformed", "not a base64 string");
|
|
51542
|
+
acc = acc << 6 | value;
|
|
51543
|
+
bits += 6;
|
|
51544
|
+
if (bits >= 8) {
|
|
51545
|
+
bits -= 8;
|
|
51546
|
+
out.push(acc >> bits & 255);
|
|
51547
|
+
}
|
|
51548
|
+
}
|
|
51549
|
+
return Uint8Array.from(out);
|
|
51550
|
+
}
|
|
51551
|
+
/** Signed pulse train to a Tuya IR code: base64(FastLZ(uint16 LE durations)). */
|
|
51552
|
+
function encodeTuya(timings) {
|
|
51553
|
+
const bytes = new Uint8Array(timings.length * 2);
|
|
51554
|
+
timings.forEach((value, index) => {
|
|
51555
|
+
const duration = Math.abs(value);
|
|
51556
|
+
if (duration === 0 || duration > MAX_DURATION_US) throw new IrCodecError("invalid-timings", `timing ${index} does not fit 16 bits`);
|
|
51557
|
+
bytes[index * 2] = duration & 255;
|
|
51558
|
+
bytes[index * 2 + 1] = duration >> 8;
|
|
51559
|
+
});
|
|
51560
|
+
return toBase64(fastlzCompress(bytes));
|
|
51561
|
+
}
|
|
51562
|
+
/** A Tuya IR code to a signed pulse train (mark first). Throws `IrCodecError`. */
|
|
51563
|
+
function decodeTuya(code) {
|
|
51564
|
+
const bytes = fastlzDecompress(fromBase64(code));
|
|
51565
|
+
if (bytes.length === 0 || bytes.length % 2 !== 0) throw new IrCodecError("tuya-malformed", "payload is not a list of 16-bit durations");
|
|
51566
|
+
const timings = [];
|
|
51567
|
+
for (let i = 0; i < bytes.length; i += 2) {
|
|
51568
|
+
const duration = (bytes[i] ?? 0) | (bytes[i + 1] ?? 0) << 8;
|
|
51569
|
+
timings.push(i / 2 % 2 === 0 ? duration : -duration);
|
|
51570
|
+
}
|
|
51571
|
+
const checked = IrTimingsSchema.safeParse(timings);
|
|
51572
|
+
if (!checked.success) throw new IrCodecError("tuya-malformed", checked.error.message);
|
|
51573
|
+
return checked.data;
|
|
51574
|
+
}
|
|
51575
|
+
/** Device-native IR code formats a transmitter can replay verbatim. */
|
|
51576
|
+
var IrNativeFormatSchema = _enum(["tuya"]);
|
|
51577
|
+
/**
|
|
51578
|
+
* Longest native code accepted: base64 of the worst-case compressed stream
|
|
51579
|
+
* (every decoded byte as a literal, plus one control byte per 32), with margin.
|
|
51580
|
+
*/
|
|
51581
|
+
var IR_NATIVE_CODE_MAX_CHARS = Math.ceil((IR_DECOMPRESSED_MAX + Math.ceil(IR_DECOMPRESSED_MAX / 32)) / 3) * 4 + 256;
|
|
51582
|
+
var IrNativeCodeSchema = object({
|
|
51583
|
+
format: IrNativeFormatSchema,
|
|
51584
|
+
code: string().min(1).max(IR_NATIVE_CODE_MAX_CHARS)
|
|
51585
|
+
});
|
|
51586
|
+
var IR_NATIVE_CODECS = { tuya: {
|
|
51587
|
+
encode: encodeTuya,
|
|
51588
|
+
decode: decodeTuya
|
|
51589
|
+
} };
|
|
51590
|
+
/** What a remote command stores: a raw pulse train, a learned Pronto code, or a device-native code. */
|
|
51591
|
+
var IrPayloadSchema = discriminatedUnion("kind", [
|
|
51592
|
+
object({
|
|
51593
|
+
kind: literal("raw"),
|
|
51594
|
+
timings: array(number$1().int()),
|
|
51595
|
+
settings: TransmitSettingsSchema.optional()
|
|
51596
|
+
}),
|
|
51597
|
+
object({
|
|
51598
|
+
kind: literal("pronto"),
|
|
51599
|
+
code: string()
|
|
51600
|
+
}),
|
|
51601
|
+
object({
|
|
51602
|
+
kind: literal("native"),
|
|
51603
|
+
format: IrNativeFormatSchema,
|
|
51604
|
+
code: string()
|
|
51605
|
+
})
|
|
51606
|
+
]);
|
|
51229
51607
|
/** Resolve a payload to what `ir-transmitter.send` takes. Throws `IrCodecError`. */
|
|
51230
51608
|
function toTimings(payload) {
|
|
51231
51609
|
if (payload.kind === "pronto") {
|
|
@@ -51235,6 +51613,10 @@ function toTimings(payload) {
|
|
|
51235
51613
|
settings: { carrierHz: decoded.carrierHz }
|
|
51236
51614
|
};
|
|
51237
51615
|
}
|
|
51616
|
+
if (payload.kind === "native") return {
|
|
51617
|
+
timings: IR_NATIVE_CODECS[payload.format].decode(payload.code),
|
|
51618
|
+
settings: {}
|
|
51619
|
+
};
|
|
51238
51620
|
const checked = IrTimingsSchema.safeParse(payload.timings);
|
|
51239
51621
|
if (!checked.success) throw new IrCodecError("invalid-timings", checked.error.message);
|
|
51240
51622
|
return {
|
|
@@ -53567,144 +53949,6 @@ method(object({
|
|
|
53567
53949
|
kind: "mutation",
|
|
53568
53950
|
auth: "admin"
|
|
53569
53951
|
});
|
|
53570
|
-
/**
|
|
53571
|
-
* `device-export` — collection cap for addons that export camstack
|
|
53572
|
-
* devices to external ecosystems (HomeAssistant via MQTT discovery,
|
|
53573
|
-
* HomeKit/HAP, Alexa Smart Home, …).
|
|
53574
|
-
*
|
|
53575
|
-
* No `ecosystem` enum — the addon id identifies the export target.
|
|
53576
|
-
* Each addon owns its mapping logic in its own settings UI. The cap
|
|
53577
|
-
* exposes only the COMMON surface: link state, supported device
|
|
53578
|
-
* kinds (so the UI can filter the device picker), and per-device
|
|
53579
|
-
* expose/unexpose.
|
|
53580
|
-
*
|
|
53581
|
-
* INSTANCES. One export addon can serve several independent targets (a
|
|
53582
|
-
* HomeKit bridge, a Home Assistant broker). `listInstances` names them.
|
|
53583
|
-
* `instanceId` on a READ is optional: absent = the whole addon (linked if any
|
|
53584
|
-
* enabled instance is, `exposedDeviceCount` = distinct devices, `setup` = the
|
|
53585
|
-
* default instance's); present = that instance; an id the provider does not
|
|
53586
|
-
* serve is refused with `ExportInstanceUnknownError` (`export-instance-unknown`),
|
|
53587
|
-
* never answered empty. `instanceId` on a WRITE is REQUIRED: a membership
|
|
53588
|
-
* change always names the target it changes. A single-instance provider
|
|
53589
|
-
* accepts an absent id on a read or its own id, and refuses any other.
|
|
53590
|
-
*/
|
|
53591
|
-
/** Which instance of an export addon a call addresses. Absent on a read = the whole addon. */
|
|
53592
|
-
var InstanceIdSchema$1 = string().min(1);
|
|
53593
|
-
var InstancePinSchema = object({ instanceId: InstanceIdSchema$1.optional() });
|
|
53594
|
-
/** One independent export target of an addon. */
|
|
53595
|
-
var ExportInstanceSchema = object({
|
|
53596
|
-
instanceId: InstanceIdSchema$1,
|
|
53597
|
-
name: string(),
|
|
53598
|
-
enabled: boolean(),
|
|
53599
|
-
kind: string().optional()
|
|
53600
|
-
});
|
|
53601
|
-
var LinkStateSchema = _enum([
|
|
53602
|
-
"unlinked",
|
|
53603
|
-
"linked",
|
|
53604
|
-
"error"
|
|
53605
|
-
]);
|
|
53606
|
-
/**
|
|
53607
|
-
* A single label/value row surfaced in the export panel's "Setup"
|
|
53608
|
-
* section. Rendered with a copy-to-clipboard button. `secret: true`
|
|
53609
|
-
* rows are masked by default with a reveal toggle (client ids,
|
|
53610
|
-
* secrets, …).
|
|
53611
|
-
*/
|
|
53612
|
-
var ExportSetupFieldSchema = object({
|
|
53613
|
-
label: string(),
|
|
53614
|
-
value: string(),
|
|
53615
|
-
/** Mask the value by default + render a reveal toggle (client id, secrets). */
|
|
53616
|
-
secret: boolean().optional()
|
|
53617
|
-
});
|
|
53618
|
-
/**
|
|
53619
|
-
* Generic, addon-agnostic pairing/account block. Any export addon can
|
|
53620
|
-
* surface a scannable QR, a set of copyable label/value rows, and a
|
|
53621
|
-
* free-form operator note — the `DeviceExportPanel` renders whatever
|
|
53622
|
-
* the provider supplies and skips the section entirely when `setup`
|
|
53623
|
-
* is unset.
|
|
53624
|
-
*/
|
|
53625
|
-
var ExportSetupSchema = object({
|
|
53626
|
-
/** A string to render as a scannable QR — HAP `X-HM://…` URI, a pairing URL, etc. Omitted when there's nothing to scan. */
|
|
53627
|
-
qr: string().optional(),
|
|
53628
|
-
/** Label/value rows shown with a copy button (HAP setup code, OAuth URLs, client id, linked-account count, …). */
|
|
53629
|
-
fields: array(ExportSetupFieldSchema).readonly().optional(),
|
|
53630
|
-
/** Free-form operator instructions rendered above the fields. */
|
|
53631
|
-
note: string().optional()
|
|
53632
|
-
});
|
|
53633
|
-
var DeviceExportStatusSchema = object({
|
|
53634
|
-
linkState: LinkStateSchema,
|
|
53635
|
-
exposedDeviceCount: number$1(),
|
|
53636
|
-
error: string().optional(),
|
|
53637
|
-
/**
|
|
53638
|
-
* Optional pairing/account info the panel renders in a generic
|
|
53639
|
-
* "Setup" section. Addon-agnostic — the addon id identifies the
|
|
53640
|
-
* export target, never an `ecosystem` key here.
|
|
53641
|
-
*/
|
|
53642
|
-
setup: ExportSetupSchema.optional()
|
|
53643
|
-
});
|
|
53644
|
-
var DeviceKindSchema = string();
|
|
53645
|
-
var ExposedDeviceSchema = object({
|
|
53646
|
-
deviceId: string(),
|
|
53647
|
-
exposedAs: string().optional(),
|
|
53648
|
-
capabilities: array(string()).optional()
|
|
53649
|
-
});
|
|
53650
|
-
var ExposeInputSchema = object({
|
|
53651
|
-
instanceId: InstanceIdSchema$1,
|
|
53652
|
-
deviceId: string(),
|
|
53653
|
-
capabilities: array(string()).optional()
|
|
53654
|
-
});
|
|
53655
|
-
var UnexposeInputSchema = object({
|
|
53656
|
-
instanceId: InstanceIdSchema$1,
|
|
53657
|
-
deviceId: string()
|
|
53658
|
-
});
|
|
53659
|
-
/**
|
|
53660
|
-
* What an exporter may expose besides devices (D968). An action and a
|
|
53661
|
-
* notification target are LIST ENTRIES, never devices (D954): they enter an
|
|
53662
|
-
* exporter's own selection, not the device registry.
|
|
53663
|
-
*/
|
|
53664
|
-
var ExportEntityKindSchema = _enum([
|
|
53665
|
-
"device",
|
|
53666
|
-
"action",
|
|
53667
|
-
"notifier"
|
|
53668
|
-
]);
|
|
53669
|
-
var ExportEntityRefSchema = discriminatedUnion("kind", [
|
|
53670
|
-
object({
|
|
53671
|
-
kind: literal("device"),
|
|
53672
|
-
deviceId: string()
|
|
53673
|
-
}),
|
|
53674
|
-
object({
|
|
53675
|
-
kind: literal("action"),
|
|
53676
|
-
actionId: string().min(1)
|
|
53677
|
-
}),
|
|
53678
|
-
object({
|
|
53679
|
-
kind: literal("notifier"),
|
|
53680
|
-
targetId: string().min(1)
|
|
53681
|
-
})
|
|
53682
|
-
]);
|
|
53683
|
-
var ExposedEntitySchema = object({
|
|
53684
|
-
entity: ExportEntityRefSchema,
|
|
53685
|
-
exposedAs: string().optional()
|
|
53686
|
-
});
|
|
53687
|
-
var ExposeEntityInputSchema = object({
|
|
53688
|
-
instanceId: InstanceIdSchema$1,
|
|
53689
|
-
entity: ExportEntityRefSchema
|
|
53690
|
-
});
|
|
53691
|
-
var UnexposeEntityInputSchema = object({
|
|
53692
|
-
instanceId: InstanceIdSchema$1,
|
|
53693
|
-
entity: ExportEntityRefSchema
|
|
53694
|
-
});
|
|
53695
|
-
method(InstancePinSchema, DeviceExportStatusSchema), method(_void(), array(ExportInstanceSchema)), method(InstancePinSchema, array(DeviceKindSchema)), method(InstancePinSchema, array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
|
|
53696
|
-
kind: "mutation",
|
|
53697
|
-
auth: "admin"
|
|
53698
|
-
}), method(UnexposeInputSchema, _void(), {
|
|
53699
|
-
kind: "mutation",
|
|
53700
|
-
auth: "admin"
|
|
53701
|
-
}), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(InstancePinSchema, array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
|
|
53702
|
-
kind: "mutation",
|
|
53703
|
-
auth: "admin"
|
|
53704
|
-
}), optionalMethod(UnexposeEntityInputSchema, _void(), {
|
|
53705
|
-
kind: "mutation",
|
|
53706
|
-
auth: "admin"
|
|
53707
|
-
});
|
|
53708
53952
|
var DeviceAliasSchema = string().max(64).regex(/^[a-z][a-z0-9_]*$/);
|
|
53709
53953
|
var DeviceTemplateSchema = string().min(1).max(4096);
|
|
53710
53954
|
/** One value a caller hands the renderer for `thisDevice`. */
|
|
@@ -58559,6 +58803,128 @@ var StateLookSchema = object({
|
|
|
58559
58803
|
background: _enum(HERO_BACKGROUNDS),
|
|
58560
58804
|
animation: HeroAnimationSchema
|
|
58561
58805
|
});
|
|
58806
|
+
/** A string in both languages, as the cap vocabulary writes it (D920): no `t()` in the model. */
|
|
58807
|
+
var HeroTextSchema = object({
|
|
58808
|
+
en: string(),
|
|
58809
|
+
it: string()
|
|
58810
|
+
});
|
|
58811
|
+
/**
|
|
58812
|
+
* The ONE measurement a simple sensor shows (card wave, phase 1).
|
|
58813
|
+
*
|
|
58814
|
+
* A sensor that measures a single quantity (a temperature, a humidity, a power,
|
|
58815
|
+
* a contact, a text state) is drawn as that reading, once, big, inside a scene
|
|
58816
|
+
* whose motion says what the number means: heat rising when hot, flakes when
|
|
58817
|
+
* cold, a wire flowing at the speed of the power, a ring while a window is
|
|
58818
|
+
* open. Everything the card draws is decided here; a client only paints it.
|
|
58819
|
+
*/
|
|
58820
|
+
var HERO_MEASUREMENT_QUANTITIES = [
|
|
58821
|
+
"temperature",
|
|
58822
|
+
"humidity",
|
|
58823
|
+
"power",
|
|
58824
|
+
"contact",
|
|
58825
|
+
"text"
|
|
58826
|
+
];
|
|
58827
|
+
/** How a temperature reads to a person; drives the heat or the cold scene. */
|
|
58828
|
+
var HERO_MEASUREMENT_BANDS = [
|
|
58829
|
+
"hot",
|
|
58830
|
+
"mild",
|
|
58831
|
+
"cold"
|
|
58832
|
+
];
|
|
58833
|
+
/** `updated` = the value was last read then ("2 min ago"); `changed` = it has held since then ("for 12 min"). */
|
|
58834
|
+
var HERO_MEASUREMENT_SINCE_KINDS = ["updated", "changed"];
|
|
58835
|
+
var HeroMeasurementReadingSchema = object({
|
|
58836
|
+
value: number$1(),
|
|
58837
|
+
quantity: ReadingQuantitySchema
|
|
58838
|
+
});
|
|
58839
|
+
var HeroMeasurementSchema = object({
|
|
58840
|
+
quantity: _enum(HERO_MEASUREMENT_QUANTITIES),
|
|
58841
|
+
reading: HeroMeasurementReadingSchema.nullable(),
|
|
58842
|
+
icon: IconKeySchema.nullable(),
|
|
58843
|
+
value: HeroTextSchema,
|
|
58844
|
+
unit: string().nullable(),
|
|
58845
|
+
band: _enum(HERO_MEASUREMENT_BANDS).nullable(),
|
|
58846
|
+
level: number$1().min(0).max(1).nullable(),
|
|
58847
|
+
word: HeroTextSchema,
|
|
58848
|
+
tone: _enum(HERO_TONES),
|
|
58849
|
+
since: number$1().nullable(),
|
|
58850
|
+
sinceKind: _enum(HERO_MEASUREMENT_SINCE_KINDS),
|
|
58851
|
+
historyKey: string().nullable()
|
|
58852
|
+
});
|
|
58853
|
+
var same$2 = (text) => t(text, text);
|
|
58854
|
+
var clamp01$1 = (n) => Math.min(1, Math.max(0, n));
|
|
58855
|
+
var trimmed = (n, digits) => String(Number(n.toFixed(digits)));
|
|
58856
|
+
function temperatureBand(celsius) {
|
|
58857
|
+
if (celsius < 12) return "cold";
|
|
58858
|
+
if (celsius > 28) return "hot";
|
|
58859
|
+
return "mild";
|
|
58860
|
+
}
|
|
58861
|
+
var BAND_WORD = {
|
|
58862
|
+
hot: t("Hot", "Caldo"),
|
|
58863
|
+
mild: t("Normal", "Normale"),
|
|
58864
|
+
cold: t("Cold", "Freddo")
|
|
58865
|
+
};
|
|
58866
|
+
var BAND_TONE = {
|
|
58867
|
+
hot: "warning",
|
|
58868
|
+
mild: "muted",
|
|
58869
|
+
cold: "info"
|
|
58870
|
+
};
|
|
58871
|
+
function temperatureMeasurement(celsius, since) {
|
|
58872
|
+
const band = temperatureBand(celsius);
|
|
58873
|
+
return {
|
|
58874
|
+
quantity: "temperature",
|
|
58875
|
+
reading: {
|
|
58876
|
+
value: celsius,
|
|
58877
|
+
quantity: "temperature"
|
|
58878
|
+
},
|
|
58879
|
+
icon: "thermometer",
|
|
58880
|
+
value: same$2(trimmed(celsius, 1)),
|
|
58881
|
+
unit: "°C",
|
|
58882
|
+
band,
|
|
58883
|
+
level: null,
|
|
58884
|
+
word: BAND_WORD[band],
|
|
58885
|
+
tone: BAND_TONE[band],
|
|
58886
|
+
since,
|
|
58887
|
+
sinceKind: "updated",
|
|
58888
|
+
historyKey: "cap:temperature-sensor.temperature"
|
|
58889
|
+
};
|
|
58890
|
+
}
|
|
58891
|
+
function humidityMeasurement(percent, since) {
|
|
58892
|
+
const dry = percent < 30;
|
|
58893
|
+
const humid = percent > 70;
|
|
58894
|
+
return {
|
|
58895
|
+
quantity: "humidity",
|
|
58896
|
+
reading: {
|
|
58897
|
+
value: percent,
|
|
58898
|
+
quantity: "humidity"
|
|
58899
|
+
},
|
|
58900
|
+
icon: "droplets",
|
|
58901
|
+
value: same$2(String(Math.round(percent))),
|
|
58902
|
+
unit: "%",
|
|
58903
|
+
band: null,
|
|
58904
|
+
level: clamp01$1(percent / 100),
|
|
58905
|
+
word: dry ? t("Dry", "Secco") : humid ? t("Humid", "Umido") : t("Normal", "Normale"),
|
|
58906
|
+
tone: dry || humid ? "warning" : "info",
|
|
58907
|
+
since,
|
|
58908
|
+
sinceKind: "updated",
|
|
58909
|
+
historyKey: "cap:humidity-sensor.humidity"
|
|
58910
|
+
};
|
|
58911
|
+
}
|
|
58912
|
+
function textMeasurement(text, since) {
|
|
58913
|
+
return {
|
|
58914
|
+
quantity: "text",
|
|
58915
|
+
reading: null,
|
|
58916
|
+
icon: null,
|
|
58917
|
+
value: same$2(text),
|
|
58918
|
+
unit: null,
|
|
58919
|
+
band: null,
|
|
58920
|
+
level: null,
|
|
58921
|
+
word: t("Changed", "Cambiato"),
|
|
58922
|
+
tone: "muted",
|
|
58923
|
+
since,
|
|
58924
|
+
sinceKind: "updated",
|
|
58925
|
+
historyKey: null
|
|
58926
|
+
};
|
|
58927
|
+
}
|
|
58562
58928
|
/** The scene as the model carries it. `null` = the device did not say (never a guess). */
|
|
58563
58929
|
var HeroSceneSchema = object({
|
|
58564
58930
|
id: _enum([
|
|
@@ -58610,11 +58976,6 @@ var HeroSceneSchema = object({
|
|
|
58610
58976
|
});
|
|
58611
58977
|
DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
|
|
58612
58978
|
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;
|
|
58613
|
-
/** A string in both languages, as the cap vocabulary writes it (D920): no `t()` in the model. */
|
|
58614
|
-
var HeroTextSchema = object({
|
|
58615
|
-
en: string(),
|
|
58616
|
-
it: string()
|
|
58617
|
-
});
|
|
58618
58979
|
var HeroRowIdSchema = _enum([
|
|
58619
58980
|
"progress",
|
|
58620
58981
|
"run-for",
|
|
@@ -58720,6 +59081,25 @@ var HeroControlSchema = discriminatedUnion("kind", [
|
|
|
58720
59081
|
optionLabels: record(string(), HeroTextSchema).optional(),
|
|
58721
59082
|
call: HeroCallSchema,
|
|
58722
59083
|
valueArg: string()
|
|
59084
|
+
}),
|
|
59085
|
+
object({
|
|
59086
|
+
kind: literal("number"),
|
|
59087
|
+
id: string(),
|
|
59088
|
+
label: HeroTextSchema,
|
|
59089
|
+
value: number$1().nullable(),
|
|
59090
|
+
/** `null` = any number; the device rounds what it does not accept. */
|
|
59091
|
+
step: number$1().nullable(),
|
|
59092
|
+
unit: string(),
|
|
59093
|
+
call: HeroCallSchema,
|
|
59094
|
+
valueArg: string()
|
|
59095
|
+
}),
|
|
59096
|
+
object({
|
|
59097
|
+
kind: literal("text"),
|
|
59098
|
+
id: string(),
|
|
59099
|
+
label: HeroTextSchema,
|
|
59100
|
+
value: string().nullable(),
|
|
59101
|
+
call: HeroCallSchema,
|
|
59102
|
+
valueArg: string()
|
|
58723
59103
|
})
|
|
58724
59104
|
]);
|
|
58725
59105
|
/** Declared by its interface: a `z.infer` of one more icon union pushes the cap's inferred type past what the compiler can print. */
|
|
@@ -58921,6 +59301,11 @@ var HeroModelSchema = object({
|
|
|
58921
59301
|
timedRun: HeroTimedRunSlotSchema.nullable(),
|
|
58922
59302
|
powerSupply: HeroPowerSupplySlotSchema.nullable(),
|
|
58923
59303
|
rows: array(HeroRowSchema),
|
|
59304
|
+
/**
|
|
59305
|
+
* The ONE measurement of a simple sensor, drawn once inside the scene in place of
|
|
59306
|
+
* the caption, tiles and value chips. Absent = the card draws as before.
|
|
59307
|
+
*/
|
|
59308
|
+
measurement: HeroMeasurementSchema.optional(),
|
|
58924
59309
|
/** Ids of parts that actuate the device, hidden from a session that may not act on it. */
|
|
58925
59310
|
gated: array(string()),
|
|
58926
59311
|
provenance: array(HeroProvenanceSchema)
|
|
@@ -58992,8 +59377,23 @@ var HeroRowModelSchema = object({
|
|
|
58992
59377
|
* the call. Absent or empty = `primary` alone.
|
|
58993
59378
|
*/
|
|
58994
59379
|
extras: array(HeroControlSchema).max(2).optional(),
|
|
58995
|
-
trailing: HeroFactSchema.nullable()
|
|
59380
|
+
trailing: HeroFactSchema.nullable(),
|
|
59381
|
+
/**
|
|
59382
|
+
* The simple sensor's one reading (the hero's `measurement`): the row draws its value
|
|
59383
|
+
* at the right, the unit small, the quantity's icon in the mini-scene. Absent = an
|
|
59384
|
+
* older server or a device that is not a simple sensor: draw `trailing`.
|
|
59385
|
+
*/
|
|
59386
|
+
measurement: HeroMeasurementSchema.optional()
|
|
58996
59387
|
});
|
|
59388
|
+
new Set([
|
|
59389
|
+
"temperature",
|
|
59390
|
+
"humidity",
|
|
59391
|
+
"pressure",
|
|
59392
|
+
"illuminance",
|
|
59393
|
+
"reading",
|
|
59394
|
+
"air-quality",
|
|
59395
|
+
"valve"
|
|
59396
|
+
]);
|
|
58997
59397
|
DeviceType.Climate;
|
|
58998
59398
|
DeviceType.Climate, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer;
|
|
58999
59399
|
/**
|
|
@@ -60082,13 +60482,32 @@ var IrSendResultSchema = object({
|
|
|
60082
60482
|
var IrTransmitterInfoSchema = object({
|
|
60083
60483
|
media: array(IrMediumSchema),
|
|
60084
60484
|
settable: array(TransmitSettingKeySchema),
|
|
60085
|
-
maxTimings: number$1().int().positive()
|
|
60485
|
+
maxTimings: number$1().int().positive(),
|
|
60486
|
+
/** The transmitter can learn a code (`learn`). */
|
|
60487
|
+
learn: boolean().default(false),
|
|
60488
|
+
/** Native code formats the transmitter replays verbatim (`send.native`). */
|
|
60489
|
+
formats: array(IrNativeFormatSchema).default([])
|
|
60490
|
+
});
|
|
60491
|
+
var IR_LEARN_TIMEOUT_DEFAULT_MS = 2e4;
|
|
60492
|
+
var IR_LEARN_TIMEOUT_MAX_MS = 6e4;
|
|
60493
|
+
var IrLearnResultSchema = object({
|
|
60494
|
+
/** The code in the device's own format, when it reports one. */
|
|
60495
|
+
native: IrNativeCodeSchema.nullable(),
|
|
60496
|
+
timings: IrTimingsSchema,
|
|
60497
|
+
carrierHz: number$1().int().positive().nullable()
|
|
60086
60498
|
});
|
|
60087
60499
|
DeviceType.Generic, method(object({
|
|
60088
60500
|
deviceId: number$1().int().nonnegative(),
|
|
60089
|
-
timings: IrTimingsSchema,
|
|
60501
|
+
timings: IrTimingsSchema.optional(),
|
|
60502
|
+
native: IrNativeCodeSchema.optional(),
|
|
60090
60503
|
settings: TransmitSettingsSchema.optional()
|
|
60091
|
-
}), IrSendResultSchema, {
|
|
60504
|
+
}).refine((input) => input.timings === void 0 !== (input.native === void 0), { message: "send takes exactly one of timings or native" }), IrSendResultSchema, {
|
|
60505
|
+
kind: "mutation",
|
|
60506
|
+
auth: "protected"
|
|
60507
|
+
}), method(object({
|
|
60508
|
+
deviceId: number$1().int().nonnegative(),
|
|
60509
|
+
timeoutMs: number$1().int().positive().max(IR_LEARN_TIMEOUT_MAX_MS).default(IR_LEARN_TIMEOUT_DEFAULT_MS)
|
|
60510
|
+
}), IrLearnResultSchema, {
|
|
60092
60511
|
kind: "mutation",
|
|
60093
60512
|
auth: "protected"
|
|
60094
60513
|
}), method(object({ deviceId: number$1().int().nonnegative() }), IrTransmitterInfoSchema, {
|
|
@@ -69874,6 +70293,7 @@ var NcSystemEventKindSchema = _enum([
|
|
|
69874
70293
|
"recording-degraded",
|
|
69875
70294
|
"device-attention-required",
|
|
69876
70295
|
"device-attention-resolved",
|
|
70296
|
+
"device-command",
|
|
69877
70297
|
"storage-location-low",
|
|
69878
70298
|
"storage-location-critical",
|
|
69879
70299
|
"storage-location-ok",
|
|
@@ -69936,11 +70356,6 @@ var NC_ALARM_SYSTEM_EVENT_KINDS = [
|
|
|
69936
70356
|
"alarm-sensor-bypass",
|
|
69937
70357
|
"alarm-disarmed"
|
|
69938
70358
|
];
|
|
69939
|
-
/**
|
|
69940
|
-
* One coherent system-event condition. `kinds` is the required opt-in safety
|
|
69941
|
-
* gate; the remaining lists are optional narrowing filters relevant to the
|
|
69942
|
-
* selected kinds.
|
|
69943
|
-
*/
|
|
69944
70359
|
var NcSystemEventConditionSchema = object({
|
|
69945
70360
|
kinds: array(NcSystemEventKindSchema).min(1),
|
|
69946
70361
|
deviceIds: array(number$1().int()).min(1).optional(),
|
|
@@ -69972,6 +70387,28 @@ var NcSystemEventConditionSchema = object({
|
|
|
69972
70387
|
nodeIds: array(string().min(1)).min(1).optional(),
|
|
69973
70388
|
packageNames: array(string().min(1)).min(1).optional(),
|
|
69974
70389
|
/**
|
|
70390
|
+
* `device-command` only: the vocabulary ACTION IDS (D983, `powerSource.forceRestart`)
|
|
70391
|
+
* this rule speaks about - the same dictionary the automation action blocks
|
|
70392
|
+
* pick from. The hub refuses an id the action index does not know.
|
|
70393
|
+
*/
|
|
70394
|
+
commandActions: array(string().min(1)).min(1).optional(),
|
|
70395
|
+
/** `device-command` only: how the command settled. A device's own change is not a command. */
|
|
70396
|
+
commandOutcomes: array(_enum([
|
|
70397
|
+
"confirmed",
|
|
70398
|
+
"already",
|
|
70399
|
+
"unconfirmed",
|
|
70400
|
+
"failed"
|
|
70401
|
+
])).min(1).optional(),
|
|
70402
|
+
/** `device-command` only: who asked (`user`, `addon`, `action`, `scheduler`, `composition`, `node`). */
|
|
70403
|
+
commandActorKinds: array(_enum([
|
|
70404
|
+
"user",
|
|
70405
|
+
"addon",
|
|
70406
|
+
"action",
|
|
70407
|
+
"scheduler",
|
|
70408
|
+
"composition",
|
|
70409
|
+
"node"
|
|
70410
|
+
])).min(1).optional(),
|
|
70411
|
+
/**
|
|
69975
70412
|
* At or below this remaining-life %, a consumable is LOW for THIS rule
|
|
69976
70413
|
* (D642). Absent = {@link NC_CONSUMABLE_LOW_PERCENT_DEFAULT}; at most
|
|
69977
70414
|
* {@link NC_CONSUMABLE_LOW_PERCENT_MAX}, so every episode can recover. Scoped to the
|
|
@@ -70952,6 +71389,13 @@ var NcRuleInputSchema = object({
|
|
|
70952
71389
|
*/
|
|
70953
71390
|
groupingSec: number$1().int().min(5).max(60).optional(),
|
|
70954
71391
|
/**
|
|
71392
|
+
* Hold deliveries of this rule to one target and, when two or more arrive
|
|
71393
|
+
* before the window ends, send them as one summary. Absent = the kind's
|
|
71394
|
+
* default (`groupWithinSecOf`): 5 s for a system-event rule, 0 for every
|
|
71395
|
+
* other kind. `0` sends each delivery as it does today.
|
|
71396
|
+
*/
|
|
71397
|
+
groupWithinSec: number$1().int().min(0).max(300).optional(),
|
|
71398
|
+
/**
|
|
70955
71399
|
* ASK ME TO CONFIRM DETECTIONS (#243): a DETECTION notification of this rule
|
|
70956
71400
|
* carries "Correct" / "Wrong" buttons (and, where the channel has room, a
|
|
70957
71401
|
* "Wrong, it is a <class>" choice). A tap marks the track for retraining, the
|
|
@@ -71388,7 +71832,12 @@ var NcHistoryEntrySchema = object({
|
|
|
71388
71832
|
* Assistant export) must render as "no image right now", never as a
|
|
71389
71833
|
* broken link.
|
|
71390
71834
|
*/
|
|
71391
|
-
artifactIds: array(string().min(1)).optional()
|
|
71835
|
+
artifactIds: array(string().min(1)).optional(),
|
|
71836
|
+
/**
|
|
71837
|
+
* When this row was folded into a group-window summary, the outbox id of
|
|
71838
|
+
* that one notification. Absent on a row that was sent as itself.
|
|
71839
|
+
*/
|
|
71840
|
+
groupedInto: string().min(1).optional()
|
|
71392
71841
|
});
|
|
71393
71842
|
/**
|
|
71394
71843
|
* Query filter for `getHistory` (spec §4.2). Every field is a narrowing
|
|
@@ -77461,10 +77910,15 @@ getStatus: method(object({ deviceId: number$1() }), RecordingSignalStatusSchema)
|
|
|
77461
77910
|
*/
|
|
77462
77911
|
history: "none"
|
|
77463
77912
|
};
|
|
77464
|
-
var RemoteBindingSchema = object({ action:
|
|
77465
|
-
|
|
77466
|
-
|
|
77467
|
-
|
|
77913
|
+
var RemoteBindingSchema = object({ action: object({
|
|
77914
|
+
kind: literal("cap"),
|
|
77915
|
+
deviceId: number$1().int().nonnegative(),
|
|
77916
|
+
cap: string().min(1),
|
|
77917
|
+
method: string().min(1),
|
|
77918
|
+
args: record(string(), unknown()).optional(),
|
|
77919
|
+
/** The unit the person picked per arg (display only); `args` always hold the cap's own unit. */
|
|
77920
|
+
argUnits: record(string(), string().min(1)).optional()
|
|
77921
|
+
}) });
|
|
77468
77922
|
var RemoteCustomButtonSchema = object({
|
|
77469
77923
|
/** The slug after `custom:` in the role. */
|
|
77470
77924
|
key: string().refine(isRemoteCustomKey, { message: "a lowercase slug" }),
|
|
@@ -78454,7 +78908,7 @@ var sceneMonitorCapability = {
|
|
|
78454
78908
|
* instead of failing silently weeks later.
|
|
78455
78909
|
*
|
|
78456
78910
|
* Reachable from three surfaces on this one mutation: the scene card, a
|
|
78457
|
-
* notification button (an `onTrigger` sequence with a `kind:'
|
|
78911
|
+
* notification button (an `onTrigger` sequence with a `kind:'command'` step —
|
|
78458
78912
|
* no new Notification-Center code at all), and tRPC for scripts.
|
|
78459
78913
|
*/
|
|
78460
78914
|
resetScene: method(object({
|
|
@@ -85447,6 +85901,13 @@ function contribution(cap, schema, build) {
|
|
|
85447
85901
|
}
|
|
85448
85902
|
};
|
|
85449
85903
|
}
|
|
85904
|
+
/** The fragment, carrying the cap's single measurement when it has one. */
|
|
85905
|
+
function measured(fragment, measurement) {
|
|
85906
|
+
return measurement === void 0 ? fragment : {
|
|
85907
|
+
...fragment,
|
|
85908
|
+
measurement
|
|
85909
|
+
};
|
|
85910
|
+
}
|
|
85450
85911
|
function action(id, label, icon, cap, method, args = {}, emphasis = "normal", confirm = null) {
|
|
85451
85912
|
return {
|
|
85452
85913
|
id,
|
|
@@ -86721,10 +87182,26 @@ var controlContribution = contribution("control", ControlStatusSchema, (slice, c
|
|
|
86721
87182
|
};
|
|
86722
87183
|
});
|
|
86723
87184
|
var CONTROL_LABEL = t("Value", "Valore");
|
|
86724
|
-
/**
|
|
87185
|
+
/**
|
|
87186
|
+
* A number with known bounds is a slider, one without a number field, a choice a picker,
|
|
87187
|
+
* free text a text field; a date stays read-only.
|
|
87188
|
+
*/
|
|
86725
87189
|
function controlRows(slice) {
|
|
86726
87190
|
if (slice.kind === "numeric" && typeof slice.value === "number") {
|
|
86727
|
-
if (slice.min === void 0 || slice.max === void 0) return [
|
|
87191
|
+
if (slice.min === void 0 || slice.max === void 0) return [row$1("control", "sliders-horizontal", null, [{
|
|
87192
|
+
kind: "number",
|
|
87193
|
+
id: "control.value",
|
|
87194
|
+
label: CONTROL_LABEL,
|
|
87195
|
+
value: slice.value,
|
|
87196
|
+
step: slice.step ?? null,
|
|
87197
|
+
unit: "",
|
|
87198
|
+
call: {
|
|
87199
|
+
cap: "control",
|
|
87200
|
+
method: "setValue",
|
|
87201
|
+
args: { control: { kind: "numeric" } }
|
|
87202
|
+
},
|
|
87203
|
+
valueArg: "control.value"
|
|
87204
|
+
}])];
|
|
86728
87205
|
return [row$1("control", "sliders-horizontal", null, [{
|
|
86729
87206
|
kind: "slider",
|
|
86730
87207
|
id: "control.value",
|
|
@@ -86755,6 +87232,18 @@ function controlRows(slice) {
|
|
|
86755
87232
|
},
|
|
86756
87233
|
valueArg: "control.value"
|
|
86757
87234
|
}])];
|
|
87235
|
+
if (slice.kind === "text") return [row$1("control", "sliders-horizontal", null, [{
|
|
87236
|
+
kind: "text",
|
|
87237
|
+
id: "control.value",
|
|
87238
|
+
label: CONTROL_LABEL,
|
|
87239
|
+
value: typeof slice.value === "string" ? slice.value : null,
|
|
87240
|
+
call: {
|
|
87241
|
+
cap: "control",
|
|
87242
|
+
method: "setValue",
|
|
87243
|
+
args: { control: { kind: "text" } }
|
|
87244
|
+
},
|
|
87245
|
+
valueArg: "control.value"
|
|
87246
|
+
}])];
|
|
86758
87247
|
return [];
|
|
86759
87248
|
}
|
|
86760
87249
|
var SENSORS_FAMILY_CONTRIBUTIONS = {
|
|
@@ -87110,15 +87599,15 @@ function sensorFragment(value, id, icon, en, it, text) {
|
|
|
87110
87599
|
tiles: { right: [tile$1(en, it, text)] }
|
|
87111
87600
|
};
|
|
87112
87601
|
}
|
|
87113
|
-
var temperatureContribution = contribution("temperature-sensor", TemperatureSensorStatusSchema, (slice) => sensorFragment(slice?.temperature ?? null, "temperature", "thermometer", "Temperature", "Temperatura", formatDegrees(slice?.temperature ?? 0)));
|
|
87114
|
-
var humidityContribution = contribution("humidity-sensor", HumiditySensorStatusSchema, (slice) => sensorFragment(slice?.humidity ?? null, "humidity", "droplets", "Humidity", "Umidità", `${Math.round(slice?.humidity ?? 0)}%`));
|
|
87602
|
+
var temperatureContribution = contribution("temperature-sensor", TemperatureSensorStatusSchema, (slice) => measured(sensorFragment(slice?.temperature ?? null, "temperature", "thermometer", "Temperature", "Temperatura", formatDegrees(slice?.temperature ?? 0)), slice?.temperature == null ? void 0 : temperatureMeasurement(slice.temperature, slice.lastFetchedAt)));
|
|
87603
|
+
var humidityContribution = contribution("humidity-sensor", HumiditySensorStatusSchema, (slice) => measured(sensorFragment(slice?.humidity ?? null, "humidity", "droplets", "Humidity", "Umidità", `${Math.round(slice?.humidity ?? 0)}%`), slice?.humidity == null ? void 0 : humidityMeasurement(slice.humidity, slice.lastFetchedAt)));
|
|
87115
87604
|
var pressureContribution = contribution("pressure-sensor", PressureSensorStatusSchema, (slice) => sensorFragment(slice?.pressure ?? null, "pressure", "gauge", "Pressure", "Pressione", `${Math.round(slice?.pressure ?? 0)} hPa`));
|
|
87116
87605
|
var illuminanceContribution = contribution("ambient-light-sensor", AmbientLightSensorStatusSchema, (slice) => sensorFragment(slice?.illuminance ?? null, "illuminance", "sun", "Light", "Luce", `${Math.round(slice?.illuminance ?? 0)} lx`));
|
|
87117
87606
|
var numericContribution = contribution("numeric-sensor", NumericSensorStatusSchema, (slice) => sensorFragment(slice?.value ?? null, "reading", "gauge", "Value", "Valore", String(Number((slice?.value ?? 0).toFixed(2)))));
|
|
87118
87607
|
var enumContribution = contribution("enum-sensor", EnumSensorStatusSchema, (slice) => {
|
|
87119
87608
|
if (slice === void 0 || slice.value === "") return { reading: reading("sensor", "unknown") };
|
|
87120
87609
|
if (slice.timestamp === true) return { reading: reading("sensor", "on") };
|
|
87121
|
-
return sensorFragment(slice.value, "reading", "gauge", "State", "Stato", slice.value);
|
|
87610
|
+
return measured(sensorFragment(slice.value, "reading", "gauge", "State", "Stato", slice.value), textMeasurement(slice.value, slice.lastFetchedAt));
|
|
87122
87611
|
});
|
|
87123
87612
|
/** Index bands: the headline of an air-quality sensor is a word, not a number. */
|
|
87124
87613
|
function airQualityWord(aqi) {
|
|
@@ -90014,6 +90503,13 @@ Object.freeze({
|
|
|
90014
90503
|
access: "view",
|
|
90015
90504
|
role: "admin"
|
|
90016
90505
|
},
|
|
90506
|
+
"deviceExport.setExposedName": {
|
|
90507
|
+
capName: "device-export",
|
|
90508
|
+
capScope: "system",
|
|
90509
|
+
addonId: null,
|
|
90510
|
+
access: "create",
|
|
90511
|
+
role: "admin"
|
|
90512
|
+
},
|
|
90017
90513
|
"deviceExport.unexposeDevice": {
|
|
90018
90514
|
capName: "device-export",
|
|
90019
90515
|
capScope: "system",
|
|
@@ -91953,6 +92449,13 @@ Object.freeze({
|
|
|
91953
92449
|
access: "view",
|
|
91954
92450
|
role: "viewer"
|
|
91955
92451
|
},
|
|
92452
|
+
"irTransmitter.learn": {
|
|
92453
|
+
capName: "ir-transmitter",
|
|
92454
|
+
capScope: "device",
|
|
92455
|
+
addonId: null,
|
|
92456
|
+
access: "create",
|
|
92457
|
+
role: "viewer"
|
|
92458
|
+
},
|
|
91956
92459
|
"irTransmitter.send": {
|
|
91957
92460
|
capName: "ir-transmitter",
|
|
91958
92461
|
capScope: "device",
|
|
@@ -98471,48 +98974,6 @@ Object.freeze({
|
|
|
98471
98974
|
role: "admin"
|
|
98472
98975
|
}
|
|
98473
98976
|
});
|
|
98474
|
-
/**
|
|
98475
|
-
* "Do" actions an operator can pick for a device — presentation-free.
|
|
98476
|
-
*
|
|
98477
|
-
* The wire shape stays `{ cap, method, args }`. Since D920 this module no
|
|
98478
|
-
* longer HOLDS a catalogue: {@link DEVICE_DO_ACTIONS} is a projection of the
|
|
98479
|
-
* cap vocabulary (`cap-vocabulary/`), the one cap-keyed map of every action
|
|
98480
|
-
* and trigger — so a picker built on this module and one built on
|
|
98481
|
-
* `actionsForDevice` (the vocabulary resolver) can never disagree about what
|
|
98482
|
-
* a device can do. Ids, baked args and the matching of saved steps are the
|
|
98483
|
-
* vocabulary's; labels here are its English labels.
|
|
98484
|
-
*
|
|
98485
|
-
* Shared by the admin NC Sequences / rule Do editor and Automations; the
|
|
98486
|
-
* viewer can reuse the same catalogue once its step editor catches up.
|
|
98487
|
-
*/
|
|
98488
|
-
function openArgKind(offer) {
|
|
98489
|
-
if (offer.kind === "number") return "number";
|
|
98490
|
-
if (offer.kind === "dynamic" && offer.valueKind === "number") return "number";
|
|
98491
|
-
if (offer.kind === "enum" && offer.values.every((v) => typeof v.value === "number")) return "number";
|
|
98492
|
-
return "string";
|
|
98493
|
-
}
|
|
98494
|
-
function projectOpenArg(arg) {
|
|
98495
|
-
return {
|
|
98496
|
-
key: arg.key,
|
|
98497
|
-
label: arg.label.en,
|
|
98498
|
-
kind: openArgKind(arg.offer),
|
|
98499
|
-
offer: arg.offer,
|
|
98500
|
-
...arg.optional === true ? { optional: true } : {}
|
|
98501
|
-
};
|
|
98502
|
-
}
|
|
98503
|
-
function projectAction(cap, action) {
|
|
98504
|
-
const open = action.openArgs ?? [];
|
|
98505
|
-
return {
|
|
98506
|
-
id: action.id,
|
|
98507
|
-
label: action.label.en,
|
|
98508
|
-
cap,
|
|
98509
|
-
method: action.method,
|
|
98510
|
-
args: action.args ?? {},
|
|
98511
|
-
...open.length > 0 ? { openArgs: open.map(projectOpenArg) } : {}
|
|
98512
|
-
};
|
|
98513
|
-
}
|
|
98514
|
-
Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
|
|
98515
|
-
DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.HeatPump, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
|
|
98516
98977
|
DeviceFeature.BatteryOperated;
|
|
98517
98978
|
function bound(cap, methods = [], authority = "reported") {
|
|
98518
98979
|
return {
|
|
@@ -99804,6 +100265,11 @@ Object.freeze({
|
|
|
99804
100265
|
form: "single",
|
|
99805
100266
|
optional: false
|
|
99806
100267
|
}],
|
|
100268
|
+
"irTransmitter.learn": [{
|
|
100269
|
+
name: "deviceId",
|
|
100270
|
+
form: "single",
|
|
100271
|
+
optional: false
|
|
100272
|
+
}],
|
|
99807
100273
|
"irTransmitter.send": [{
|
|
99808
100274
|
name: "deviceId",
|
|
99809
100275
|
form: "single",
|
|
@@ -101743,7 +102209,10 @@ var NC_SYSTEM_EVENT_FILTER_KEYS = [
|
|
|
101743
102209
|
"deviceTypes",
|
|
101744
102210
|
"nodeIds",
|
|
101745
102211
|
"packageNames",
|
|
101746
|
-
"consumableLowPercent"
|
|
102212
|
+
"consumableLowPercent",
|
|
102213
|
+
"commandActions",
|
|
102214
|
+
"commandOutcomes",
|
|
102215
|
+
"commandActorKinds"
|
|
101747
102216
|
];
|
|
101748
102217
|
new Map(Object.entries({
|
|
101749
102218
|
"device-online": "device-events",
|
|
@@ -101766,6 +102235,7 @@ new Map(Object.entries({
|
|
|
101766
102235
|
"recording-degraded": "device-events",
|
|
101767
102236
|
"device-attention-required": "device-events",
|
|
101768
102237
|
"device-attention-resolved": "device-events",
|
|
102238
|
+
"device-command": "device-events",
|
|
101769
102239
|
"node-online": "system",
|
|
101770
102240
|
"node-offline": "system",
|
|
101771
102241
|
"node-restarting": "system",
|
|
@@ -101866,6 +102336,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
|
|
|
101866
102336
|
label: "Needs attention",
|
|
101867
102337
|
kinds: ["device-attention-required", "device-attention-resolved"]
|
|
101868
102338
|
},
|
|
102339
|
+
{
|
|
102340
|
+
id: "device-command",
|
|
102341
|
+
family: "device-events",
|
|
102342
|
+
label: "Commands given to devices",
|
|
102343
|
+
kinds: ["device-command"]
|
|
102344
|
+
},
|
|
101869
102345
|
{
|
|
101870
102346
|
id: "nodes",
|
|
101871
102347
|
family: "system",
|
|
@@ -101943,6 +102419,15 @@ function systemEventKindsOfFamily(family) {
|
|
|
101943
102419
|
return NC_SYSTEM_EVENT_KIND_GROUP_SPECS.filter((g) => g.family === family).flatMap((g) => g.kinds);
|
|
101944
102420
|
}
|
|
101945
102421
|
NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
|
|
102422
|
+
new Set([
|
|
102423
|
+
"alarm.trigger",
|
|
102424
|
+
"alarm.disarm",
|
|
102425
|
+
"alarm.arm.home",
|
|
102426
|
+
"alarm.arm.away",
|
|
102427
|
+
"alarm.arm.night",
|
|
102428
|
+
"alarm.arm.vacation",
|
|
102429
|
+
"alarm.arm.custom_bypass"
|
|
102430
|
+
]);
|
|
101946
102431
|
/**
|
|
101947
102432
|
* WHICH WAY A BOOLEAN SENSOR MOVED — read off the one slice a sensor event
|
|
101948
102433
|
* persists (D640 "a sensor rule fires on the raise, the clear, or both").
|
|
@@ -102138,6 +102623,7 @@ var DEVICE_EVENTS = [
|
|
|
102138
102623
|
"recording-healthy",
|
|
102139
102624
|
"recording-degraded",
|
|
102140
102625
|
...DEVICE_ATTENTION,
|
|
102626
|
+
"device-command",
|
|
102141
102627
|
...ALARM
|
|
102142
102628
|
];
|
|
102143
102629
|
/** Every kind a summary may speak about — the two summarisable families. */
|
|
@@ -102291,6 +102777,9 @@ var STATE_SUMMARY_VARS = [
|
|
|
102291
102777
|
[...RULE_BODY_ONLY],
|
|
102292
102778
|
[...RULE_TEXT],
|
|
102293
102779
|
([...RULE_TEXT], [...DEVICE_EVENTS]),
|
|
102780
|
+
[...RULE_TEXT],
|
|
102781
|
+
[...RULE_TEXT],
|
|
102782
|
+
([...RULE_TEXT], [...DEVICE_EVENTS]),
|
|
102294
102783
|
([...RULE_TEXT], [...COMBINED_ALARM]),
|
|
102295
102784
|
([...RULE_TEXT], [...COMBINED_ALARM]),
|
|
102296
102785
|
([...RULE_TEXT], [...DEVICE_EVENTS]),
|
|
@@ -103624,7 +104113,7 @@ function findSpec(id) {
|
|
|
103624
104113
|
function capsOfSpec(spec) {
|
|
103625
104114
|
switch (spec.kind) {
|
|
103626
104115
|
case "power-meter": return ["power-meter"];
|
|
103627
|
-
case "battery": return ["
|
|
104116
|
+
case "battery": return ["numeric-sensor", "power-meter"];
|
|
103628
104117
|
case "meter": return ["meter"];
|
|
103629
104118
|
case "numeric": return ["numeric-sensor"];
|
|
103630
104119
|
case "text": return ["enum-sensor"];
|
|
@@ -103727,11 +104216,9 @@ function computeSlices(spec, snapshot, options, now) {
|
|
|
103727
104216
|
const v = numberOf(voltage);
|
|
103728
104217
|
const a = numberOf(current);
|
|
103729
104218
|
return {
|
|
103730
|
-
|
|
103731
|
-
|
|
103732
|
-
|
|
103733
|
-
sleeping: false,
|
|
103734
|
-
lastUpdated: now
|
|
104219
|
+
numeric: {
|
|
104220
|
+
value: nullableOf(soc),
|
|
104221
|
+
lastFetchedAt: now
|
|
103735
104222
|
},
|
|
103736
104223
|
...w !== void 0 || v !== void 0 || a !== void 0 ? { powerMeter: {
|
|
103737
104224
|
...w !== void 0 ? { power: w * sign || 0 } : {},
|
|
@@ -103877,12 +104364,6 @@ var METER_CAP = meterCapability.name;
|
|
|
103877
104364
|
var NUMERIC_CAP = numericSensorCapability.name;
|
|
103878
104365
|
var ENUM_CAP = enumSensorCapability.name;
|
|
103879
104366
|
var POWER_COLD = { lastFetchedAt: 0 };
|
|
103880
|
-
var BATTERY_COLD = {
|
|
103881
|
-
level: null,
|
|
103882
|
-
charging: "unknown",
|
|
103883
|
-
sleeping: false,
|
|
103884
|
-
lastUpdated: 0
|
|
103885
|
-
};
|
|
103886
104367
|
var VALUE_COLD = {
|
|
103887
104368
|
value: null,
|
|
103888
104369
|
lastFetchedAt: 0
|
|
@@ -103930,6 +104411,7 @@ var EnpalSensorDevice = class extends BaseDevice {
|
|
|
103930
104411
|
id: `${this.integrationId}:${spec.id}`,
|
|
103931
104412
|
system: ENPAL_SYSTEM,
|
|
103932
104413
|
...spec.kind === "meter" || spec.kind === "numeric" ? { quantity: spec.quantity } : {},
|
|
104414
|
+
...spec.kind === "battery" ? { quantity: spec.level.quantity } : {},
|
|
103933
104415
|
...spec.kind === "text" && spec.timestamp === true ? { dateTimeShape: "date" } : {}
|
|
103934
104416
|
});
|
|
103935
104417
|
}
|
|
@@ -103979,7 +104461,6 @@ var EnpalSensorDevice = class extends BaseDevice {
|
|
|
103979
104461
|
energy: this.energyGuard.accept(kwh)
|
|
103980
104462
|
});
|
|
103981
104463
|
}
|
|
103982
|
-
if (slices.battery !== void 0) this.runtimeState.setCapState(BATTERY_CAP, slices.battery);
|
|
103983
104464
|
if (slices.meter !== void 0) {
|
|
103984
104465
|
const raw = slices.meter.value;
|
|
103985
104466
|
this.runtimeState.setCapState(METER_CAP, {
|
|
@@ -103996,7 +104477,8 @@ var EnpalSensorDevice = class extends BaseDevice {
|
|
|
103996
104477
|
this.registerPowerMeter();
|
|
103997
104478
|
return;
|
|
103998
104479
|
case "battery":
|
|
103999
|
-
this.
|
|
104480
|
+
this.retractBatteryCap();
|
|
104481
|
+
this.registerNumeric();
|
|
104000
104482
|
this.registerPowerMeter();
|
|
104001
104483
|
return;
|
|
104002
104484
|
case "meter":
|
|
@@ -104008,8 +104490,7 @@ var EnpalSensorDevice = class extends BaseDevice {
|
|
|
104008
104490
|
this.runtimeState.setCapState(ENUM_CAP, TEXT_COLD);
|
|
104009
104491
|
return;
|
|
104010
104492
|
case "numeric":
|
|
104011
|
-
this.
|
|
104012
|
-
this.runtimeState.setCapState(NUMERIC_CAP, VALUE_COLD);
|
|
104493
|
+
this.registerNumeric();
|
|
104013
104494
|
return;
|
|
104014
104495
|
}
|
|
104015
104496
|
}
|
|
@@ -104017,15 +104498,18 @@ var EnpalSensorDevice = class extends BaseDevice {
|
|
|
104017
104498
|
this.ctx.registerNativeCap(powerMeterCapability, { getStatus: async () => this.runtimeState.getCapState(POWER_CAP) ?? POWER_COLD });
|
|
104018
104499
|
this.runtimeState.setCapState(POWER_CAP, POWER_COLD);
|
|
104019
104500
|
}
|
|
104020
|
-
|
|
104021
|
-
this.ctx.registerNativeCap(
|
|
104022
|
-
|
|
104023
|
-
|
|
104024
|
-
|
|
104025
|
-
|
|
104026
|
-
|
|
104027
|
-
|
|
104028
|
-
|
|
104501
|
+
registerNumeric() {
|
|
104502
|
+
this.ctx.registerNativeCap(numericSensorCapability, { getStatus: async () => this.runtimeState.getCapState(NUMERIC_CAP) ?? VALUE_COLD });
|
|
104503
|
+
this.runtimeState.setCapState(NUMERIC_CAP, VALUE_COLD);
|
|
104504
|
+
}
|
|
104505
|
+
/**
|
|
104506
|
+
* The storage battery once registered the `battery` cap (#522). This process never
|
|
104507
|
+
* registers it, so the call retracts the binding a previous incarnation left on the hub
|
|
104508
|
+
* (`unregisterNativeCap`'s stale-retract path): a restored device stops answering
|
|
104509
|
+
* `battery` and no `battery` slice is written again.
|
|
104510
|
+
*/
|
|
104511
|
+
retractBatteryCap() {
|
|
104512
|
+
this.ctx.unregisterNativeCap?.(BATTERY_CAP);
|
|
104029
104513
|
}
|
|
104030
104514
|
};
|
|
104031
104515
|
//#endregion
|
|
@@ -105373,7 +105857,39 @@ var enpalConnectionsEntryKind = defineEntryKind("connections", {
|
|
|
105373
105857
|
});
|
|
105374
105858
|
/** Every kind this addon declares, keyed by its name. */
|
|
105375
105859
|
var enpalRecordKinds = { connections: enpalConnectionsEntryKind };
|
|
105860
|
+
var REJECTED_KINDS = new Set([
|
|
105861
|
+
"unauthorized",
|
|
105862
|
+
"token-missing",
|
|
105863
|
+
"empty"
|
|
105864
|
+
]);
|
|
105865
|
+
function connectionForTest(values) {
|
|
105866
|
+
const host = values["host"];
|
|
105867
|
+
if (typeof host !== "string" || host.trim().length === 0) return null;
|
|
105868
|
+
const parsed = enpalConnectionSchema.safeParse({ ...values });
|
|
105869
|
+
return parsed.success ? parsed.data : null;
|
|
105870
|
+
}
|
|
105871
|
+
function testFailure(error) {
|
|
105872
|
+
if (error instanceof EnpalError) return {
|
|
105873
|
+
success: false,
|
|
105874
|
+
status: REJECTED_KINDS.has(error.kind) ? "rejected" : "inconclusive",
|
|
105875
|
+
error: error.message
|
|
105876
|
+
};
|
|
105877
|
+
return {
|
|
105878
|
+
success: false,
|
|
105879
|
+
status: "inconclusive",
|
|
105880
|
+
error: "The Enpal box did not answer"
|
|
105881
|
+
};
|
|
105882
|
+
}
|
|
105883
|
+
function liveProbe(blazor) {
|
|
105884
|
+
return (connection) => readEnpalSnapshot({
|
|
105885
|
+
connection,
|
|
105886
|
+
fetch: globalThis.fetch,
|
|
105887
|
+
now: () => Date.now(),
|
|
105888
|
+
blazor: blazor?.()
|
|
105889
|
+
});
|
|
105890
|
+
}
|
|
105376
105891
|
function buildEnpalInstances(records, deps) {
|
|
105892
|
+
const probe = deps.probe ?? liveProbe(deps.blazor);
|
|
105377
105893
|
return new RecordIntegrationInstances(records, enpalConnectionsEntryKind, {
|
|
105378
105894
|
description: {
|
|
105379
105895
|
role: "devices",
|
|
@@ -105383,18 +105899,36 @@ function buildEnpalInstances(records, deps) {
|
|
|
105383
105899
|
},
|
|
105384
105900
|
instanceMode: "multiple",
|
|
105385
105901
|
kind: "server",
|
|
105386
|
-
canTest:
|
|
105902
|
+
canTest: true,
|
|
105387
105903
|
kinds: [{
|
|
105388
105904
|
id: "connection",
|
|
105389
105905
|
label: "Enpal",
|
|
105390
105906
|
functionCap: "device-provider",
|
|
105391
|
-
role: "devices"
|
|
105907
|
+
role: "devices",
|
|
105908
|
+
canTest: true
|
|
105392
105909
|
}]
|
|
105393
105910
|
},
|
|
105394
105911
|
formSchema: () => buildConnectionFormSchema(),
|
|
105395
105912
|
initialBody: (name) => ({ name }),
|
|
105396
105913
|
configured: (body) => (body.host ?? "").length > 0,
|
|
105397
|
-
onChanged: () => deps.onChanged()
|
|
105914
|
+
onChanged: () => deps.onChanged(),
|
|
105915
|
+
test: async ({ values }) => {
|
|
105916
|
+
const connection = connectionForTest(values);
|
|
105917
|
+
if (connection === null) return {
|
|
105918
|
+
success: false,
|
|
105919
|
+
status: "rejected",
|
|
105920
|
+
error: "Host is required"
|
|
105921
|
+
};
|
|
105922
|
+
try {
|
|
105923
|
+
await probe(connection);
|
|
105924
|
+
return {
|
|
105925
|
+
success: true,
|
|
105926
|
+
status: "validated"
|
|
105927
|
+
};
|
|
105928
|
+
} catch (error) {
|
|
105929
|
+
return testFailure(error);
|
|
105930
|
+
}
|
|
105931
|
+
}
|
|
105398
105932
|
});
|
|
105399
105933
|
}
|
|
105400
105934
|
/**
|
|
@@ -105687,7 +106221,10 @@ var EnpalProviderAddon = class extends BaseDeviceProvider {
|
|
|
105687
106221
|
...rowsDepsOver(api),
|
|
105688
106222
|
kind: enpalConnectionsEntryKind
|
|
105689
106223
|
}));
|
|
105690
|
-
this.instances = buildEnpalInstances(records, {
|
|
106224
|
+
this.instances = buildEnpalInstances(records, {
|
|
106225
|
+
onChanged: () => this.reconcileIntegrations(),
|
|
106226
|
+
blazor: () => this.blazorReader ?? void 0
|
|
106227
|
+
});
|
|
105691
106228
|
this.blazorReader = await this.createBlazorBridge();
|
|
105692
106229
|
this.clients = new EnpalClientRegistry(this.ctx.logger, {
|
|
105693
106230
|
blazor: () => this.blazorReader ?? void 0,
|