@camstack/types 1.2.433 → 1.2.435
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/dist/addon.js +2 -2
- package/dist/addon.mjs +2 -2
- package/dist/automation/index.d.ts +3 -1
- package/dist/automation/recipe.d.ts +33 -3
- package/dist/automation/stationary-trigger.d.ts +48 -0
- package/dist/automation/trace.d.ts +4 -1
- package/dist/capabilities/actions.cap.d.ts +1 -0
- package/dist/capabilities/air-quality-sensor.cap.d.ts +8 -8
- package/dist/capabilities/ambient-light-sensor.cap.d.ts +4 -4
- package/dist/capabilities/battery.cap.d.ts +14 -14
- package/dist/capabilities/brightness.cap.d.ts +7 -7
- package/dist/capabilities/capability-definition.d.ts +3 -3
- package/dist/capabilities/color.cap.d.ts +6 -6
- package/dist/capabilities/core-blocks.cap.d.ts +156 -0
- package/dist/capabilities/device-history.cap.d.ts +2 -2
- package/dist/capabilities/dishwasher.cap.d.ts +11 -11
- package/dist/capabilities/fan-control.cap.d.ts +6 -6
- package/dist/capabilities/humidity-sensor.cap.d.ts +4 -4
- package/dist/capabilities/lawn-mower-control.cap.d.ts +3 -3
- package/dist/capabilities/media-player.cap.d.ts +8 -8
- package/dist/capabilities/meter.cap.d.ts +1 -1
- package/dist/capabilities/network-link.cap.d.ts +13 -13
- package/dist/capabilities/power-meter.cap.d.ts +22 -22
- package/dist/capabilities/presence.cap.d.ts +1 -15
- package/dist/capabilities/pressure-sensor.cap.d.ts +4 -4
- package/dist/capabilities/temperature-sensor.cap.d.ts +4 -4
- package/dist/capabilities/vacuum-control.cap.d.ts +3 -3
- package/dist/client.js +1 -1
- package/dist/client.mjs +1 -1
- package/dist/composition/composition-report.d.ts +1 -1
- package/dist/composition/examples.d.ts +1 -1
- package/dist/composition/helpers/appliance.d.ts +1 -1
- package/dist/{composition-CpAc0YfQ.js → composition-BZf_gqG7.js} +2 -2
- package/dist/{composition-Bb8iwHEJ.mjs → composition-CZImLpOI.mjs} +2 -2
- package/dist/device/base-device.d.ts +2 -2
- package/dist/device/device-runtime-state.d.ts +1 -1
- package/dist/device/device-state-handle.d.ts +1 -1
- package/dist/device/system-mirror.d.ts +1 -1
- package/dist/enums/event-category.d.ts +1 -1
- package/dist/enums.js +1 -1
- package/dist/enums.mjs +1 -1
- package/dist/{event-category-hdMR7CTW.mjs → event-category-DmZDPDf_.mjs} +1 -1
- package/dist/{event-category-BhzwWQCH.js → event-category-E17zaHjN.js} +1 -1
- package/dist/generated/addon-cap-facts.d.ts +1 -1
- package/dist/generated/customize-catalog.d.ts +15 -15
- package/dist/generated/history-catalog.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +673 -518
- package/dist/index.mjs +673 -519
- package/dist/interfaces/event-bus.d.ts +24 -3
- package/dist/node.js +1 -1
- package/dist/node.mjs +1 -1
- package/dist/{sleep-BHjNez61.mjs → sleep-Crlmu_tO.mjs} +27 -31
- package/dist/{sleep-5zRztaHa.js → sleep-DHitF_e4.js} +27 -31
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_sleep = require("./sleep-
|
|
3
|
-
const require_event_category = require("./event-category-
|
|
4
|
-
const require_composition = require("./composition-
|
|
2
|
+
const require_sleep = require("./sleep-DHitF_e4.js");
|
|
3
|
+
const require_event_category = require("./event-category-E17zaHjN.js");
|
|
4
|
+
const require_composition = require("./composition-BZf_gqG7.js");
|
|
5
5
|
const require_node_color = require("./node-color.js");
|
|
6
6
|
const require_err_msg = require("./err-msg-U7lLUMNz.js");
|
|
7
7
|
const require_enums = require("./enums.js");
|
|
@@ -8074,10 +8074,10 @@ var ExpressionSourceSchema = zod.z.object({
|
|
|
8074
8074
|
*
|
|
8075
8075
|
* Three vocabularies, each salvaged rather than invented:
|
|
8076
8076
|
*
|
|
8077
|
-
* - **Triggers** — the only genuinely new surface.
|
|
8077
|
+
* - **Triggers** — the only genuinely new surface. Seven members, none of which
|
|
8078
8078
|
* touches notification delivery. The NC keeps its four *pipeline* triggers;
|
|
8079
8079
|
* these are device/time triggers and the two sets do not overlap.
|
|
8080
|
-
* - **Conditions** — `ALL`/`ANY`/`NOT` over
|
|
8080
|
+
* - **Conditions** — `ALL`/`ANY`/`NOT` over three leaf kinds, bounded. The
|
|
8081
8081
|
* expression leaf is the bounded engine at `../expression/`, which is where
|
|
8082
8082
|
* arithmetic between two device fields lives and the only place it lives.
|
|
8083
8083
|
* - **Actions** — `NcRuleActionSchema`'s two steps, plus the `{kind:'code'}`
|
|
@@ -8124,6 +8124,14 @@ var AutomationConditionLeafSchema = zod.z.object({
|
|
|
8124
8124
|
zod.z.array(zod.z.union([zod.z.string(), zod.z.number()]))
|
|
8125
8125
|
])
|
|
8126
8126
|
});
|
|
8127
|
+
var AutomationConditionStationaryPresentSchema = zod.z.object({
|
|
8128
|
+
kind: zod.z.literal("stationary-present"),
|
|
8129
|
+
deviceId: zod.z.number().int().nonnegative(),
|
|
8130
|
+
/** Class names; a macro class matches every raw class that maps to it. */
|
|
8131
|
+
classes: zod.z.array(zod.z.string().min(1)).min(1).optional(),
|
|
8132
|
+
/** The parked object must be in at least one of these zones. */
|
|
8133
|
+
zones: zod.z.array(zod.z.string().min(1)).min(1).optional()
|
|
8134
|
+
});
|
|
8127
8135
|
/**
|
|
8128
8136
|
* The expression leaf, declared as a plain object rather than an intersection
|
|
8129
8137
|
* with {@link ExpressionSourceSchema}: a discriminated union has to be able to
|
|
@@ -8157,7 +8165,8 @@ var AutomationConditionSchema = zod.z.lazy(() => zod.z.discriminatedUnion("kind"
|
|
|
8157
8165
|
child: AutomationConditionSchema
|
|
8158
8166
|
}),
|
|
8159
8167
|
AutomationConditionLeafSchema,
|
|
8160
|
-
AutomationConditionExpressionSchema
|
|
8168
|
+
AutomationConditionExpressionSchema,
|
|
8169
|
+
AutomationConditionStationaryPresentSchema
|
|
8161
8170
|
]));
|
|
8162
8171
|
/**
|
|
8163
8172
|
* What starts a run.
|
|
@@ -8211,6 +8220,17 @@ var AutomationTriggerSchema = zod.z.discriminatedUnion("kind", [
|
|
|
8211
8220
|
/** Best-so-far confidence the track must reach. */
|
|
8212
8221
|
minScore: zod.z.number().min(0).max(1).optional()
|
|
8213
8222
|
}),
|
|
8223
|
+
zod.z.object({
|
|
8224
|
+
kind: zod.z.literal("stationary"),
|
|
8225
|
+
deviceIds: zod.z.array(zod.z.number().int().nonnegative()).min(1),
|
|
8226
|
+
/** Class names; a macro class matches like the detection trigger. */
|
|
8227
|
+
classes: zod.z.array(zod.z.string().min(1)).min(1).optional(),
|
|
8228
|
+
/** The parked object must be in at least one of these zones. */
|
|
8229
|
+
zones: zod.z.array(zod.z.string().min(1)).min(1).optional(),
|
|
8230
|
+
/** `appeared`: it became parked. `departed`: the registry retired it.
|
|
8231
|
+
* The vocabulary of `PipelineAnalyticsStationaryChanged`. */
|
|
8232
|
+
phase: zod.z.enum(["appeared", "departed"])
|
|
8233
|
+
}),
|
|
8214
8234
|
zod.z.object({
|
|
8215
8235
|
kind: zod.z.literal("interval"),
|
|
8216
8236
|
minutes: zod.z.number().int().min(1).max(1440)
|
|
@@ -8323,9 +8343,12 @@ var AutomationRecipeSchema = zod.z.object({
|
|
|
8323
8343
|
/** How many run traces the automation keeps (default {@link DEFAULT_TRACE_KEEP}). */
|
|
8324
8344
|
keepTraces: zod.z.number().int().min(1).max(100).optional()
|
|
8325
8345
|
});
|
|
8346
|
+
function isLeaf(cond) {
|
|
8347
|
+
return cond.kind === "condition" || cond.kind === "expression" || cond.kind === "stationary-present";
|
|
8348
|
+
}
|
|
8326
8349
|
/** Depth of a tree, root container = 1. A bare leaf is depth 1. */
|
|
8327
8350
|
function conditionDepth(cond) {
|
|
8328
|
-
if (cond
|
|
8351
|
+
if (isLeaf(cond)) return 1;
|
|
8329
8352
|
if (cond.kind === "not") return 1 + conditionDepth(cond.child);
|
|
8330
8353
|
const children = cond.children;
|
|
8331
8354
|
if (children.length === 0) return 1;
|
|
@@ -8338,14 +8361,14 @@ function conditionDepth(cond) {
|
|
|
8338
8361
|
}
|
|
8339
8362
|
/** Leaves only — containers do not count toward the leaf budget. */
|
|
8340
8363
|
function countConditionLeaves(cond) {
|
|
8341
|
-
if (cond
|
|
8364
|
+
if (isLeaf(cond)) return 1;
|
|
8342
8365
|
if (cond.kind === "not") return countConditionLeaves(cond.child);
|
|
8343
8366
|
let total = 0;
|
|
8344
8367
|
for (const child of cond.children) total += countConditionLeaves(child);
|
|
8345
8368
|
return total;
|
|
8346
8369
|
}
|
|
8347
8370
|
function findEmptyContainer(cond) {
|
|
8348
|
-
if (cond
|
|
8371
|
+
if (isLeaf(cond)) return false;
|
|
8349
8372
|
if (cond.kind === "not") return findEmptyContainer(cond.child);
|
|
8350
8373
|
if (cond.children.length === 0) return true;
|
|
8351
8374
|
return cond.children.some(findEmptyContainer);
|
|
@@ -8504,6 +8527,32 @@ function emitTrigger(trigger, index) {
|
|
|
8504
8527
|
},
|
|
8505
8528
|
),
|
|
8506
8529
|
)
|
|
8530
|
+
}`;
|
|
8531
|
+
case "stationary": return ` // trigger ${index}: stationary — a parked object appeared / departed. Stateless:
|
|
8532
|
+
// the registry emits one event per transition. A dropped event only misses a fire;
|
|
8533
|
+
// the stationary-present CONDITION reads the registry, not this event.
|
|
8534
|
+
{
|
|
8535
|
+
const trigger = ${j(trigger)} as const
|
|
8536
|
+
disposers.push(
|
|
8537
|
+
ctx.eventBus.subscribe(
|
|
8538
|
+
{ category: EventCategory.PipelineAnalyticsStationaryChanged },
|
|
8539
|
+
(event) => {
|
|
8540
|
+
const decision = evaluateStationaryTrigger(trigger, event.data)
|
|
8541
|
+
if (decision.kind === 'ignore') return
|
|
8542
|
+
if (decision.kind === 'reject') {
|
|
8543
|
+
ctx.logger.debug('stationary trigger rejected an event', {
|
|
8544
|
+
tags: { deviceId: decision.vars.deviceId },
|
|
8545
|
+
reason: decision.reason,
|
|
8546
|
+
})
|
|
8547
|
+
}
|
|
8548
|
+
void run(
|
|
8549
|
+
{ kind: 'stationary', at: Date.now(), ...decision.vars },
|
|
8550
|
+
decision.reason,
|
|
8551
|
+
decision.kind === 'fire',
|
|
8552
|
+
)
|
|
8553
|
+
},
|
|
8554
|
+
),
|
|
8555
|
+
)
|
|
8507
8556
|
}`;
|
|
8508
8557
|
}
|
|
8509
8558
|
}
|
|
@@ -8570,6 +8619,7 @@ import {
|
|
|
8570
8619
|
collectMirrorSources,
|
|
8571
8620
|
coreBlockIdFromAddonId,
|
|
8572
8621
|
evaluateConditionTree,
|
|
8622
|
+
evaluateStationaryTrigger,
|
|
8573
8623
|
isSkippedTrace,
|
|
8574
8624
|
mergeSkipTraces,
|
|
8575
8625
|
resolveDeviceTarget,
|
|
@@ -9064,310 +9114,6 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
|
|
|
9064
9114
|
`;
|
|
9065
9115
|
}
|
|
9066
9116
|
//#endregion
|
|
9067
|
-
//#region src/automation/condition-eval.ts
|
|
9068
|
-
/**
|
|
9069
|
-
* Evaluating an automation's condition tree against the ConditionMirror.
|
|
9070
|
-
*
|
|
9071
|
-
* D49: a trigger is a one-shot event, so its gate must never hang on a read
|
|
9072
|
-
* that can fail. Every field a condition leaf names is therefore kept in
|
|
9073
|
-
* memory — filled at start, updated off device state-changed events, reconciled
|
|
9074
|
-
* on a timer — and evaluation reads that memory ONLY. A value the mirror does
|
|
9075
|
-
* not hold is UNKNOWN, which is `fail` with a reason, never a throw and never a
|
|
9076
|
-
* `pass`. A failed refresh changes nothing: the previous value stays.
|
|
9077
|
-
*
|
|
9078
|
-
* The evaluator returns a node per tree position (containers, leaves, and the
|
|
9079
|
-
* ones a short-circuit never reached) with a stable path and a human reason, so
|
|
9080
|
-
* the editor can paint each block.
|
|
9081
|
-
*/
|
|
9082
|
-
function collectInto(cond, out) {
|
|
9083
|
-
switch (cond.kind) {
|
|
9084
|
-
case "all":
|
|
9085
|
-
case "any":
|
|
9086
|
-
for (const child of cond.children) collectInto(child, out);
|
|
9087
|
-
return;
|
|
9088
|
-
case "not":
|
|
9089
|
-
collectInto(cond.child, out);
|
|
9090
|
-
return;
|
|
9091
|
-
case "condition":
|
|
9092
|
-
out.push({
|
|
9093
|
-
kind: "device",
|
|
9094
|
-
deviceId: cond.deviceId,
|
|
9095
|
-
cap: cond.cap
|
|
9096
|
-
});
|
|
9097
|
-
return;
|
|
9098
|
-
case "expression":
|
|
9099
|
-
for (const binding of Object.values(cond.bindings)) if (binding.kind === "global") out.push({
|
|
9100
|
-
kind: "stable",
|
|
9101
|
-
stableId: binding.sourceStableId,
|
|
9102
|
-
cap: binding.cap
|
|
9103
|
-
});
|
|
9104
|
-
return;
|
|
9105
|
-
}
|
|
9106
|
-
}
|
|
9107
|
-
/** Every (device, cap) a tree reads, deduplicated. */
|
|
9108
|
-
function collectMirrorSources(cond) {
|
|
9109
|
-
if (cond === void 0) return [];
|
|
9110
|
-
const all = [];
|
|
9111
|
-
collectInto(cond, all);
|
|
9112
|
-
const seen = /* @__PURE__ */ new Set();
|
|
9113
|
-
return all.filter((s) => {
|
|
9114
|
-
const key = s.kind === "device" ? `d|${s.deviceId}|${s.cap}` : `s|${s.stableId}|${s.cap}`;
|
|
9115
|
-
if (seen.has(key)) return false;
|
|
9116
|
-
seen.add(key);
|
|
9117
|
-
return true;
|
|
9118
|
-
});
|
|
9119
|
-
}
|
|
9120
|
-
function sliceKey(deviceId, cap) {
|
|
9121
|
-
return `${deviceId}|${cap}`;
|
|
9122
|
-
}
|
|
9123
|
-
function pick$1(slice, fieldPath) {
|
|
9124
|
-
let cursor = slice;
|
|
9125
|
-
for (const part of fieldPath.split(".")) {
|
|
9126
|
-
if (cursor === null || typeof cursor !== "object") return void 0;
|
|
9127
|
-
cursor = Reflect.get(cursor, part);
|
|
9128
|
-
}
|
|
9129
|
-
return cursor;
|
|
9130
|
-
}
|
|
9131
|
-
function isSliceObject(value) {
|
|
9132
|
-
return typeof value === "object" && value !== null;
|
|
9133
|
-
}
|
|
9134
|
-
var ConditionMirror = class {
|
|
9135
|
-
sources;
|
|
9136
|
-
slices = /* @__PURE__ */ new Map();
|
|
9137
|
-
stableIds = /* @__PURE__ */ new Map();
|
|
9138
|
-
watched;
|
|
9139
|
-
constructor(sources) {
|
|
9140
|
-
this.sources = sources;
|
|
9141
|
-
this.watched = new Set(sources.filter((s) => s.kind === "device").map((s) => sliceKey(s.deviceId, s.cap)));
|
|
9142
|
-
}
|
|
9143
|
-
/** A `device.state-changed` event. Slices nobody reads are ignored. */
|
|
9144
|
-
apply(deviceId, cap, slice) {
|
|
9145
|
-
const key = sliceKey(deviceId, cap);
|
|
9146
|
-
if (!isSliceObject(slice)) return;
|
|
9147
|
-
if (!this.watched.has(key) && !this.isStableTarget(deviceId, cap)) return;
|
|
9148
|
-
this.slices.set(key, slice);
|
|
9149
|
-
}
|
|
9150
|
-
isStableTarget(deviceId, cap) {
|
|
9151
|
-
for (const s of this.sources) if (s.kind === "stable" && s.cap === cap && this.stableIds.get(s.stableId) === deviceId) return true;
|
|
9152
|
-
return false;
|
|
9153
|
-
}
|
|
9154
|
-
read(deviceId, cap, fieldPath) {
|
|
9155
|
-
const slice = this.slices.get(sliceKey(deviceId, cap));
|
|
9156
|
-
return slice === void 0 ? void 0 : pick$1(slice, fieldPath);
|
|
9157
|
-
}
|
|
9158
|
-
readStable(stableId, cap, fieldPath) {
|
|
9159
|
-
const deviceId = this.stableIds.get(stableId);
|
|
9160
|
-
return deviceId === void 0 ? void 0 : this.read(deviceId, cap, fieldPath);
|
|
9161
|
-
}
|
|
9162
|
-
/** Fill / reconcile. A read that fails or answers nothing keeps the old value. */
|
|
9163
|
-
async refresh(fetchers) {
|
|
9164
|
-
let updated = 0;
|
|
9165
|
-
let kept = 0;
|
|
9166
|
-
if (this.sources.some((s) => s.kind === "stable")) try {
|
|
9167
|
-
this.stableIds = await fetchers.fetchStableIds();
|
|
9168
|
-
} catch {}
|
|
9169
|
-
const targets = /* @__PURE__ */ new Map();
|
|
9170
|
-
for (const s of this.sources) {
|
|
9171
|
-
const deviceId = s.kind === "device" ? s.deviceId : this.stableIds.get(s.stableId);
|
|
9172
|
-
if (deviceId === void 0) {
|
|
9173
|
-
kept += 1;
|
|
9174
|
-
continue;
|
|
9175
|
-
}
|
|
9176
|
-
targets.set(sliceKey(deviceId, s.cap), {
|
|
9177
|
-
deviceId,
|
|
9178
|
-
cap: s.cap
|
|
9179
|
-
});
|
|
9180
|
-
}
|
|
9181
|
-
for (const [key, target] of targets) try {
|
|
9182
|
-
const slice = await fetchers.fetchSlice(target.deviceId, target.cap);
|
|
9183
|
-
if (isSliceObject(slice)) {
|
|
9184
|
-
this.slices.set(key, slice);
|
|
9185
|
-
updated += 1;
|
|
9186
|
-
} else kept += 1;
|
|
9187
|
-
} catch {
|
|
9188
|
-
kept += 1;
|
|
9189
|
-
}
|
|
9190
|
-
return {
|
|
9191
|
-
updated,
|
|
9192
|
-
kept
|
|
9193
|
-
};
|
|
9194
|
-
}
|
|
9195
|
-
};
|
|
9196
|
-
var OPERATOR_SYMBOL = {
|
|
9197
|
-
eq: "=",
|
|
9198
|
-
ne: "≠",
|
|
9199
|
-
gt: ">",
|
|
9200
|
-
gte: "≥",
|
|
9201
|
-
lt: "<",
|
|
9202
|
-
lte: "≤",
|
|
9203
|
-
contains: "contains",
|
|
9204
|
-
in: "in"
|
|
9205
|
-
};
|
|
9206
|
-
function compare(actual, operator, expected) {
|
|
9207
|
-
switch (operator) {
|
|
9208
|
-
case "eq": return actual === expected;
|
|
9209
|
-
case "ne": return actual !== expected;
|
|
9210
|
-
case "gt": return typeof actual === "number" && typeof expected === "number" && actual > expected;
|
|
9211
|
-
case "gte": return typeof actual === "number" && typeof expected === "number" && actual >= expected;
|
|
9212
|
-
case "lt": return typeof actual === "number" && typeof expected === "number" && actual < expected;
|
|
9213
|
-
case "lte": return typeof actual === "number" && typeof expected === "number" && actual <= expected;
|
|
9214
|
-
case "contains": return typeof actual === "string" && typeof expected === "string" && actual.includes(expected);
|
|
9215
|
-
case "in": return Array.isArray(expected) && expected.some((v) => v === actual);
|
|
9216
|
-
}
|
|
9217
|
-
}
|
|
9218
|
-
function toTraceValue(value) {
|
|
9219
|
-
if (value === void 0 || value === null) return null;
|
|
9220
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
9221
|
-
if (Array.isArray(value) && value.every((v) => typeof v === "string" || typeof v === "number")) return value;
|
|
9222
|
-
return JSON.stringify(value);
|
|
9223
|
-
}
|
|
9224
|
-
function show(value) {
|
|
9225
|
-
return Array.isArray(value) ? `[${value.join(", ")}]` : String(value);
|
|
9226
|
-
}
|
|
9227
|
-
function markSkipped(cond, path, why, out) {
|
|
9228
|
-
out.push({
|
|
9229
|
-
path,
|
|
9230
|
-
kind: cond.kind,
|
|
9231
|
-
outcome: "not-evaluated",
|
|
9232
|
-
reason: why
|
|
9233
|
-
});
|
|
9234
|
-
if (cond.kind === "all" || cond.kind === "any") cond.children.forEach((c, i) => {
|
|
9235
|
-
markSkipped(c, `${path}.${i}`, why, out);
|
|
9236
|
-
});
|
|
9237
|
-
else if (cond.kind === "not") markSkipped(cond.child, `${path}.0`, why, out);
|
|
9238
|
-
}
|
|
9239
|
-
function evalNode(cond, path, reader, now, out) {
|
|
9240
|
-
switch (cond.kind) {
|
|
9241
|
-
case "condition": {
|
|
9242
|
-
const observed = reader.read(cond.deviceId, cond.cap, cond.fieldPath);
|
|
9243
|
-
const base = {
|
|
9244
|
-
path,
|
|
9245
|
-
kind: cond.kind,
|
|
9246
|
-
deviceId: cond.deviceId,
|
|
9247
|
-
cap: cond.cap,
|
|
9248
|
-
fieldPath: cond.fieldPath,
|
|
9249
|
-
operator: cond.operator,
|
|
9250
|
-
expected: toTraceValue(cond.value),
|
|
9251
|
-
observed: toTraceValue(observed)
|
|
9252
|
-
};
|
|
9253
|
-
if (observed === void 0) {
|
|
9254
|
-
out.push({
|
|
9255
|
-
...base,
|
|
9256
|
-
outcome: "fail",
|
|
9257
|
-
reason: `${cond.fieldPath} value unknown → fail`
|
|
9258
|
-
});
|
|
9259
|
-
return false;
|
|
9260
|
-
}
|
|
9261
|
-
const ok = compare(observed, cond.operator, cond.value);
|
|
9262
|
-
out.push({
|
|
9263
|
-
...base,
|
|
9264
|
-
outcome: ok ? "pass" : "fail",
|
|
9265
|
-
reason: `${cond.fieldPath} ${show(observed)}, expected ${OPERATOR_SYMBOL[cond.operator]} ${show(cond.value)} → ${ok ? "pass" : "fail"}`
|
|
9266
|
-
});
|
|
9267
|
-
return ok;
|
|
9268
|
-
}
|
|
9269
|
-
case "expression": {
|
|
9270
|
-
const values = {};
|
|
9271
|
-
for (const [alias, binding] of Object.entries(cond.bindings)) {
|
|
9272
|
-
if (binding.kind === "literal") {
|
|
9273
|
-
values[alias] = require_composition.toExpressionValue(binding.value) ?? null;
|
|
9274
|
-
continue;
|
|
9275
|
-
}
|
|
9276
|
-
const raw = binding.kind === "global" ? reader.readStable(binding.sourceStableId, binding.cap, binding.fieldPath) : void 0;
|
|
9277
|
-
if (raw === void 0) {
|
|
9278
|
-
out.push({
|
|
9279
|
-
path,
|
|
9280
|
-
kind: cond.kind,
|
|
9281
|
-
outcome: "fail",
|
|
9282
|
-
reason: `${alias} value unknown → fail`
|
|
9283
|
-
});
|
|
9284
|
-
return false;
|
|
9285
|
-
}
|
|
9286
|
-
values[alias] = require_composition.toExpressionValue(raw) ?? null;
|
|
9287
|
-
}
|
|
9288
|
-
const result = require_composition.evaluateExpressionSource(cond.expr, values, now);
|
|
9289
|
-
if (!result.ok) {
|
|
9290
|
-
out.push({
|
|
9291
|
-
path,
|
|
9292
|
-
kind: cond.kind,
|
|
9293
|
-
outcome: "fail",
|
|
9294
|
-
reason: `${cond.expr}: ${result.error} → fail`
|
|
9295
|
-
});
|
|
9296
|
-
return false;
|
|
9297
|
-
}
|
|
9298
|
-
const ok = result.value === true;
|
|
9299
|
-
out.push({
|
|
9300
|
-
path,
|
|
9301
|
-
kind: cond.kind,
|
|
9302
|
-
outcome: ok ? "pass" : "fail",
|
|
9303
|
-
reason: `${cond.expr} with ${JSON.stringify(values)} → ${ok ? "pass" : "fail"}`
|
|
9304
|
-
});
|
|
9305
|
-
return ok;
|
|
9306
|
-
}
|
|
9307
|
-
case "not": {
|
|
9308
|
-
const at = out.length;
|
|
9309
|
-
out.push({
|
|
9310
|
-
path,
|
|
9311
|
-
kind: "not",
|
|
9312
|
-
outcome: "not-evaluated",
|
|
9313
|
-
reason: ""
|
|
9314
|
-
});
|
|
9315
|
-
const inner = evalNode(cond.child, `${path}.0`, reader, now, out);
|
|
9316
|
-
const ok = !inner;
|
|
9317
|
-
out.splice(at, 1, {
|
|
9318
|
-
path,
|
|
9319
|
-
kind: "not",
|
|
9320
|
-
outcome: ok ? "pass" : "fail",
|
|
9321
|
-
reason: `not (${inner ? "pass" : "fail"}) → ${ok ? "pass" : "fail"}`
|
|
9322
|
-
});
|
|
9323
|
-
return ok;
|
|
9324
|
-
}
|
|
9325
|
-
case "all":
|
|
9326
|
-
case "any": {
|
|
9327
|
-
const isAll = cond.kind === "all";
|
|
9328
|
-
const at = out.length;
|
|
9329
|
-
out.push({
|
|
9330
|
-
path,
|
|
9331
|
-
kind: cond.kind,
|
|
9332
|
-
outcome: "not-evaluated",
|
|
9333
|
-
reason: ""
|
|
9334
|
-
});
|
|
9335
|
-
let decided = null;
|
|
9336
|
-
for (let i = 0; i < cond.children.length; i += 1) {
|
|
9337
|
-
const child = cond.children[i];
|
|
9338
|
-
if (child === void 0) continue;
|
|
9339
|
-
if (decided !== null) {
|
|
9340
|
-
markSkipped(child, `${path}.${i}`, `short-circuited by ${path}.${decided}`, out);
|
|
9341
|
-
continue;
|
|
9342
|
-
}
|
|
9343
|
-
const ok = evalNode(child, `${path}.${i}`, reader, now, out);
|
|
9344
|
-
if (isAll ? !ok : ok) decided = i;
|
|
9345
|
-
}
|
|
9346
|
-
const ok = isAll ? decided === null : decided !== null;
|
|
9347
|
-
const reason = isAll ? ok ? `all ${cond.children.length} passed → pass` : `${path}.${decided} failed → fail` : ok ? `${path}.${decided} passed → pass` : `none of ${cond.children.length} passed → fail`;
|
|
9348
|
-
out.splice(at, 1, {
|
|
9349
|
-
path,
|
|
9350
|
-
kind: cond.kind,
|
|
9351
|
-
outcome: ok ? "pass" : "fail",
|
|
9352
|
-
reason
|
|
9353
|
-
});
|
|
9354
|
-
return ok;
|
|
9355
|
-
}
|
|
9356
|
-
}
|
|
9357
|
-
}
|
|
9358
|
-
/** No condition tree is a pass with no nodes. */
|
|
9359
|
-
function evaluateConditionTree(cond, reader, now) {
|
|
9360
|
-
if (cond === void 0) return {
|
|
9361
|
-
pass: true,
|
|
9362
|
-
nodes: []
|
|
9363
|
-
};
|
|
9364
|
-
const nodes = [];
|
|
9365
|
-
return {
|
|
9366
|
-
pass: evalNode(cond, "conditions", reader, now, nodes),
|
|
9367
|
-
nodes
|
|
9368
|
-
};
|
|
9369
|
-
}
|
|
9370
|
-
//#endregion
|
|
9371
9117
|
//#region src/catalogs/coco-classmap.ts
|
|
9372
9118
|
var COCO_80_LABELS = [
|
|
9373
9119
|
{
|
|
@@ -9770,7 +9516,7 @@ function detectionClassMatches(wanted, actual) {
|
|
|
9770
9516
|
if (w === a) return true;
|
|
9771
9517
|
return MACRO_NAMES.has(w) && macroOf(a) === w;
|
|
9772
9518
|
}
|
|
9773
|
-
function isPhase(value) {
|
|
9519
|
+
function isPhase$1(value) {
|
|
9774
9520
|
return value === "start" || value === "update" || value === "end";
|
|
9775
9521
|
}
|
|
9776
9522
|
var DetectionTriggerFilter = class {
|
|
@@ -9785,7 +9531,7 @@ var DetectionTriggerFilter = class {
|
|
|
9785
9531
|
}
|
|
9786
9532
|
evaluate(event, now) {
|
|
9787
9533
|
const { deviceId, trackId, phase } = event;
|
|
9788
|
-
if (typeof deviceId !== "number" || typeof trackId !== "string" || !isPhase(phase)) return {
|
|
9534
|
+
if (typeof deviceId !== "number" || typeof trackId !== "string" || !isPhase$1(phase)) return {
|
|
9789
9535
|
kind: "ignore",
|
|
9790
9536
|
why: "malformed track-lifecycle event"
|
|
9791
9537
|
};
|
|
@@ -9856,6 +9602,433 @@ var DetectionTriggerFilter = class {
|
|
|
9856
9602
|
}
|
|
9857
9603
|
};
|
|
9858
9604
|
//#endregion
|
|
9605
|
+
//#region src/automation/condition-eval.ts
|
|
9606
|
+
/**
|
|
9607
|
+
* Evaluating an automation's condition tree against the ConditionMirror.
|
|
9608
|
+
*
|
|
9609
|
+
* D49: a trigger is a one-shot event, so its gate must never hang on a read
|
|
9610
|
+
* that can fail. Every field a condition leaf names is therefore kept in
|
|
9611
|
+
* memory — filled at start, updated off device state-changed events, reconciled
|
|
9612
|
+
* on a timer — and evaluation reads that memory ONLY. A value the mirror does
|
|
9613
|
+
* not hold is UNKNOWN, which is `fail` with a reason, never a throw and never a
|
|
9614
|
+
* `pass`. A failed refresh changes nothing: the previous value stays.
|
|
9615
|
+
*
|
|
9616
|
+
* The evaluator returns a node per tree position (containers, leaves, and the
|
|
9617
|
+
* ones a short-circuit never reached) with a stable path and a human reason, so
|
|
9618
|
+
* the editor can paint each block.
|
|
9619
|
+
*/
|
|
9620
|
+
/** The cap whose runtime-state slice IS the occupancy snapshot, parked objects included. */
|
|
9621
|
+
var ZONE_ANALYTICS_CAP = "zone-analytics";
|
|
9622
|
+
function collectInto(cond, out) {
|
|
9623
|
+
switch (cond.kind) {
|
|
9624
|
+
case "all":
|
|
9625
|
+
case "any":
|
|
9626
|
+
for (const child of cond.children) collectInto(child, out);
|
|
9627
|
+
return;
|
|
9628
|
+
case "not":
|
|
9629
|
+
collectInto(cond.child, out);
|
|
9630
|
+
return;
|
|
9631
|
+
case "condition":
|
|
9632
|
+
out.push({
|
|
9633
|
+
kind: "device",
|
|
9634
|
+
deviceId: cond.deviceId,
|
|
9635
|
+
cap: cond.cap
|
|
9636
|
+
});
|
|
9637
|
+
return;
|
|
9638
|
+
case "stationary-present":
|
|
9639
|
+
out.push({
|
|
9640
|
+
kind: "device",
|
|
9641
|
+
deviceId: cond.deviceId,
|
|
9642
|
+
cap: ZONE_ANALYTICS_CAP
|
|
9643
|
+
});
|
|
9644
|
+
return;
|
|
9645
|
+
case "expression":
|
|
9646
|
+
for (const binding of Object.values(cond.bindings)) if (binding.kind === "global") out.push({
|
|
9647
|
+
kind: "stable",
|
|
9648
|
+
stableId: binding.sourceStableId,
|
|
9649
|
+
cap: binding.cap
|
|
9650
|
+
});
|
|
9651
|
+
return;
|
|
9652
|
+
}
|
|
9653
|
+
}
|
|
9654
|
+
/** Every (device, cap) a tree reads, deduplicated. */
|
|
9655
|
+
function collectMirrorSources(cond) {
|
|
9656
|
+
if (cond === void 0) return [];
|
|
9657
|
+
const all = [];
|
|
9658
|
+
collectInto(cond, all);
|
|
9659
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9660
|
+
return all.filter((s) => {
|
|
9661
|
+
const key = s.kind === "device" ? `d|${s.deviceId}|${s.cap}` : `s|${s.stableId}|${s.cap}`;
|
|
9662
|
+
if (seen.has(key)) return false;
|
|
9663
|
+
seen.add(key);
|
|
9664
|
+
return true;
|
|
9665
|
+
});
|
|
9666
|
+
}
|
|
9667
|
+
function sliceKey(deviceId, cap) {
|
|
9668
|
+
return `${deviceId}|${cap}`;
|
|
9669
|
+
}
|
|
9670
|
+
function pick$1(slice, fieldPath) {
|
|
9671
|
+
let cursor = slice;
|
|
9672
|
+
for (const part of fieldPath.split(".")) {
|
|
9673
|
+
if (cursor === null || typeof cursor !== "object") return void 0;
|
|
9674
|
+
cursor = Reflect.get(cursor, part);
|
|
9675
|
+
}
|
|
9676
|
+
return cursor;
|
|
9677
|
+
}
|
|
9678
|
+
function isSliceObject(value) {
|
|
9679
|
+
return typeof value === "object" && value !== null;
|
|
9680
|
+
}
|
|
9681
|
+
var ConditionMirror = class {
|
|
9682
|
+
sources;
|
|
9683
|
+
slices = /* @__PURE__ */ new Map();
|
|
9684
|
+
stableIds = /* @__PURE__ */ new Map();
|
|
9685
|
+
watched;
|
|
9686
|
+
constructor(sources) {
|
|
9687
|
+
this.sources = sources;
|
|
9688
|
+
this.watched = new Set(sources.filter((s) => s.kind === "device").map((s) => sliceKey(s.deviceId, s.cap)));
|
|
9689
|
+
}
|
|
9690
|
+
/** A `device.state-changed` event. Slices nobody reads are ignored. */
|
|
9691
|
+
apply(deviceId, cap, slice) {
|
|
9692
|
+
const key = sliceKey(deviceId, cap);
|
|
9693
|
+
if (!isSliceObject(slice)) return;
|
|
9694
|
+
if (!this.watched.has(key) && !this.isStableTarget(deviceId, cap)) return;
|
|
9695
|
+
this.slices.set(key, slice);
|
|
9696
|
+
}
|
|
9697
|
+
isStableTarget(deviceId, cap) {
|
|
9698
|
+
for (const s of this.sources) if (s.kind === "stable" && s.cap === cap && this.stableIds.get(s.stableId) === deviceId) return true;
|
|
9699
|
+
return false;
|
|
9700
|
+
}
|
|
9701
|
+
read(deviceId, cap, fieldPath) {
|
|
9702
|
+
const slice = this.slices.get(sliceKey(deviceId, cap));
|
|
9703
|
+
return slice === void 0 ? void 0 : pick$1(slice, fieldPath);
|
|
9704
|
+
}
|
|
9705
|
+
readStable(stableId, cap, fieldPath) {
|
|
9706
|
+
const deviceId = this.stableIds.get(stableId);
|
|
9707
|
+
return deviceId === void 0 ? void 0 : this.read(deviceId, cap, fieldPath);
|
|
9708
|
+
}
|
|
9709
|
+
/** Fill / reconcile. A read that fails or answers nothing keeps the old value. */
|
|
9710
|
+
async refresh(fetchers) {
|
|
9711
|
+
let updated = 0;
|
|
9712
|
+
let kept = 0;
|
|
9713
|
+
if (this.sources.some((s) => s.kind === "stable")) try {
|
|
9714
|
+
this.stableIds = await fetchers.fetchStableIds();
|
|
9715
|
+
} catch {}
|
|
9716
|
+
const targets = /* @__PURE__ */ new Map();
|
|
9717
|
+
for (const s of this.sources) {
|
|
9718
|
+
const deviceId = s.kind === "device" ? s.deviceId : this.stableIds.get(s.stableId);
|
|
9719
|
+
if (deviceId === void 0) {
|
|
9720
|
+
kept += 1;
|
|
9721
|
+
continue;
|
|
9722
|
+
}
|
|
9723
|
+
targets.set(sliceKey(deviceId, s.cap), {
|
|
9724
|
+
deviceId,
|
|
9725
|
+
cap: s.cap
|
|
9726
|
+
});
|
|
9727
|
+
}
|
|
9728
|
+
for (const [key, target] of targets) try {
|
|
9729
|
+
const slice = await fetchers.fetchSlice(target.deviceId, target.cap);
|
|
9730
|
+
if (isSliceObject(slice)) {
|
|
9731
|
+
this.slices.set(key, slice);
|
|
9732
|
+
updated += 1;
|
|
9733
|
+
} else kept += 1;
|
|
9734
|
+
} catch {
|
|
9735
|
+
kept += 1;
|
|
9736
|
+
}
|
|
9737
|
+
return {
|
|
9738
|
+
updated,
|
|
9739
|
+
kept
|
|
9740
|
+
};
|
|
9741
|
+
}
|
|
9742
|
+
};
|
|
9743
|
+
var OPERATOR_SYMBOL = {
|
|
9744
|
+
eq: "=",
|
|
9745
|
+
ne: "≠",
|
|
9746
|
+
gt: ">",
|
|
9747
|
+
gte: "≥",
|
|
9748
|
+
lt: "<",
|
|
9749
|
+
lte: "≤",
|
|
9750
|
+
contains: "contains",
|
|
9751
|
+
in: "in"
|
|
9752
|
+
};
|
|
9753
|
+
function compare(actual, operator, expected) {
|
|
9754
|
+
switch (operator) {
|
|
9755
|
+
case "eq": return actual === expected;
|
|
9756
|
+
case "ne": return actual !== expected;
|
|
9757
|
+
case "gt": return typeof actual === "number" && typeof expected === "number" && actual > expected;
|
|
9758
|
+
case "gte": return typeof actual === "number" && typeof expected === "number" && actual >= expected;
|
|
9759
|
+
case "lt": return typeof actual === "number" && typeof expected === "number" && actual < expected;
|
|
9760
|
+
case "lte": return typeof actual === "number" && typeof expected === "number" && actual <= expected;
|
|
9761
|
+
case "contains": return typeof actual === "string" && typeof expected === "string" && actual.includes(expected);
|
|
9762
|
+
case "in": return Array.isArray(expected) && expected.some((v) => v === actual);
|
|
9763
|
+
}
|
|
9764
|
+
}
|
|
9765
|
+
function toTraceValue(value) {
|
|
9766
|
+
if (value === void 0 || value === null) return null;
|
|
9767
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
9768
|
+
if (Array.isArray(value) && value.every((v) => typeof v === "string" || typeof v === "number")) return value;
|
|
9769
|
+
return JSON.stringify(value);
|
|
9770
|
+
}
|
|
9771
|
+
function show(value) {
|
|
9772
|
+
return Array.isArray(value) ? `[${value.join(", ")}]` : String(value);
|
|
9773
|
+
}
|
|
9774
|
+
function markSkipped(cond, path, why, out) {
|
|
9775
|
+
out.push({
|
|
9776
|
+
path,
|
|
9777
|
+
kind: cond.kind,
|
|
9778
|
+
outcome: "not-evaluated",
|
|
9779
|
+
reason: why
|
|
9780
|
+
});
|
|
9781
|
+
if (cond.kind === "all" || cond.kind === "any") cond.children.forEach((c, i) => {
|
|
9782
|
+
markSkipped(c, `${path}.${i}`, why, out);
|
|
9783
|
+
});
|
|
9784
|
+
else if (cond.kind === "not") markSkipped(cond.child, `${path}.0`, why, out);
|
|
9785
|
+
}
|
|
9786
|
+
function parkedObjectsOf(value) {
|
|
9787
|
+
if (!Array.isArray(value)) return [];
|
|
9788
|
+
const rows = [];
|
|
9789
|
+
for (const item of value) {
|
|
9790
|
+
const id = Reflect.get(Object(item), "id");
|
|
9791
|
+
const className = Reflect.get(Object(item), "className");
|
|
9792
|
+
if (typeof id === "string" && typeof className === "string") rows.push({
|
|
9793
|
+
id,
|
|
9794
|
+
className
|
|
9795
|
+
});
|
|
9796
|
+
}
|
|
9797
|
+
return rows;
|
|
9798
|
+
}
|
|
9799
|
+
/** `zoneId`s whose `trackIds` list the parked object (`stationary:<id>`, as the snapshot builds it). */
|
|
9800
|
+
function zonesOfParked(zones, parkedId) {
|
|
9801
|
+
if (!Array.isArray(zones)) return [];
|
|
9802
|
+
const out = [];
|
|
9803
|
+
for (const zone of zones) {
|
|
9804
|
+
const zoneId = Reflect.get(Object(zone), "zoneId");
|
|
9805
|
+
const trackIds = Reflect.get(Object(zone), "trackIds");
|
|
9806
|
+
if (typeof zoneId === "string" && Array.isArray(trackIds) && trackIds.includes(`stationary:${parkedId}`)) out.push(zoneId);
|
|
9807
|
+
}
|
|
9808
|
+
return out;
|
|
9809
|
+
}
|
|
9810
|
+
/**
|
|
9811
|
+
* Is a matching parked object there NOW? Read off the mirrored zone-analytics
|
|
9812
|
+
* snapshot: `frame` is always present in a snapshot, so its absence means the
|
|
9813
|
+
* slice was never read (UNKNOWN, a fail that says so), while a snapshot without
|
|
9814
|
+
* `stationaryObjects` is a definite "nothing parked" (the provider omits an
|
|
9815
|
+
* empty list).
|
|
9816
|
+
*/
|
|
9817
|
+
function judgeStationaryPresent(cond, reader) {
|
|
9818
|
+
if (reader.read(cond.deviceId, ZONE_ANALYTICS_CAP, "frame") === void 0) return {
|
|
9819
|
+
pass: false,
|
|
9820
|
+
why: `device ${cond.deviceId} occupancy snapshot unknown`
|
|
9821
|
+
};
|
|
9822
|
+
const parked = parkedObjectsOf(reader.read(cond.deviceId, ZONE_ANALYTICS_CAP, "stationaryObjects"));
|
|
9823
|
+
if (parked.length === 0) return {
|
|
9824
|
+
pass: false,
|
|
9825
|
+
why: `no parked objects on device ${cond.deviceId}`
|
|
9826
|
+
};
|
|
9827
|
+
const zones = reader.read(cond.deviceId, ZONE_ANALYTICS_CAP, "zones");
|
|
9828
|
+
for (const obj of parked) {
|
|
9829
|
+
if (cond.classes !== void 0 && !cond.classes.some((c) => detectionClassMatches(c, obj.className))) continue;
|
|
9830
|
+
if (cond.zones !== void 0) {
|
|
9831
|
+
const inZones = zonesOfParked(zones, obj.id);
|
|
9832
|
+
if (!cond.zones.some((z) => inZones.includes(z))) continue;
|
|
9833
|
+
}
|
|
9834
|
+
return {
|
|
9835
|
+
pass: true,
|
|
9836
|
+
why: `parked ${obj.className} (${obj.id}) on device ${cond.deviceId}`
|
|
9837
|
+
};
|
|
9838
|
+
}
|
|
9839
|
+
return {
|
|
9840
|
+
pass: false,
|
|
9841
|
+
why: `${parked.length} parked on device ${cond.deviceId}, none matching the class/zone filter`
|
|
9842
|
+
};
|
|
9843
|
+
}
|
|
9844
|
+
function evalNode(cond, path, reader, now, out) {
|
|
9845
|
+
switch (cond.kind) {
|
|
9846
|
+
case "condition": {
|
|
9847
|
+
const observed = reader.read(cond.deviceId, cond.cap, cond.fieldPath);
|
|
9848
|
+
const base = {
|
|
9849
|
+
path,
|
|
9850
|
+
kind: cond.kind,
|
|
9851
|
+
deviceId: cond.deviceId,
|
|
9852
|
+
cap: cond.cap,
|
|
9853
|
+
fieldPath: cond.fieldPath,
|
|
9854
|
+
operator: cond.operator,
|
|
9855
|
+
expected: toTraceValue(cond.value),
|
|
9856
|
+
observed: toTraceValue(observed)
|
|
9857
|
+
};
|
|
9858
|
+
if (observed === void 0) {
|
|
9859
|
+
out.push({
|
|
9860
|
+
...base,
|
|
9861
|
+
outcome: "fail",
|
|
9862
|
+
reason: `${cond.fieldPath} value unknown → fail`
|
|
9863
|
+
});
|
|
9864
|
+
return false;
|
|
9865
|
+
}
|
|
9866
|
+
const ok = compare(observed, cond.operator, cond.value);
|
|
9867
|
+
out.push({
|
|
9868
|
+
...base,
|
|
9869
|
+
outcome: ok ? "pass" : "fail",
|
|
9870
|
+
reason: `${cond.fieldPath} ${show(observed)}, expected ${OPERATOR_SYMBOL[cond.operator]} ${show(cond.value)} → ${ok ? "pass" : "fail"}`
|
|
9871
|
+
});
|
|
9872
|
+
return ok;
|
|
9873
|
+
}
|
|
9874
|
+
case "stationary-present": {
|
|
9875
|
+
const verdict = judgeStationaryPresent(cond, reader);
|
|
9876
|
+
out.push({
|
|
9877
|
+
path,
|
|
9878
|
+
kind: cond.kind,
|
|
9879
|
+
outcome: verdict.pass ? "pass" : "fail",
|
|
9880
|
+
reason: `${verdict.why} → ${verdict.pass ? "pass" : "fail"}`,
|
|
9881
|
+
deviceId: cond.deviceId,
|
|
9882
|
+
cap: ZONE_ANALYTICS_CAP
|
|
9883
|
+
});
|
|
9884
|
+
return verdict.pass;
|
|
9885
|
+
}
|
|
9886
|
+
case "expression": {
|
|
9887
|
+
const values = {};
|
|
9888
|
+
for (const [alias, binding] of Object.entries(cond.bindings)) {
|
|
9889
|
+
if (binding.kind === "literal") {
|
|
9890
|
+
values[alias] = require_composition.toExpressionValue(binding.value) ?? null;
|
|
9891
|
+
continue;
|
|
9892
|
+
}
|
|
9893
|
+
const raw = binding.kind === "global" ? reader.readStable(binding.sourceStableId, binding.cap, binding.fieldPath) : void 0;
|
|
9894
|
+
if (raw === void 0) {
|
|
9895
|
+
out.push({
|
|
9896
|
+
path,
|
|
9897
|
+
kind: cond.kind,
|
|
9898
|
+
outcome: "fail",
|
|
9899
|
+
reason: `${alias} value unknown → fail`
|
|
9900
|
+
});
|
|
9901
|
+
return false;
|
|
9902
|
+
}
|
|
9903
|
+
values[alias] = require_composition.toExpressionValue(raw) ?? null;
|
|
9904
|
+
}
|
|
9905
|
+
const result = require_composition.evaluateExpressionSource(cond.expr, values, now);
|
|
9906
|
+
if (!result.ok) {
|
|
9907
|
+
out.push({
|
|
9908
|
+
path,
|
|
9909
|
+
kind: cond.kind,
|
|
9910
|
+
outcome: "fail",
|
|
9911
|
+
reason: `${cond.expr}: ${result.error} → fail`
|
|
9912
|
+
});
|
|
9913
|
+
return false;
|
|
9914
|
+
}
|
|
9915
|
+
const ok = result.value === true;
|
|
9916
|
+
out.push({
|
|
9917
|
+
path,
|
|
9918
|
+
kind: cond.kind,
|
|
9919
|
+
outcome: ok ? "pass" : "fail",
|
|
9920
|
+
reason: `${cond.expr} with ${JSON.stringify(values)} → ${ok ? "pass" : "fail"}`
|
|
9921
|
+
});
|
|
9922
|
+
return ok;
|
|
9923
|
+
}
|
|
9924
|
+
case "not": {
|
|
9925
|
+
const at = out.length;
|
|
9926
|
+
out.push({
|
|
9927
|
+
path,
|
|
9928
|
+
kind: "not",
|
|
9929
|
+
outcome: "not-evaluated",
|
|
9930
|
+
reason: ""
|
|
9931
|
+
});
|
|
9932
|
+
const inner = evalNode(cond.child, `${path}.0`, reader, now, out);
|
|
9933
|
+
const ok = !inner;
|
|
9934
|
+
out.splice(at, 1, {
|
|
9935
|
+
path,
|
|
9936
|
+
kind: "not",
|
|
9937
|
+
outcome: ok ? "pass" : "fail",
|
|
9938
|
+
reason: `not (${inner ? "pass" : "fail"}) → ${ok ? "pass" : "fail"}`
|
|
9939
|
+
});
|
|
9940
|
+
return ok;
|
|
9941
|
+
}
|
|
9942
|
+
case "all":
|
|
9943
|
+
case "any": {
|
|
9944
|
+
const isAll = cond.kind === "all";
|
|
9945
|
+
const at = out.length;
|
|
9946
|
+
out.push({
|
|
9947
|
+
path,
|
|
9948
|
+
kind: cond.kind,
|
|
9949
|
+
outcome: "not-evaluated",
|
|
9950
|
+
reason: ""
|
|
9951
|
+
});
|
|
9952
|
+
let decided = null;
|
|
9953
|
+
for (let i = 0; i < cond.children.length; i += 1) {
|
|
9954
|
+
const child = cond.children[i];
|
|
9955
|
+
if (child === void 0) continue;
|
|
9956
|
+
if (decided !== null) {
|
|
9957
|
+
markSkipped(child, `${path}.${i}`, `short-circuited by ${path}.${decided}`, out);
|
|
9958
|
+
continue;
|
|
9959
|
+
}
|
|
9960
|
+
const ok = evalNode(child, `${path}.${i}`, reader, now, out);
|
|
9961
|
+
if (isAll ? !ok : ok) decided = i;
|
|
9962
|
+
}
|
|
9963
|
+
const ok = isAll ? decided === null : decided !== null;
|
|
9964
|
+
const reason = isAll ? ok ? `all ${cond.children.length} passed → pass` : `${path}.${decided} failed → fail` : ok ? `${path}.${decided} passed → pass` : `none of ${cond.children.length} passed → fail`;
|
|
9965
|
+
out.splice(at, 1, {
|
|
9966
|
+
path,
|
|
9967
|
+
kind: cond.kind,
|
|
9968
|
+
outcome: ok ? "pass" : "fail",
|
|
9969
|
+
reason
|
|
9970
|
+
});
|
|
9971
|
+
return ok;
|
|
9972
|
+
}
|
|
9973
|
+
}
|
|
9974
|
+
}
|
|
9975
|
+
/** No condition tree is a pass with no nodes. */
|
|
9976
|
+
function evaluateConditionTree(cond, reader, now) {
|
|
9977
|
+
if (cond === void 0) return {
|
|
9978
|
+
pass: true,
|
|
9979
|
+
nodes: []
|
|
9980
|
+
};
|
|
9981
|
+
const nodes = [];
|
|
9982
|
+
return {
|
|
9983
|
+
pass: evalNode(cond, "conditions", reader, now, nodes),
|
|
9984
|
+
nodes
|
|
9985
|
+
};
|
|
9986
|
+
}
|
|
9987
|
+
//#endregion
|
|
9988
|
+
//#region src/automation/stationary-trigger.ts
|
|
9989
|
+
function isPhase(value) {
|
|
9990
|
+
return value === "appeared" || value === "departed";
|
|
9991
|
+
}
|
|
9992
|
+
function evaluateStationaryTrigger(trigger, event) {
|
|
9993
|
+
const { deviceId, entryId, phase } = event;
|
|
9994
|
+
if (typeof deviceId !== "number" || typeof entryId !== "string" || !isPhase(phase)) return {
|
|
9995
|
+
kind: "ignore",
|
|
9996
|
+
why: "malformed stationary-changed event"
|
|
9997
|
+
};
|
|
9998
|
+
if (!trigger.deviceIds.includes(deviceId)) return {
|
|
9999
|
+
kind: "ignore",
|
|
10000
|
+
why: `device ${deviceId} is not one of the trigger's devices`
|
|
10001
|
+
};
|
|
10002
|
+
if (phase !== trigger.phase) return {
|
|
10003
|
+
kind: "ignore",
|
|
10004
|
+
why: `phase ${phase}, the trigger wants ${trigger.phase}`
|
|
10005
|
+
};
|
|
10006
|
+
const className = typeof event.className === "string" ? event.className : "";
|
|
10007
|
+
const zones = Array.isArray(event.zones) ? event.zones.filter((z) => typeof z === "string") : [];
|
|
10008
|
+
const vars = {
|
|
10009
|
+
deviceId,
|
|
10010
|
+
className,
|
|
10011
|
+
zones,
|
|
10012
|
+
phase,
|
|
10013
|
+
entryId
|
|
10014
|
+
};
|
|
10015
|
+
if (trigger.classes !== void 0 && !trigger.classes.some((c) => detectionClassMatches(c, className))) return {
|
|
10016
|
+
kind: "reject",
|
|
10017
|
+
reason: `class ${className || "unknown"} not in [${trigger.classes.join(", ")}]`,
|
|
10018
|
+
vars
|
|
10019
|
+
};
|
|
10020
|
+
if (trigger.zones !== void 0 && !trigger.zones.some((z) => zones.includes(z))) return {
|
|
10021
|
+
kind: "reject",
|
|
10022
|
+
reason: `zone mismatch: in [${zones.join(", ")}], wanted [${trigger.zones.join(", ")}]`,
|
|
10023
|
+
vars
|
|
10024
|
+
};
|
|
10025
|
+
return {
|
|
10026
|
+
kind: "fire",
|
|
10027
|
+
reason: `${className} ${phase} on device ${deviceId}`,
|
|
10028
|
+
vars
|
|
10029
|
+
};
|
|
10030
|
+
}
|
|
10031
|
+
//#endregion
|
|
9859
10032
|
//#region src/automation/run-gate.ts
|
|
9860
10033
|
var MINUTE_MS$1 = 6e4;
|
|
9861
10034
|
function minutes(ms) {
|
|
@@ -10032,7 +10205,8 @@ var AutomationConditionTraceNodeSchema = zod.z.object({
|
|
|
10032
10205
|
"any",
|
|
10033
10206
|
"not",
|
|
10034
10207
|
"condition",
|
|
10035
|
-
"expression"
|
|
10208
|
+
"expression",
|
|
10209
|
+
"stationary-present"
|
|
10036
10210
|
]),
|
|
10037
10211
|
outcome: TraceNodeOutcomeSchema,
|
|
10038
10212
|
reason: zod.z.string(),
|
|
@@ -10159,7 +10333,7 @@ function appendTrace(ring, trace, keep) {
|
|
|
10159
10333
|
}
|
|
10160
10334
|
/**
|
|
10161
10335
|
* The trigger stage of a trace from the bag a trigger handed to `run()`:
|
|
10162
|
-
* primitives
|
|
10336
|
+
* primitives and short lists of them (a device-state `to` that is an object is left out), minus the
|
|
10163
10337
|
* bookkeeping keys `kind` and `at`.
|
|
10164
10338
|
*/
|
|
10165
10339
|
function buildTriggerTrace(trigger, outcome, reason) {
|
|
@@ -10167,6 +10341,7 @@ function buildTriggerTrace(trigger, outcome, reason) {
|
|
|
10167
10341
|
for (const [key, value] of Object.entries(trigger)) {
|
|
10168
10342
|
if (key === "kind" || key === "at") continue;
|
|
10169
10343
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null) vars[key] = value;
|
|
10344
|
+
else if (Array.isArray(value) && value.every((v) => typeof v === "string" || typeof v === "number")) vars[key] = value;
|
|
10170
10345
|
}
|
|
10171
10346
|
return {
|
|
10172
10347
|
kind: typeof trigger["kind"] === "string" ? trigger["kind"] : "unknown",
|
|
@@ -14730,7 +14905,7 @@ var CompositionProblemCodeSchema = zod.z.enum([
|
|
|
14730
14905
|
"member-constraint",
|
|
14731
14906
|
"retention"
|
|
14732
14907
|
]);
|
|
14733
|
-
/** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.
|
|
14908
|
+
/** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.temperature`. */
|
|
14734
14909
|
var CompositionProblemSchema = zod.z.object({
|
|
14735
14910
|
code: CompositionProblemCodeSchema,
|
|
14736
14911
|
path: zod.z.string(),
|
|
@@ -15436,7 +15611,7 @@ var GROUP_TEMPLATES = [
|
|
|
15436
15611
|
summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
|
|
15437
15612
|
role: require_err_msg.DeviceRole.TemperatureSensor,
|
|
15438
15613
|
outputCap: "temperature-sensor",
|
|
15439
|
-
outputField: "
|
|
15614
|
+
outputField: "temperature",
|
|
15440
15615
|
quantity: "temperature",
|
|
15441
15616
|
defaultAggregate: "mean",
|
|
15442
15617
|
empty: "null",
|
|
@@ -15447,7 +15622,7 @@ var GROUP_TEMPLATES = [
|
|
|
15447
15622
|
summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
|
|
15448
15623
|
role: require_err_msg.DeviceRole.HumiditySensor,
|
|
15449
15624
|
outputCap: "humidity-sensor",
|
|
15450
|
-
outputField: "
|
|
15625
|
+
outputField: "humidity",
|
|
15451
15626
|
quantity: "humidity",
|
|
15452
15627
|
defaultAggregate: "mean",
|
|
15453
15628
|
empty: "null",
|
|
@@ -15458,7 +15633,7 @@ var GROUP_TEMPLATES = [
|
|
|
15458
15633
|
summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
|
|
15459
15634
|
role: require_err_msg.DeviceRole.PressureSensor,
|
|
15460
15635
|
outputCap: "pressure-sensor",
|
|
15461
|
-
outputField: "
|
|
15636
|
+
outputField: "pressure",
|
|
15462
15637
|
quantity: "pressure",
|
|
15463
15638
|
defaultAggregate: "mean",
|
|
15464
15639
|
empty: "null",
|
|
@@ -15469,7 +15644,7 @@ var GROUP_TEMPLATES = [
|
|
|
15469
15644
|
summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
|
|
15470
15645
|
role: require_err_msg.DeviceRole.AmbientLightSensor,
|
|
15471
15646
|
outputCap: "ambient-light-sensor",
|
|
15472
|
-
outputField: "
|
|
15647
|
+
outputField: "illuminance",
|
|
15473
15648
|
quantity: "illuminance",
|
|
15474
15649
|
defaultAggregate: "mean",
|
|
15475
15650
|
empty: "null",
|
|
@@ -15480,7 +15655,7 @@ var GROUP_TEMPLATES = [
|
|
|
15480
15655
|
summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
|
|
15481
15656
|
role: require_err_msg.DeviceRole.PowerSensor,
|
|
15482
15657
|
outputCap: "power-meter",
|
|
15483
|
-
outputField: "
|
|
15658
|
+
outputField: "power",
|
|
15484
15659
|
quantity: "power",
|
|
15485
15660
|
defaultAggregate: "sum",
|
|
15486
15661
|
empty: "omit",
|
|
@@ -15491,7 +15666,7 @@ var GROUP_TEMPLATES = [
|
|
|
15491
15666
|
summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
|
|
15492
15667
|
role: require_err_msg.DeviceRole.EnergySensor,
|
|
15493
15668
|
outputCap: "power-meter",
|
|
15494
|
-
outputField: "
|
|
15669
|
+
outputField: "energy",
|
|
15495
15670
|
quantity: "energy",
|
|
15496
15671
|
defaultAggregate: "sum",
|
|
15497
15672
|
empty: "omit",
|
|
@@ -15502,7 +15677,7 @@ var GROUP_TEMPLATES = [
|
|
|
15502
15677
|
summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
|
|
15503
15678
|
role: require_err_msg.DeviceRole.BatterySensor,
|
|
15504
15679
|
outputCap: "battery",
|
|
15505
|
-
outputField: "
|
|
15680
|
+
outputField: "level",
|
|
15506
15681
|
quantity: "percentage",
|
|
15507
15682
|
defaultAggregate: "mean",
|
|
15508
15683
|
empty: "null",
|
|
@@ -15956,17 +16131,17 @@ async function runFanOut(calls, invoke) {
|
|
|
15956
16131
|
//#region src/generated/history-catalog.ts
|
|
15957
16132
|
/**
|
|
15958
16133
|
* Cap history catalog, projected from every `*.cap.ts` `history` block.
|
|
15959
|
-
*
|
|
16134
|
+
* 41 caps with series (76 series), 34 caps answered `'none'`.
|
|
15960
16135
|
* Field keys are `runtimeState` paths (`a.b` into fixed objects only).
|
|
15961
16136
|
*/
|
|
15962
16137
|
var HISTORY_CATALOG = {
|
|
15963
16138
|
"air-quality-sensor": {
|
|
15964
|
-
"
|
|
16139
|
+
"co2": {
|
|
15965
16140
|
kind: "gauge",
|
|
15966
16141
|
unit: "ppm",
|
|
15967
16142
|
deadband: 5
|
|
15968
16143
|
},
|
|
15969
|
-
"
|
|
16144
|
+
"voc": {
|
|
15970
16145
|
kind: "gauge",
|
|
15971
16146
|
unit: "ppb",
|
|
15972
16147
|
deadband: 5
|
|
@@ -16002,24 +16177,24 @@ var HISTORY_CATALOG = {
|
|
|
16002
16177
|
"triggered"
|
|
16003
16178
|
]
|
|
16004
16179
|
} },
|
|
16005
|
-
"ambient-light-sensor": { "
|
|
16180
|
+
"ambient-light-sensor": { "illuminance": {
|
|
16006
16181
|
kind: "gauge",
|
|
16007
16182
|
unit: "lx",
|
|
16008
16183
|
deadbandPct: 10,
|
|
16009
16184
|
minIntervalMs: 6e4
|
|
16010
16185
|
} },
|
|
16011
16186
|
"battery": {
|
|
16012
|
-
"
|
|
16187
|
+
"level": {
|
|
16013
16188
|
kind: "gauge",
|
|
16014
16189
|
unit: "%"
|
|
16015
16190
|
},
|
|
16016
|
-
"
|
|
16191
|
+
"voltage": {
|
|
16017
16192
|
kind: "gauge",
|
|
16018
16193
|
unit: "V"
|
|
16019
16194
|
}
|
|
16020
16195
|
},
|
|
16021
16196
|
"binary": { "on": { kind: "boolean" } },
|
|
16022
|
-
"brightness": { "
|
|
16197
|
+
"brightness": { "level": {
|
|
16023
16198
|
kind: "gauge",
|
|
16024
16199
|
unit: "%",
|
|
16025
16200
|
minIntervalMs: 0
|
|
@@ -16061,7 +16236,7 @@ var HISTORY_CATALOG = {
|
|
|
16061
16236
|
} },
|
|
16062
16237
|
"device-status": { "online": { kind: "boolean" } },
|
|
16063
16238
|
"dishwasher": {
|
|
16064
|
-
"
|
|
16239
|
+
"progress": {
|
|
16065
16240
|
kind: "gauge",
|
|
16066
16241
|
unit: "%",
|
|
16067
16242
|
deadband: 2
|
|
@@ -16091,7 +16266,7 @@ var HISTORY_CATALOG = {
|
|
|
16091
16266
|
"Aborting"
|
|
16092
16267
|
]
|
|
16093
16268
|
},
|
|
16094
|
-
"
|
|
16269
|
+
"remaining": {
|
|
16095
16270
|
kind: "gauge",
|
|
16096
16271
|
unit: "s",
|
|
16097
16272
|
minIntervalMs: 6e4
|
|
@@ -16104,7 +16279,7 @@ var HISTORY_CATALOG = {
|
|
|
16104
16279
|
skipWhenSet: "timestamp"
|
|
16105
16280
|
} },
|
|
16106
16281
|
"fan-control": {
|
|
16107
|
-
"
|
|
16282
|
+
"speed": {
|
|
16108
16283
|
kind: "gauge",
|
|
16109
16284
|
unit: "%",
|
|
16110
16285
|
minIntervalMs: 0
|
|
@@ -16126,7 +16301,7 @@ var HISTORY_CATALOG = {
|
|
|
16126
16301
|
},
|
|
16127
16302
|
"on": { kind: "boolean" }
|
|
16128
16303
|
},
|
|
16129
|
-
"humidity-sensor": { "
|
|
16304
|
+
"humidity-sensor": { "humidity": {
|
|
16130
16305
|
kind: "gauge",
|
|
16131
16306
|
unit: "%",
|
|
16132
16307
|
deadband: .5
|
|
@@ -16164,13 +16339,13 @@ var HISTORY_CATALOG = {
|
|
|
16164
16339
|
excludeDeviceTypes: [require_err_msg.DeviceType.Camera]
|
|
16165
16340
|
} },
|
|
16166
16341
|
"network-link": {
|
|
16167
|
-
"
|
|
16342
|
+
"signal": {
|
|
16168
16343
|
kind: "gauge",
|
|
16169
16344
|
unit: "%",
|
|
16170
16345
|
deadband: 3,
|
|
16171
16346
|
minIntervalMs: 6e4
|
|
16172
16347
|
},
|
|
16173
|
-
"
|
|
16348
|
+
"rssi": {
|
|
16174
16349
|
kind: "gauge",
|
|
16175
16350
|
unit: "dBm",
|
|
16176
16351
|
deadband: 2,
|
|
@@ -16197,31 +16372,31 @@ var HISTORY_CATALOG = {
|
|
|
16197
16372
|
}
|
|
16198
16373
|
},
|
|
16199
16374
|
"power-meter": {
|
|
16200
|
-
"
|
|
16375
|
+
"power": {
|
|
16201
16376
|
kind: "gauge",
|
|
16202
16377
|
unit: "W",
|
|
16203
16378
|
deadband: 1,
|
|
16204
16379
|
minIntervalMs: 3e4
|
|
16205
16380
|
},
|
|
16206
|
-
"
|
|
16381
|
+
"energy": {
|
|
16207
16382
|
kind: "counter",
|
|
16208
16383
|
unit: "kWh",
|
|
16209
16384
|
deadband: .01,
|
|
16210
16385
|
minIntervalMs: 6e4
|
|
16211
16386
|
},
|
|
16212
|
-
"
|
|
16387
|
+
"voltage": {
|
|
16213
16388
|
kind: "gauge",
|
|
16214
16389
|
unit: "V",
|
|
16215
16390
|
deadband: 1,
|
|
16216
16391
|
minIntervalMs: 3e4
|
|
16217
16392
|
},
|
|
16218
|
-
"
|
|
16393
|
+
"current": {
|
|
16219
16394
|
kind: "gauge",
|
|
16220
16395
|
unit: "A",
|
|
16221
16396
|
deadband: .05,
|
|
16222
16397
|
minIntervalMs: 3e4
|
|
16223
16398
|
},
|
|
16224
|
-
"
|
|
16399
|
+
"apparentPower": {
|
|
16225
16400
|
kind: "gauge",
|
|
16226
16401
|
unit: "VA",
|
|
16227
16402
|
deadband: 1,
|
|
@@ -16234,12 +16409,7 @@ var HISTORY_CATALOG = {
|
|
|
16234
16409
|
minIntervalMs: 6e4
|
|
16235
16410
|
}
|
|
16236
16411
|
},
|
|
16237
|
-
"
|
|
16238
|
-
kind: "gauge",
|
|
16239
|
-
unit: "%",
|
|
16240
|
-
deadband: 1
|
|
16241
|
-
} },
|
|
16242
|
-
"pressure-sensor": { "hpa": {
|
|
16412
|
+
"pressure-sensor": { "pressure": {
|
|
16243
16413
|
kind: "gauge",
|
|
16244
16414
|
unit: "hPa",
|
|
16245
16415
|
deadband: .1
|
|
@@ -16266,7 +16436,7 @@ var HISTORY_CATALOG = {
|
|
|
16266
16436
|
},
|
|
16267
16437
|
"switch": { "on": { kind: "boolean" } },
|
|
16268
16438
|
"tamper": { "tampered": { kind: "boolean" } },
|
|
16269
|
-
"temperature-sensor": { "
|
|
16439
|
+
"temperature-sensor": { "temperature": {
|
|
16270
16440
|
kind: "gauge",
|
|
16271
16441
|
unit: "°C",
|
|
16272
16442
|
deadband: .1
|
|
@@ -16374,6 +16544,7 @@ var HISTORY_NONE_CAPS = [
|
|
|
16374
16544
|
"native-object-detection",
|
|
16375
16545
|
"navigation",
|
|
16376
16546
|
"notifier",
|
|
16547
|
+
"presence",
|
|
16377
16548
|
"privacy-mask",
|
|
16378
16549
|
"ptz-autotrack",
|
|
16379
16550
|
"recording-onboard",
|
|
@@ -16511,10 +16682,10 @@ function derivativeDraft(spec, retention = require_composition.DEFAULT_HISTORY_R
|
|
|
16511
16682
|
function integralDraft(spec, retention = require_composition.DEFAULT_HISTORY_RETENTION) {
|
|
16512
16683
|
const problems = [...historyProblems(spec.source)];
|
|
16513
16684
|
const kind = declaredSeriesKind(spec.source);
|
|
16514
|
-
const
|
|
16685
|
+
const isPower = spec.source.cap === "power-meter" && spec.source.fieldPath === "power";
|
|
16515
16686
|
if (kind === "counter" || kind === "boolean" || kind === "enum") problems.push(problem("kind-mismatch", "helper.source", "an integral reads an instantaneous gauge, not a counter or a state"));
|
|
16516
|
-
if (spec.output === "energy" && !
|
|
16517
|
-
if (spec.output === "meter" &&
|
|
16687
|
+
if (spec.output === "energy" && !isPower) problems.push(problem("kind-mismatch", "helper.output", "energy is the integral of power-meter.power; another reading uses the meter"));
|
|
16688
|
+
if (spec.output === "meter" && isPower) problems.push(problem("kind-mismatch", "helper.output", "power integrates to power-meter.energy, which energy dashboards already read"));
|
|
16518
16689
|
const tier = require_composition.planResetTier(spec.reset, retention);
|
|
16519
16690
|
if (!tier.ok) problems.push(problem("retention", "helper.reset", tier.reason));
|
|
16520
16691
|
const energy = spec.output === "energy";
|
|
@@ -16523,7 +16694,7 @@ function integralDraft(spec, retention = require_composition.DEFAULT_HISTORY_RET
|
|
|
16523
16694
|
draft: draft({
|
|
16524
16695
|
role: energy ? require_err_msg.DeviceRole.EnergySensor : require_err_msg.DeviceRole.NumericSensor,
|
|
16525
16696
|
cap: energy ? "power-meter" : "meter",
|
|
16526
|
-
field: energy ? "
|
|
16697
|
+
field: energy ? "energy" : "value",
|
|
16527
16698
|
empty: "offline",
|
|
16528
16699
|
spec,
|
|
16529
16700
|
member: spec.source
|
|
@@ -16782,9 +16953,9 @@ function expressionSource(expr, bindings = {}) {
|
|
|
16782
16953
|
}
|
|
16783
16954
|
function compileAppliance(spec) {
|
|
16784
16955
|
const member = spec.member;
|
|
16785
|
-
if (member.cap !== "power-meter" || member.fieldPath !== "
|
|
16956
|
+
if (member.cap !== "power-meter" || member.fieldPath !== "power") return {
|
|
16786
16957
|
ok: false,
|
|
16787
|
-
problems: [problem("member-constraint", "helper.member", "an appliance helper reads power-meter.
|
|
16958
|
+
problems: [problem("member-constraint", "helper.member", "an appliance helper reads power-meter.power")]
|
|
16788
16959
|
};
|
|
16789
16960
|
const threshold = unsignedToken(spec.thresholdWatts);
|
|
16790
16961
|
const start = unsignedToken(spec.startDwellMs);
|
|
@@ -16825,7 +16996,7 @@ function compileAppliance(spec) {
|
|
|
16825
16996
|
kind: "fields",
|
|
16826
16997
|
cap: "power-meter",
|
|
16827
16998
|
fields: {
|
|
16828
|
-
|
|
16999
|
+
power: read,
|
|
16829
17000
|
lastFetchedAt: clock
|
|
16830
17001
|
}
|
|
16831
17002
|
}]
|
|
@@ -19051,8 +19222,8 @@ var DeviceHistoryProducerKindSchema = zod.z.enum([
|
|
|
19051
19222
|
/**
|
|
19052
19223
|
* A key computed at READ time from stored series of the same device, never
|
|
19053
19224
|
* stored itself. Energy (kWh per bucket) comes from the power meter's
|
|
19054
|
-
* `
|
|
19055
|
-
* `
|
|
19225
|
+
* `energy` counter when the device has one, else from integrating its
|
|
19226
|
+
* `power` gauge over time.
|
|
19056
19227
|
*/
|
|
19057
19228
|
var DEVICE_HISTORY_ENERGY_KEY = "derived:energy.kwh";
|
|
19058
19229
|
var DeviceHistoryResolutionSchema = zod.z.enum([
|
|
@@ -64091,9 +64262,9 @@ var addonsCapability = {
|
|
|
64091
64262
|
*/
|
|
64092
64263
|
var AirQualitySensorStatusSchema = zod.z.object({
|
|
64093
64264
|
/** Carbon dioxide concentration in ppm. */
|
|
64094
|
-
|
|
64265
|
+
co2: zod.z.number().min(0).optional().meta(require_sleep.readingMeta("concentration-volume")),
|
|
64095
64266
|
/** Total volatile organic compounds in ppb. */
|
|
64096
|
-
|
|
64267
|
+
voc: zod.z.number().min(0).optional().meta(require_sleep.readingMeta("concentration-ppb")),
|
|
64097
64268
|
/** Particulate matter ≤ 2.5 μm in µg/m³. */
|
|
64098
64269
|
pm25: zod.z.number().min(0).optional().meta(require_sleep.readingMeta("concentration-mass")),
|
|
64099
64270
|
/** Particulate matter ≤ 10 μm in µg/m³. */
|
|
@@ -64126,12 +64297,12 @@ var airQualitySensorCapability = {
|
|
|
64126
64297
|
* History (D850): recorded series.
|
|
64127
64298
|
*/
|
|
64128
64299
|
history: { fields: {
|
|
64129
|
-
|
|
64300
|
+
co2: {
|
|
64130
64301
|
kind: "gauge",
|
|
64131
64302
|
unit: "ppm",
|
|
64132
64303
|
deadband: 5
|
|
64133
64304
|
},
|
|
64134
|
-
|
|
64305
|
+
voc: {
|
|
64135
64306
|
kind: "gauge",
|
|
64136
64307
|
unit: "ppb",
|
|
64137
64308
|
deadband: 5
|
|
@@ -64164,7 +64335,7 @@ var airQualitySensorCapability = {
|
|
|
64164
64335
|
*/
|
|
64165
64336
|
var AmbientLightSensorStatusSchema = zod.z.object({
|
|
64166
64337
|
/** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
|
|
64167
|
-
|
|
64338
|
+
illuminance: zod.z.number().min(0).nullable().meta(require_sleep.readingMeta("illuminance", {
|
|
64168
64339
|
precision: 0,
|
|
64169
64340
|
label: "Illuminance"
|
|
64170
64341
|
})),
|
|
@@ -64193,7 +64364,7 @@ var ambientLightSensorCapability = {
|
|
|
64193
64364
|
/**
|
|
64194
64365
|
* History (D850): recorded series. Wide range, so a RELATIVE deadband (10 %).
|
|
64195
64366
|
*/
|
|
64196
|
-
history: { fields: {
|
|
64367
|
+
history: { fields: { illuminance: {
|
|
64197
64368
|
kind: "gauge",
|
|
64198
64369
|
unit: "lx",
|
|
64199
64370
|
deadbandPct: 10,
|
|
@@ -64589,7 +64760,7 @@ var BatteryStatusSchema = zod.z.object({
|
|
|
64589
64760
|
* Consumers must SKIP a null rather than coerce it — `battery-band` already
|
|
64590
64761
|
* declines to band a non-finite reading, which is the correct shape.
|
|
64591
64762
|
*/
|
|
64592
|
-
|
|
64763
|
+
level: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage", { label: "Charge" })),
|
|
64593
64764
|
/**
|
|
64594
64765
|
* Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
|
|
64595
64766
|
* Reolink-specific for the Solar Panel 2 accessory (will become
|
|
@@ -64599,7 +64770,7 @@ var BatteryStatusSchema = zod.z.object({
|
|
|
64599
64770
|
* `'unknown'` is the absence of an answer: the provider has not read the
|
|
64600
64771
|
* power fields yet, or the firmware reported neither. Before it existed,
|
|
64601
64772
|
* two unreadable fields produced `'none'`, and a camera nobody had reached
|
|
64602
|
-
* was drawn "on battery alone" — the same D315 defect `
|
|
64773
|
+
* was drawn "on battery alone" — the same D315 defect `level` closed
|
|
64603
64774
|
* by going nullable. Consumers SKIP it (no charger state published, no
|
|
64604
64775
|
* glyph, no HomeKit ChargingState) rather than coerce it either way.
|
|
64605
64776
|
*/
|
|
@@ -64623,7 +64794,7 @@ var BatteryStatusSchema = zod.z.object({
|
|
|
64623
64794
|
* that only report charge. A second reading on this cap, not a second
|
|
64624
64795
|
* `numeric-sensor` on the same device.
|
|
64625
64796
|
*/
|
|
64626
|
-
|
|
64797
|
+
voltage: zod.z.number().nullable().optional().meta(require_sleep.readingMeta("voltage", { label: "Voltage" })),
|
|
64627
64798
|
/**
|
|
64628
64799
|
* Ms epoch of the last time the device PROVED it was reachable — a
|
|
64629
64800
|
* completed firmware round-trip, an observed wake, or an inbound push
|
|
@@ -64657,7 +64828,7 @@ var BatteryStatusSchema = zod.z.object({
|
|
|
64657
64828
|
/**
|
|
64658
64829
|
* True when the source is a BINARY low-battery indicator (HA
|
|
64659
64830
|
* `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
|
|
64660
|
-
* charge level — `
|
|
64831
|
+
* charge level — `level` is then a coarse stand-in (100 = normal,
|
|
64661
64832
|
* sub-threshold = low). UI MUST render "Normal"/"Low" instead of a
|
|
64662
64833
|
* misleading exact percentage. Absent/false → genuine 0–100 % reading.
|
|
64663
64834
|
*/
|
|
@@ -64677,15 +64848,15 @@ var BATTERY_DEVICE_TYPES = [
|
|
|
64677
64848
|
var batteryCustomize = {
|
|
64678
64849
|
version: 1,
|
|
64679
64850
|
targets: [{
|
|
64680
|
-
id: "
|
|
64851
|
+
id: "level",
|
|
64681
64852
|
cardinality: "scalar",
|
|
64682
64853
|
vocabulary: {
|
|
64683
64854
|
cap: "battery",
|
|
64684
|
-
stateField: "
|
|
64855
|
+
stateField: "level"
|
|
64685
64856
|
},
|
|
64686
64857
|
deviceTypes: BATTERY_DEVICE_TYPES,
|
|
64687
64858
|
apply: "add",
|
|
64688
|
-
path: "
|
|
64859
|
+
path: "level",
|
|
64689
64860
|
sources: [{
|
|
64690
64861
|
kind: "field",
|
|
64691
64862
|
valueKinds: ["number", "string"],
|
|
@@ -64779,7 +64950,7 @@ onStatusChanged: { data: zod.z.object({
|
|
|
64779
64950
|
schema: BatteryStatusSchema,
|
|
64780
64951
|
kind: "push",
|
|
64781
64952
|
empty: {
|
|
64782
|
-
|
|
64953
|
+
level: null,
|
|
64783
64954
|
charging: "unknown",
|
|
64784
64955
|
sleeping: false,
|
|
64785
64956
|
lastUpdated: 0
|
|
@@ -64806,11 +64977,11 @@ onStatusChanged: { data: zod.z.object({
|
|
|
64806
64977
|
* History (D850): recorded series. The generic capture replaces the `battery` series of `pipeline-analytics:stats-5m`.
|
|
64807
64978
|
*/
|
|
64808
64979
|
history: { fields: {
|
|
64809
|
-
|
|
64980
|
+
level: {
|
|
64810
64981
|
kind: "gauge",
|
|
64811
64982
|
unit: "%"
|
|
64812
64983
|
},
|
|
64813
|
-
|
|
64984
|
+
voltage: {
|
|
64814
64985
|
kind: "gauge",
|
|
64815
64986
|
unit: "V"
|
|
64816
64987
|
}
|
|
@@ -64874,14 +65045,14 @@ var binaryCapability = {
|
|
|
64874
65045
|
* controls to this single-method surface.
|
|
64875
65046
|
*
|
|
64876
65047
|
* The cap is intentionally minimal: a single `setBrightness({deviceId,
|
|
64877
|
-
*
|
|
65048
|
+
* level})` mutation plus the auto-injected `getStatus`. Drivers
|
|
64878
65049
|
* that expose richer controls (color temperature, scenes, schedules)
|
|
64879
65050
|
* should surface those via the device's `getSettingsUISchema()`
|
|
64880
65051
|
* instead of bloating this cap.
|
|
64881
65052
|
*/
|
|
64882
65053
|
var BrightnessStatusSchema = zod.z.object({
|
|
64883
65054
|
/** Current level as 0..100 inclusive. Firmware-reported. */
|
|
64884
|
-
|
|
65055
|
+
level: zod.z.number().min(0).max(100).meta(require_sleep.readingMeta("percentage")),
|
|
64885
65056
|
/** Ms epoch of the last operator-driven change. Useful for UI freshness. */
|
|
64886
65057
|
lastChangedAt: zod.z.number()
|
|
64887
65058
|
});
|
|
@@ -64893,7 +65064,7 @@ var brightnessCapability = {
|
|
|
64893
65064
|
deviceTypes: [require_err_msg.DeviceType.Light],
|
|
64894
65065
|
methods: { setBrightness: require_sleep.method(zod.z.object({
|
|
64895
65066
|
deviceId: zod.z.number().int().nonnegative(),
|
|
64896
|
-
|
|
65067
|
+
level: zod.z.number().min(0).max(100)
|
|
64897
65068
|
}), zod.z.void(), {
|
|
64898
65069
|
kind: "mutation",
|
|
64899
65070
|
auth: "protected"
|
|
@@ -64906,7 +65077,7 @@ var brightnessCapability = {
|
|
|
64906
65077
|
*/
|
|
64907
65078
|
onBrightnessChanged: { data: zod.z.object({
|
|
64908
65079
|
deviceId: zod.z.number(),
|
|
64909
|
-
|
|
65080
|
+
level: zod.z.number().min(0).max(100),
|
|
64910
65081
|
lastChangedAt: zod.z.number()
|
|
64911
65082
|
}) } },
|
|
64912
65083
|
status: {
|
|
@@ -64929,7 +65100,7 @@ onBrightnessChanged: { data: zod.z.object({
|
|
|
64929
65100
|
/**
|
|
64930
65101
|
* History (D850): recorded series. Actuator tier: level keeps every change.
|
|
64931
65102
|
*/
|
|
64932
|
-
history: { fields: {
|
|
65103
|
+
history: { fields: { level: {
|
|
64933
65104
|
kind: "gauge",
|
|
64934
65105
|
unit: "%",
|
|
64935
65106
|
minIntervalMs: 0
|
|
@@ -65673,11 +65844,11 @@ var ColorInputSchema = zod.z.discriminatedUnion("mode", [
|
|
|
65673
65844
|
}),
|
|
65674
65845
|
zod.z.object({
|
|
65675
65846
|
mode: zod.z.literal("mired"),
|
|
65676
|
-
|
|
65847
|
+
colorTemperature: zod.z.number().int().min(50).max(1e3)
|
|
65677
65848
|
})
|
|
65678
65849
|
]);
|
|
65679
65850
|
var ColorStatusSchema = zod.z.object({
|
|
65680
|
-
/** Active color mode — which of `rgb` / `hsv` / `
|
|
65851
|
+
/** Active color mode — which of `rgb` / `hsv` / `colorTemperature` reflects
|
|
65681
65852
|
* the bulb's current state. */
|
|
65682
65853
|
mode: zod.z.enum([
|
|
65683
65854
|
"rgb",
|
|
@@ -65689,7 +65860,7 @@ var ColorStatusSchema = zod.z.object({
|
|
|
65689
65860
|
/** Populated when `mode === 'hsv'`. */
|
|
65690
65861
|
hsv: HsvTripletSchema.optional(),
|
|
65691
65862
|
/** Populated when `mode === 'mired'`. */
|
|
65692
|
-
|
|
65863
|
+
colorTemperature: zod.z.number().int().optional().meta(require_sleep.readingMeta("colour-temperature")),
|
|
65693
65864
|
/** Ms epoch of the last operator-driven change. */
|
|
65694
65865
|
lastChangedAt: zod.z.number()
|
|
65695
65866
|
});
|
|
@@ -65725,7 +65896,7 @@ onColorChanged: { data: zod.z.object({
|
|
|
65725
65896
|
]),
|
|
65726
65897
|
rgb: RgbTripletSchema.optional(),
|
|
65727
65898
|
hsv: HsvTripletSchema.optional(),
|
|
65728
|
-
|
|
65899
|
+
colorTemperature: zod.z.number().int().optional(),
|
|
65729
65900
|
lastChangedAt: zod.z.number()
|
|
65730
65901
|
}) } },
|
|
65731
65902
|
status: {
|
|
@@ -66758,11 +66929,11 @@ var DishwasherStatusSchema = zod.z.object({
|
|
|
66758
66929
|
/** Feature keys of the programs the appliance currently offers — drives the UI selector. */
|
|
66759
66930
|
availablePrograms: zod.z.array(zod.z.string()),
|
|
66760
66931
|
/** Seconds remaining on the active cycle, or null when not running / not known. */
|
|
66761
|
-
|
|
66932
|
+
remaining: zod.z.number().int().nonnegative().nullable().meta(require_sleep.readingMeta("duration")),
|
|
66762
66933
|
/** Seconds the delayed start is set to (time until the cycle starts), or null. */
|
|
66763
|
-
|
|
66934
|
+
delayedStart: zod.z.number().int().nonnegative().nullable().meta(require_sleep.readingMeta("duration")),
|
|
66764
66935
|
/** Cycle completion 0..100, or null when not applicable. */
|
|
66765
|
-
|
|
66936
|
+
progress: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
|
|
66766
66937
|
/** True when the door is reported open; null when the door state is not known. */
|
|
66767
66938
|
doorOpen: zod.z.boolean().nullable(),
|
|
66768
66939
|
/** True when the door is latched (locked) during a cycle; null when not reported. */
|
|
@@ -66981,14 +67152,14 @@ var dishwasherCapability = {
|
|
|
66981
67152
|
runtimeState: DishwasherStatusSchema,
|
|
66982
67153
|
durability: "session",
|
|
66983
67154
|
history: { fields: {
|
|
66984
|
-
|
|
67155
|
+
progress: {
|
|
66985
67156
|
kind: "gauge",
|
|
66986
67157
|
unit: "%",
|
|
66987
67158
|
deadband: 2
|
|
66988
67159
|
},
|
|
66989
67160
|
state: { kind: "enum" },
|
|
66990
67161
|
operationState: { kind: "enum" },
|
|
66991
|
-
|
|
67162
|
+
remaining: {
|
|
66992
67163
|
kind: "gauge",
|
|
66993
67164
|
unit: "s",
|
|
66994
67165
|
minIntervalMs: 6e4
|
|
@@ -67641,7 +67812,7 @@ var FanDirectionSchema = zod.z.enum(["forward", "reverse"]);
|
|
|
67641
67812
|
var FanControlStatusSchema = zod.z.object({
|
|
67642
67813
|
/** Active speed as 0..100 inclusive. Null when the device has no
|
|
67643
67814
|
* speed surface (single-speed fan). */
|
|
67644
|
-
|
|
67815
|
+
speed: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
|
|
67645
67816
|
/** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
|
|
67646
67817
|
* a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
|
|
67647
67818
|
percentageStep: zod.z.number().positive().optional().meta(require_sleep.readingMeta("percentage")),
|
|
@@ -67668,7 +67839,7 @@ var fanControlCapability = {
|
|
|
67668
67839
|
methods: {
|
|
67669
67840
|
setPercentage: require_sleep.method(zod.z.object({
|
|
67670
67841
|
deviceId: zod.z.number().int().nonnegative(),
|
|
67671
|
-
|
|
67842
|
+
speed: zod.z.number().min(0).max(100)
|
|
67672
67843
|
}), zod.z.void(), {
|
|
67673
67844
|
kind: "mutation",
|
|
67674
67845
|
auth: "protected"
|
|
@@ -67701,7 +67872,7 @@ var fanControlCapability = {
|
|
|
67701
67872
|
},
|
|
67702
67873
|
/**
|
|
67703
67874
|
* Runtime-state slice — mirrored by the kernel. UI fan speed
|
|
67704
|
-
* sliders read `
|
|
67875
|
+
* sliders read `speed` for live updates.
|
|
67705
67876
|
*/
|
|
67706
67877
|
runtimeState: FanControlStatusSchema,
|
|
67707
67878
|
/**
|
|
@@ -67715,7 +67886,7 @@ var fanControlCapability = {
|
|
|
67715
67886
|
* History (D850): recorded series. Actuator tier: speed keeps every change.
|
|
67716
67887
|
*/
|
|
67717
67888
|
history: { fields: {
|
|
67718
|
-
|
|
67889
|
+
speed: {
|
|
67719
67890
|
kind: "gauge",
|
|
67720
67891
|
unit: "%",
|
|
67721
67892
|
minIntervalMs: 0
|
|
@@ -68185,7 +68356,7 @@ var humidifierCapability = {
|
|
|
68185
68356
|
*/
|
|
68186
68357
|
var HumiditySensorStatusSchema = zod.z.object({
|
|
68187
68358
|
/** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
|
|
68188
|
-
|
|
68359
|
+
humidity: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("humidity", {
|
|
68189
68360
|
precision: 0,
|
|
68190
68361
|
label: "Humidity"
|
|
68191
68362
|
})),
|
|
@@ -68214,7 +68385,7 @@ var humiditySensorCapability = {
|
|
|
68214
68385
|
/**
|
|
68215
68386
|
* History (D850): recorded series.
|
|
68216
68387
|
*/
|
|
68217
|
-
history: { fields: {
|
|
68388
|
+
history: { fields: { humidity: {
|
|
68218
68389
|
kind: "gauge",
|
|
68219
68390
|
unit: "%",
|
|
68220
68391
|
deadband: .5
|
|
@@ -68882,7 +69053,7 @@ var LawnMowerControlStatusSchema = zod.z.object({
|
|
|
68882
69053
|
batteryLevel: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
|
|
68883
69054
|
/** 0..100 mowing-completion percentage of the current task, or null when no
|
|
68884
69055
|
* task is active / progress is unavailable. */
|
|
68885
|
-
|
|
69056
|
+
progress: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
|
|
68886
69057
|
/** Current device/event code (dynamic — mostly status, sometimes an error),
|
|
68887
69058
|
* or null when unknown. */
|
|
68888
69059
|
currentCode: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
|
|
@@ -69579,7 +69750,7 @@ var lockControlCapability = {
|
|
|
69579
69750
|
//#endregion
|
|
69580
69751
|
//#region src/capabilities/meter.cap.ts
|
|
69581
69752
|
/**
|
|
69582
|
-
* A monotonic total between resets (D944). Energy uses `power-meter.
|
|
69753
|
+
* A monotonic total between resets (D944). Energy uses `power-meter.energy`
|
|
69583
69754
|
* so dashboards keep one series; every other instantaneous reading lands here.
|
|
69584
69755
|
* `value` is the time integral in the source quantity's accumulated base
|
|
69585
69756
|
* (base-unit seconds, or kWh on the power meter). `null` is a gap, never zero.
|
|
@@ -69675,10 +69846,10 @@ var MediaPlayerStatusSchema = zod.z.object({
|
|
|
69675
69846
|
currentMedia: MediaInfoSchema.nullable(),
|
|
69676
69847
|
/** Current playback position in ms. Null when not seekable or
|
|
69677
69848
|
* nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
|
|
69678
|
-
|
|
69849
|
+
position: zod.z.number().int().nonnegative().nullable().meta(require_sleep.readingMeta("duration-ms")),
|
|
69679
69850
|
/** Total duration of the current media in ms. Null when unknown
|
|
69680
69851
|
* (live stream) or nothing is playing. */
|
|
69681
|
-
|
|
69852
|
+
duration: zod.z.number().int().nonnegative().nullable().meta(require_sleep.readingMeta("duration-ms")),
|
|
69682
69853
|
/** Shuffle toggle. Null when no shuffle surface.
|
|
69683
69854
|
* Pair with `DeviceFeature.MediaPlayerShuffle`. */
|
|
69684
69855
|
shuffle: zod.z.boolean().nullable(),
|
|
@@ -69717,7 +69888,7 @@ var mediaPlayerCapability = {
|
|
|
69717
69888
|
}),
|
|
69718
69889
|
seek: require_sleep.method(zod.z.object({
|
|
69719
69890
|
deviceId: zod.z.number().int().nonnegative(),
|
|
69720
|
-
|
|
69891
|
+
position: zod.z.number().int().nonnegative()
|
|
69721
69892
|
}), zod.z.void(), {
|
|
69722
69893
|
kind: "mutation",
|
|
69723
69894
|
auth: "protected"
|
|
@@ -69789,7 +69960,7 @@ var mediaPlayerCapability = {
|
|
|
69789
69960
|
/**
|
|
69790
69961
|
* History (D850): volume (a level an operator or an automation sets; the
|
|
69791
69962
|
* default 10 s floor folds a slider drag into its end value) and mute.
|
|
69792
|
-
* `
|
|
69963
|
+
* `position` is not recorded: it moves continuously while playing.
|
|
69793
69964
|
*/
|
|
69794
69965
|
history: { fields: {
|
|
69795
69966
|
volumeLevel: {
|
|
@@ -71014,9 +71185,9 @@ var NetworkLinkStatusSchema = zod.z.object({
|
|
|
71014
71185
|
* one whose reading has not landed must not be drawn at 0 %. Consumers
|
|
71015
71186
|
* SKIP a null rather than coerce it.
|
|
71016
71187
|
*/
|
|
71017
|
-
|
|
71188
|
+
signal: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
|
|
71018
71189
|
/** Raw received signal strength in dBm, when the firmware reports one. */
|
|
71019
|
-
|
|
71190
|
+
rssi: zod.z.number().optional().meta(require_sleep.readingMeta("signal-level")),
|
|
71020
71191
|
/** Network name of a wireless link, when the firmware reports it. */
|
|
71021
71192
|
ssid: zod.z.string().optional(),
|
|
71022
71193
|
/** Ms epoch of the last observation. Lets consumers reason about freshness. */
|
|
@@ -71025,7 +71196,7 @@ var NetworkLinkStatusSchema = zod.z.object({
|
|
|
71025
71196
|
/** The slice a provider seeds before its first read: nothing is known yet. */
|
|
71026
71197
|
var NETWORK_LINK_UNKNOWN = {
|
|
71027
71198
|
type: "unknown",
|
|
71028
|
-
|
|
71199
|
+
signal: null,
|
|
71029
71200
|
lastUpdated: 0
|
|
71030
71201
|
};
|
|
71031
71202
|
/**
|
|
@@ -71042,9 +71213,9 @@ function signalPercentFromBars(bars, maxBars) {
|
|
|
71042
71213
|
* below is 0 %, -50 dBm and above is 100 %, linear between. Non-finite or
|
|
71043
71214
|
* positive input is not an RSSI: `null`.
|
|
71044
71215
|
*/
|
|
71045
|
-
function signalPercentFromRssi(
|
|
71046
|
-
if (
|
|
71047
|
-
return Math.round((Math.min(-50, Math.max(-100,
|
|
71216
|
+
function signalPercentFromRssi(rssi) {
|
|
71217
|
+
if (rssi === void 0 || !Number.isFinite(rssi) || rssi > 0) return null;
|
|
71218
|
+
return Math.round((Math.min(-50, Math.max(-100, rssi)) + 100) * 2);
|
|
71048
71219
|
}
|
|
71049
71220
|
var networkLinkCapability = {
|
|
71050
71221
|
name: "network-link",
|
|
@@ -71095,13 +71266,13 @@ onStatusChanged: { data: zod.z.object({
|
|
|
71095
71266
|
* History (D850): recorded series.
|
|
71096
71267
|
*/
|
|
71097
71268
|
history: { fields: {
|
|
71098
|
-
|
|
71269
|
+
signal: {
|
|
71099
71270
|
kind: "gauge",
|
|
71100
71271
|
unit: "%",
|
|
71101
71272
|
deadband: 3,
|
|
71102
71273
|
minIntervalMs: 6e4
|
|
71103
71274
|
},
|
|
71104
|
-
|
|
71275
|
+
rssi: {
|
|
71105
71276
|
kind: "gauge",
|
|
71106
71277
|
unit: "dBm",
|
|
71107
71278
|
deadband: 2,
|
|
@@ -73464,16 +73635,16 @@ var platformProbeCapability = {
|
|
|
73464
73635
|
* in real-world devices.
|
|
73465
73636
|
*/
|
|
73466
73637
|
var PowerMeterStatusSchema = zod.z.object({
|
|
73467
|
-
/** Instantaneous power draw
|
|
73468
|
-
|
|
73638
|
+
/** Instantaneous power draw (watts). */
|
|
73639
|
+
power: zod.z.number().optional().meta(require_sleep.readingMeta("power", { label: "Power" })),
|
|
73469
73640
|
/** Cumulative energy in kilowatt-hours since the meter was reset. */
|
|
73470
|
-
|
|
73471
|
-
/** Voltage
|
|
73472
|
-
|
|
73641
|
+
energy: zod.z.number().optional().meta(require_sleep.readingMeta("energy", { label: "Energy" })),
|
|
73642
|
+
/** Voltage (volts). */
|
|
73643
|
+
voltage: zod.z.number().optional().meta(require_sleep.readingMeta("voltage", { label: "Voltage" })),
|
|
73473
73644
|
/** Current in amperes. */
|
|
73474
|
-
|
|
73645
|
+
current: zod.z.number().optional().meta(require_sleep.readingMeta("current", { label: "Current" })),
|
|
73475
73646
|
/** Apparent power in volt-amperes. */
|
|
73476
|
-
|
|
73647
|
+
apparentPower: zod.z.number().optional().meta(require_sleep.readingMeta("apparent-power")),
|
|
73477
73648
|
/** Power factor (0..1) if reported. */
|
|
73478
73649
|
powerFactor: zod.z.number().min(0).max(1).optional().meta(require_sleep.readingMeta("ratio")),
|
|
73479
73650
|
/** Ms epoch when the slice was last updated. */
|
|
@@ -73531,14 +73702,14 @@ function powerTarget(id, unit) {
|
|
|
73531
73702
|
advancedExpression: true
|
|
73532
73703
|
};
|
|
73533
73704
|
}
|
|
73534
|
-
/** "Power and energy" extension:
|
|
73705
|
+
/** "Power and energy" extension: power, energy, voltage and current from other devices (D951). */
|
|
73535
73706
|
var powerMeterCustomize = {
|
|
73536
73707
|
version: 1,
|
|
73537
73708
|
targets: [
|
|
73538
|
-
powerTarget("
|
|
73539
|
-
powerTarget("
|
|
73540
|
-
powerTarget("
|
|
73541
|
-
powerTarget("
|
|
73709
|
+
powerTarget("power", "W"),
|
|
73710
|
+
powerTarget("energy", "kWh"),
|
|
73711
|
+
powerTarget("voltage", "V"),
|
|
73712
|
+
powerTarget("current", "A")
|
|
73542
73713
|
]
|
|
73543
73714
|
};
|
|
73544
73715
|
var powerMeterCapability = {
|
|
@@ -73554,41 +73725,41 @@ var powerMeterCapability = {
|
|
|
73554
73725
|
},
|
|
73555
73726
|
runtimeState: PowerMeterStatusSchema,
|
|
73556
73727
|
/**
|
|
73557
|
-
* Runtime-state durability: **restored** — as `numeric-sensor`; `
|
|
73728
|
+
* Runtime-state durability: **restored** — as `numeric-sensor`; `energy` is an accumulator whose restored value is the baseline.
|
|
73558
73729
|
*
|
|
73559
73730
|
* See `RuntimeStateDurability`. Enforced by
|
|
73560
73731
|
* `scripts/check-runtime-state-durability.ts`.
|
|
73561
73732
|
*/
|
|
73562
73733
|
durability: "restored",
|
|
73563
73734
|
/**
|
|
73564
|
-
* History (D850): recorded series. Gauges are floored at 30 s; `
|
|
73735
|
+
* History (D850): recorded series. Gauges are floored at 30 s; `energy` is the one monotonic meter (counter).
|
|
73565
73736
|
*/
|
|
73566
73737
|
history: { fields: {
|
|
73567
|
-
|
|
73738
|
+
power: {
|
|
73568
73739
|
kind: "gauge",
|
|
73569
73740
|
unit: "W",
|
|
73570
73741
|
deadband: 1,
|
|
73571
73742
|
minIntervalMs: 3e4
|
|
73572
73743
|
},
|
|
73573
|
-
|
|
73744
|
+
energy: {
|
|
73574
73745
|
kind: "counter",
|
|
73575
73746
|
unit: "kWh",
|
|
73576
73747
|
deadband: .01,
|
|
73577
73748
|
minIntervalMs: 6e4
|
|
73578
73749
|
},
|
|
73579
|
-
|
|
73750
|
+
voltage: {
|
|
73580
73751
|
kind: "gauge",
|
|
73581
73752
|
unit: "V",
|
|
73582
73753
|
deadband: 1,
|
|
73583
73754
|
minIntervalMs: 3e4
|
|
73584
73755
|
},
|
|
73585
|
-
|
|
73756
|
+
current: {
|
|
73586
73757
|
kind: "gauge",
|
|
73587
73758
|
unit: "A",
|
|
73588
73759
|
deadband: .05,
|
|
73589
73760
|
minIntervalMs: 3e4
|
|
73590
73761
|
},
|
|
73591
|
-
|
|
73762
|
+
apparentPower: {
|
|
73592
73763
|
kind: "gauge",
|
|
73593
73764
|
unit: "VA",
|
|
73594
73765
|
deadband: 1,
|
|
@@ -73640,9 +73811,6 @@ var PresenceStatusSchema = zod.z.object({
|
|
|
73640
73811
|
location: zod.z.string().nullable(),
|
|
73641
73812
|
/** GPS coordinates when available. Null otherwise. */
|
|
73642
73813
|
gps: GpsLocationSchema.nullable(),
|
|
73643
|
-
/** Optional battery level of the tracking device (0..100). Null
|
|
73644
|
-
* when not reported. */
|
|
73645
|
-
batteryPercent: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
|
|
73646
73814
|
/** Ms epoch when the slice was last updated. */
|
|
73647
73815
|
lastChangedAt: zod.z.number()
|
|
73648
73816
|
});
|
|
@@ -73671,14 +73839,7 @@ var presenceCapability = {
|
|
|
73671
73839
|
* `scripts/check-runtime-state-durability.ts`.
|
|
73672
73840
|
*/
|
|
73673
73841
|
durability: "restored",
|
|
73674
|
-
|
|
73675
|
-
* History (D850): recorded series.
|
|
73676
|
-
*/
|
|
73677
|
-
history: { fields: { batteryPercent: {
|
|
73678
|
-
kind: "gauge",
|
|
73679
|
-
unit: "%",
|
|
73680
|
-
deadband: 1
|
|
73681
|
-
} } },
|
|
73842
|
+
history: "none",
|
|
73682
73843
|
/** Clock fields: written, but excluded from the compare that decides
|
|
73683
73844
|
* whether persisting is worth a SQLite commit. */
|
|
73684
73845
|
volatileStateFields: ["lastChangedAt"]
|
|
@@ -73695,7 +73856,7 @@ var presenceCapability = {
|
|
|
73695
73856
|
*/
|
|
73696
73857
|
var PressureSensorStatusSchema = zod.z.object({
|
|
73697
73858
|
/** Current pressure in hPa. `null` when the sample was refused (D393). */
|
|
73698
|
-
|
|
73859
|
+
pressure: zod.z.number().nullable().meta(require_sleep.readingMeta("pressure", { precision: 0 })),
|
|
73699
73860
|
/** Ms epoch when the slice was last updated. */
|
|
73700
73861
|
lastFetchedAt: zod.z.number()
|
|
73701
73862
|
});
|
|
@@ -73721,7 +73882,7 @@ var pressureSensorCapability = {
|
|
|
73721
73882
|
/**
|
|
73722
73883
|
* History (D850): recorded series.
|
|
73723
73884
|
*/
|
|
73724
|
-
history: { fields: {
|
|
73885
|
+
history: { fields: { pressure: {
|
|
73725
73886
|
kind: "gauge",
|
|
73726
73887
|
unit: "hPa",
|
|
73727
73888
|
deadband: .1
|
|
@@ -78948,7 +79109,7 @@ var tamperCapability = {
|
|
|
78948
79109
|
*/
|
|
78949
79110
|
var TemperatureSensorStatusSchema = zod.z.object({
|
|
78950
79111
|
/** Current temperature in Celsius. `null` when the sample was refused (D393). */
|
|
78951
|
-
|
|
79112
|
+
temperature: zod.z.number().nullable().meta(require_sleep.readingMeta("temperature", {
|
|
78952
79113
|
precision: 1,
|
|
78953
79114
|
label: "Temperature"
|
|
78954
79115
|
})),
|
|
@@ -78977,7 +79138,7 @@ var temperatureSensorCapability = {
|
|
|
78977
79138
|
/**
|
|
78978
79139
|
* History (D850): recorded series.
|
|
78979
79140
|
*/
|
|
78980
|
-
history: { fields: {
|
|
79141
|
+
history: { fields: { temperature: {
|
|
78981
79142
|
kind: "gauge",
|
|
78982
79143
|
unit: "°C",
|
|
78983
79144
|
deadband: .1
|
|
@@ -79345,7 +79506,7 @@ var VacuumControlStatusSchema = zod.z.object({
|
|
|
79345
79506
|
/** Speed tokens the hardware accepts — drives the UI selector. */
|
|
79346
79507
|
availableFanSpeeds: zod.z.array(zod.z.string()),
|
|
79347
79508
|
/** 0..100 cleaning-completion percentage of the current task, or null. */
|
|
79348
|
-
|
|
79509
|
+
progress: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
|
|
79349
79510
|
/** Current error code (0 / null = no error). */
|
|
79350
79511
|
errorCode: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
|
|
79351
79512
|
/** Human label for {@link errorCode}, or null when none / undecodable. */
|
|
@@ -81995,7 +82156,7 @@ function named(prefix, sources, cap, fieldPath) {
|
|
|
81995
82156
|
return Object.fromEntries(sources.map((s, i) => [`${prefix}${i + 1}`, from(s, cap, fieldPath)]));
|
|
81996
82157
|
}
|
|
81997
82158
|
function roomCompositionExample(input) {
|
|
81998
|
-
const t = named("t", input.temperatureSources, "temperature-sensor", "
|
|
82159
|
+
const t = named("t", input.temperatureSources, "temperature-sensor", "temperature");
|
|
81999
82160
|
const f = named("f", input.temperatureSources, "temperature-sensor", "lastFetchedAt");
|
|
82000
82161
|
const d = named("d", input.motionSources, "motion", "detected");
|
|
82001
82162
|
const m = named("m", input.motionSources, "motion", "lastDetectedAt");
|
|
@@ -82010,7 +82171,7 @@ function roomCompositionExample(input) {
|
|
|
82010
82171
|
kind: "fields",
|
|
82011
82172
|
cap: "temperature-sensor",
|
|
82012
82173
|
fields: {
|
|
82013
|
-
|
|
82174
|
+
temperature: {
|
|
82014
82175
|
kind: "expression",
|
|
82015
82176
|
expr: `round(avg(${Object.keys(t).join(", ")}), 1)`,
|
|
82016
82177
|
bindings: t
|
|
@@ -82056,7 +82217,7 @@ function roomCompositionExample(input) {
|
|
|
82056
82217
|
* an `enum-sensor` whose `value` is the raw STRING state — `number(d)` reads it,
|
|
82057
82218
|
* and anything that is not a number is `null`, never 0 (D393). The battery
|
|
82058
82219
|
* status sensor is an `enum-sensor` too (`unavailable` keeps its cold `''`, so
|
|
82059
|
-
* `
|
|
82220
|
+
* `level` is `null`). `charging` maps `battery_charging` off to
|
|
82060
82221
|
* `'unknown'`, never `'none'`: off does not prove "on battery alone" (the
|
|
82061
82222
|
* operator confirms the real semantics live).
|
|
82062
82223
|
*/
|
|
@@ -82125,7 +82286,7 @@ function feederCustomizationExample(sources) {
|
|
|
82125
82286
|
kind: "fields",
|
|
82126
82287
|
cap: "battery",
|
|
82127
82288
|
fields: {
|
|
82128
|
-
|
|
82289
|
+
level: {
|
|
82129
82290
|
kind: "expression",
|
|
82130
82291
|
expr: "s == \"low\" ? 15 : (s == \"normal\" ? 100 : null)",
|
|
82131
82292
|
bindings: { s: from(sources.batteryStatus, "enum-sensor", "value") }
|
|
@@ -82193,10 +82354,10 @@ function unitConstant(to) {
|
|
|
82193
82354
|
/** Primary numeric field that also publishes the cap's `unit` string. */
|
|
82194
82355
|
var UNIT_COMPANION_FIELD = {
|
|
82195
82356
|
"numeric-sensor": "value",
|
|
82196
|
-
"temperature-sensor": "
|
|
82197
|
-
"humidity-sensor": "
|
|
82198
|
-
"pressure-sensor": "
|
|
82199
|
-
"ambient-light-sensor": "
|
|
82357
|
+
"temperature-sensor": "temperature",
|
|
82358
|
+
"humidity-sensor": "humidity",
|
|
82359
|
+
"pressure-sensor": "pressure",
|
|
82360
|
+
"ambient-light-sensor": "illuminance"
|
|
82200
82361
|
};
|
|
82201
82362
|
/** `unit` when converting that cap's primary numeric field; otherwise none. */
|
|
82202
82363
|
function unitCompanionField(cap, fieldPath) {
|
|
@@ -82383,7 +82544,7 @@ var CAP_CUSTOMIZE_CATALOG = {
|
|
|
82383
82544
|
"battery": {
|
|
82384
82545
|
version: 1,
|
|
82385
82546
|
targets: [{
|
|
82386
|
-
id: "
|
|
82547
|
+
id: "level",
|
|
82387
82548
|
cardinality: "scalar",
|
|
82388
82549
|
deviceTypes: [
|
|
82389
82550
|
require_err_msg.DeviceType.Camera,
|
|
@@ -82397,7 +82558,7 @@ var CAP_CUSTOMIZE_CATALOG = {
|
|
|
82397
82558
|
],
|
|
82398
82559
|
apply: "add",
|
|
82399
82560
|
vocabularyCap: "battery",
|
|
82400
|
-
vocabularyStateField: "
|
|
82561
|
+
vocabularyStateField: "level",
|
|
82401
82562
|
sources: [{
|
|
82402
82563
|
kind: "field",
|
|
82403
82564
|
valueKinds: ["number", "string"],
|
|
@@ -82412,7 +82573,7 @@ var CAP_CUSTOMIZE_CATALOG = {
|
|
|
82412
82573
|
}],
|
|
82413
82574
|
sourceReport: { status: "assignment-status" },
|
|
82414
82575
|
advancedExpression: true,
|
|
82415
|
-
path: "
|
|
82576
|
+
path: "level",
|
|
82416
82577
|
valueKind: "number",
|
|
82417
82578
|
nullable: true,
|
|
82418
82579
|
companions: [
|
|
@@ -82647,7 +82808,7 @@ var CAP_CUSTOMIZE_CATALOG = {
|
|
|
82647
82808
|
version: 1,
|
|
82648
82809
|
targets: [
|
|
82649
82810
|
{
|
|
82650
|
-
id: "
|
|
82811
|
+
id: "power",
|
|
82651
82812
|
cardinality: "scalar",
|
|
82652
82813
|
deviceTypes: [
|
|
82653
82814
|
require_err_msg.DeviceType.Switch,
|
|
@@ -82667,7 +82828,7 @@ var CAP_CUSTOMIZE_CATALOG = {
|
|
|
82667
82828
|
],
|
|
82668
82829
|
apply: "add",
|
|
82669
82830
|
vocabularyCap: "power-meter",
|
|
82670
|
-
vocabularyStateField: "
|
|
82831
|
+
vocabularyStateField: "power",
|
|
82671
82832
|
sources: [{
|
|
82672
82833
|
kind: "numeric",
|
|
82673
82834
|
requiredUnit: "W",
|
|
@@ -82676,7 +82837,7 @@ var CAP_CUSTOMIZE_CATALOG = {
|
|
|
82676
82837
|
}],
|
|
82677
82838
|
sourceReport: { status: "assignment-status" },
|
|
82678
82839
|
advancedExpression: true,
|
|
82679
|
-
path: "
|
|
82840
|
+
path: "power",
|
|
82680
82841
|
valueKind: "number",
|
|
82681
82842
|
nullable: false,
|
|
82682
82843
|
companions: [{
|
|
@@ -82686,7 +82847,7 @@ var CAP_CUSTOMIZE_CATALOG = {
|
|
|
82686
82847
|
}]
|
|
82687
82848
|
},
|
|
82688
82849
|
{
|
|
82689
|
-
id: "
|
|
82850
|
+
id: "energy",
|
|
82690
82851
|
cardinality: "scalar",
|
|
82691
82852
|
deviceTypes: [
|
|
82692
82853
|
require_err_msg.DeviceType.Switch,
|
|
@@ -82706,7 +82867,7 @@ var CAP_CUSTOMIZE_CATALOG = {
|
|
|
82706
82867
|
],
|
|
82707
82868
|
apply: "add",
|
|
82708
82869
|
vocabularyCap: "power-meter",
|
|
82709
|
-
vocabularyStateField: "
|
|
82870
|
+
vocabularyStateField: "energy",
|
|
82710
82871
|
sources: [{
|
|
82711
82872
|
kind: "numeric",
|
|
82712
82873
|
requiredUnit: "kWh",
|
|
@@ -82715,7 +82876,7 @@ var CAP_CUSTOMIZE_CATALOG = {
|
|
|
82715
82876
|
}],
|
|
82716
82877
|
sourceReport: { status: "assignment-status" },
|
|
82717
82878
|
advancedExpression: true,
|
|
82718
|
-
path: "
|
|
82879
|
+
path: "energy",
|
|
82719
82880
|
valueKind: "number",
|
|
82720
82881
|
nullable: false,
|
|
82721
82882
|
companions: [{
|
|
@@ -82725,7 +82886,7 @@ var CAP_CUSTOMIZE_CATALOG = {
|
|
|
82725
82886
|
}]
|
|
82726
82887
|
},
|
|
82727
82888
|
{
|
|
82728
|
-
id: "
|
|
82889
|
+
id: "voltage",
|
|
82729
82890
|
cardinality: "scalar",
|
|
82730
82891
|
deviceTypes: [
|
|
82731
82892
|
require_err_msg.DeviceType.Switch,
|
|
@@ -82745,7 +82906,7 @@ var CAP_CUSTOMIZE_CATALOG = {
|
|
|
82745
82906
|
],
|
|
82746
82907
|
apply: "add",
|
|
82747
82908
|
vocabularyCap: "power-meter",
|
|
82748
|
-
vocabularyStateField: "
|
|
82909
|
+
vocabularyStateField: "voltage",
|
|
82749
82910
|
sources: [{
|
|
82750
82911
|
kind: "numeric",
|
|
82751
82912
|
requiredUnit: "V",
|
|
@@ -82754,7 +82915,7 @@ var CAP_CUSTOMIZE_CATALOG = {
|
|
|
82754
82915
|
}],
|
|
82755
82916
|
sourceReport: { status: "assignment-status" },
|
|
82756
82917
|
advancedExpression: true,
|
|
82757
|
-
path: "
|
|
82918
|
+
path: "voltage",
|
|
82758
82919
|
valueKind: "number",
|
|
82759
82920
|
nullable: false,
|
|
82760
82921
|
companions: [{
|
|
@@ -82764,7 +82925,7 @@ var CAP_CUSTOMIZE_CATALOG = {
|
|
|
82764
82925
|
}]
|
|
82765
82926
|
},
|
|
82766
82927
|
{
|
|
82767
|
-
id: "
|
|
82928
|
+
id: "current",
|
|
82768
82929
|
cardinality: "scalar",
|
|
82769
82930
|
deviceTypes: [
|
|
82770
82931
|
require_err_msg.DeviceType.Switch,
|
|
@@ -82784,7 +82945,7 @@ var CAP_CUSTOMIZE_CATALOG = {
|
|
|
82784
82945
|
],
|
|
82785
82946
|
apply: "add",
|
|
82786
82947
|
vocabularyCap: "power-meter",
|
|
82787
|
-
vocabularyStateField: "
|
|
82948
|
+
vocabularyStateField: "current",
|
|
82788
82949
|
sources: [{
|
|
82789
82950
|
kind: "numeric",
|
|
82790
82951
|
requiredUnit: "A",
|
|
@@ -82793,7 +82954,7 @@ var CAP_CUSTOMIZE_CATALOG = {
|
|
|
82793
82954
|
}],
|
|
82794
82955
|
sourceReport: { status: "assignment-status" },
|
|
82795
82956
|
advancedExpression: true,
|
|
82796
|
-
path: "
|
|
82957
|
+
path: "current",
|
|
82797
82958
|
valueKind: "number",
|
|
82798
82959
|
nullable: false,
|
|
82799
82960
|
companions: [{
|
|
@@ -82894,19 +83055,19 @@ var CAP_CUSTOMIZE_CATALOG = {
|
|
|
82894
83055
|
* it is not an override list (D944).
|
|
82895
83056
|
*/
|
|
82896
83057
|
var CAP_RUNTIME_FIELD_UNITS = {
|
|
82897
|
-
"ambient-light-sensor": {
|
|
83058
|
+
"ambient-light-sensor": { illuminance: "lx" },
|
|
82898
83059
|
"battery": {
|
|
82899
|
-
|
|
82900
|
-
|
|
83060
|
+
level: "%",
|
|
83061
|
+
voltage: "V"
|
|
82901
83062
|
},
|
|
82902
|
-
"humidity-sensor": {
|
|
83063
|
+
"humidity-sensor": { humidity: "%" },
|
|
82903
83064
|
"power-meter": {
|
|
82904
|
-
|
|
82905
|
-
|
|
82906
|
-
|
|
82907
|
-
|
|
83065
|
+
power: "W",
|
|
83066
|
+
energy: "kWh",
|
|
83067
|
+
voltage: "V",
|
|
83068
|
+
current: "A"
|
|
82908
83069
|
},
|
|
82909
|
-
"temperature-sensor": {
|
|
83070
|
+
"temperature-sensor": { temperature: "°C" }
|
|
82910
83071
|
};
|
|
82911
83072
|
//#endregion
|
|
82912
83073
|
//#region src/composition/customize/field-source.ts
|
|
@@ -85025,7 +85186,7 @@ var BaseDevice = class {
|
|
|
85025
85186
|
* `device-local-state.ts`. Drivers access via:
|
|
85026
85187
|
*
|
|
85027
85188
|
* `this.state.battery.sleeping = true` // patches the battery slice
|
|
85028
|
-
* `const pct = this.state.battery.
|
|
85189
|
+
* `const pct = this.state.battery.level` // reads the battery slice
|
|
85029
85190
|
* `this.state.deviceStatus.online = true` // mirrors `markOnline(true)`
|
|
85030
85191
|
*
|
|
85031
85192
|
* Adding a new cap with `runtimeState:` automatically extends this
|
|
@@ -85065,7 +85226,7 @@ var BaseDevice = class {
|
|
|
85065
85226
|
* shape; drivers don't redeclare it, they just write through.
|
|
85066
85227
|
*
|
|
85067
85228
|
* Read: `this.runtimeState.getCapState('battery')` →
|
|
85068
|
-
* `{
|
|
85229
|
+
* `{level, charging, sleeping, lastUpdated}` for any
|
|
85069
85230
|
* provider that registers `batteryCapability`.
|
|
85070
85231
|
* Write: `this.runtimeState.setCapState('battery', { … })`.
|
|
85071
85232
|
*
|
|
@@ -87030,17 +87191,17 @@ function pickAccessoryControl(device) {
|
|
|
87030
87191
|
var SENSOR_MAP = {
|
|
87031
87192
|
"temperature-sensor": {
|
|
87032
87193
|
capKey: "temperatureSensor",
|
|
87033
|
-
field: "
|
|
87194
|
+
field: "temperature",
|
|
87034
87195
|
fallbackUnit: "°C"
|
|
87035
87196
|
},
|
|
87036
87197
|
"humidity-sensor": {
|
|
87037
87198
|
capKey: "humiditySensor",
|
|
87038
|
-
field: "
|
|
87199
|
+
field: "humidity",
|
|
87039
87200
|
fallbackUnit: "%"
|
|
87040
87201
|
},
|
|
87041
87202
|
"ambient-light-sensor": {
|
|
87042
87203
|
capKey: "ambientLightSensor",
|
|
87043
|
-
field: "
|
|
87204
|
+
field: "illuminance",
|
|
87044
87205
|
fallbackUnit: "lx"
|
|
87045
87206
|
},
|
|
87046
87207
|
"numeric-sensor": {
|
|
@@ -97498,7 +97659,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
97498
97659
|
label: t("Full brightness", "Luminosità massima"),
|
|
97499
97660
|
phrase: t("Set {device} to full brightness", "Porta {device} alla massima luminosità"),
|
|
97500
97661
|
icon: "sun",
|
|
97501
|
-
args: {
|
|
97662
|
+
args: { level: 100 }
|
|
97502
97663
|
},
|
|
97503
97664
|
{
|
|
97504
97665
|
id: "brightness.half",
|
|
@@ -97506,7 +97667,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
97506
97667
|
label: t("Half brightness", "Luminosità a metà"),
|
|
97507
97668
|
phrase: t("Dim {device} to half", "Abbassa {device} a metà luminosità"),
|
|
97508
97669
|
icon: "sun-dim",
|
|
97509
|
-
args: {
|
|
97670
|
+
args: { level: 50 }
|
|
97510
97671
|
},
|
|
97511
97672
|
{
|
|
97512
97673
|
id: "brightness.off",
|
|
@@ -97514,16 +97675,16 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
97514
97675
|
label: t("Brightness off", "Luminosità a zero"),
|
|
97515
97676
|
phrase: t("Dim {device} to zero", "Abbassa {device} a zero"),
|
|
97516
97677
|
icon: "lightbulb-off",
|
|
97517
|
-
args: {
|
|
97678
|
+
args: { level: 0 }
|
|
97518
97679
|
},
|
|
97519
97680
|
{
|
|
97520
97681
|
id: "brightness.set",
|
|
97521
97682
|
method: "setBrightness",
|
|
97522
97683
|
label: t("Set brightness", "Imposta la luminosità"),
|
|
97523
|
-
phrase: t("Set the brightness of {device} to {
|
|
97684
|
+
phrase: t("Set the brightness of {device} to {level}", "Imposta la luminosità di {device} a {level}"),
|
|
97524
97685
|
icon: "sun-medium",
|
|
97525
97686
|
openArgs: [{
|
|
97526
|
-
key: "
|
|
97687
|
+
key: "level",
|
|
97527
97688
|
label: PERCENT,
|
|
97528
97689
|
offer: {
|
|
97529
97690
|
kind: "number",
|
|
@@ -97537,13 +97698,13 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
97537
97698
|
],
|
|
97538
97699
|
state: [{
|
|
97539
97700
|
kind: "numeric",
|
|
97540
|
-
field: "
|
|
97701
|
+
field: "level",
|
|
97541
97702
|
quantity: t("the brightness", "la luminosità"),
|
|
97542
97703
|
label: t("Brightness", "Luminosità"),
|
|
97543
97704
|
unit: "%",
|
|
97544
97705
|
icon: "sun"
|
|
97545
97706
|
}],
|
|
97546
|
-
events: { onBrightnessChanged: { notOperatorFacing: "the `
|
|
97707
|
+
events: { onBrightnessChanged: { notOperatorFacing: "the `level` state trigger reads the same change" } }
|
|
97547
97708
|
},
|
|
97548
97709
|
color: {
|
|
97549
97710
|
scope: "device",
|
|
@@ -97557,7 +97718,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
97557
97718
|
icon: "sun",
|
|
97558
97719
|
args: { color: {
|
|
97559
97720
|
mode: "mired",
|
|
97560
|
-
|
|
97721
|
+
colorTemperature: 370
|
|
97561
97722
|
} }
|
|
97562
97723
|
},
|
|
97563
97724
|
{
|
|
@@ -97568,7 +97729,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
97568
97729
|
icon: "snowflake",
|
|
97569
97730
|
args: { color: {
|
|
97570
97731
|
mode: "mired",
|
|
97571
|
-
|
|
97732
|
+
colorTemperature: 200
|
|
97572
97733
|
} }
|
|
97573
97734
|
},
|
|
97574
97735
|
{
|
|
@@ -98182,7 +98343,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
98182
98343
|
label: t("Fan off", "Ventilatore spento"),
|
|
98183
98344
|
phrase: t("Stop {device}", "Ferma {device}"),
|
|
98184
98345
|
icon: "power-off",
|
|
98185
|
-
args: {
|
|
98346
|
+
args: { speed: 0 }
|
|
98186
98347
|
},
|
|
98187
98348
|
{
|
|
98188
98349
|
id: "fan.full",
|
|
@@ -98190,16 +98351,16 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
98190
98351
|
label: t("Fan full", "Ventilatore al massimo"),
|
|
98191
98352
|
phrase: t("Run {device} at full speed", "Porta {device} alla massima velocità"),
|
|
98192
98353
|
icon: "fan",
|
|
98193
|
-
args: {
|
|
98354
|
+
args: { speed: 100 }
|
|
98194
98355
|
},
|
|
98195
98356
|
{
|
|
98196
98357
|
id: "fan.speed",
|
|
98197
98358
|
method: "setPercentage",
|
|
98198
98359
|
label: t("Set speed", "Imposta la velocità"),
|
|
98199
|
-
phrase: t("Set the speed of {device} to {
|
|
98360
|
+
phrase: t("Set the speed of {device} to {speed}", "Imposta la velocità di {device} al {speed}"),
|
|
98200
98361
|
icon: "fan",
|
|
98201
98362
|
openArgs: [{
|
|
98202
|
-
key: "
|
|
98363
|
+
key: "speed",
|
|
98203
98364
|
label: t("Speed", "Velocità"),
|
|
98204
98365
|
offer: {
|
|
98205
98366
|
kind: "number",
|
|
@@ -98261,7 +98422,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
98261
98422
|
],
|
|
98262
98423
|
state: [{
|
|
98263
98424
|
kind: "numeric",
|
|
98264
|
-
field: "
|
|
98425
|
+
field: "speed",
|
|
98265
98426
|
quantity: t("the speed", "la velocità"),
|
|
98266
98427
|
label: t("Speed", "Velocità"),
|
|
98267
98428
|
unit: "%",
|
|
@@ -99061,7 +99222,7 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
99061
99222
|
},
|
|
99062
99223
|
{
|
|
99063
99224
|
kind: "numeric",
|
|
99064
|
-
field: "
|
|
99225
|
+
field: "progress",
|
|
99065
99226
|
quantity: t("the cycle progress", "l’avanzamento del lavaggio"),
|
|
99066
99227
|
label: t("Progress", "Avanzamento"),
|
|
99067
99228
|
unit: "%",
|
|
@@ -99419,10 +99580,10 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
99419
99580
|
id: "media.seek",
|
|
99420
99581
|
method: "seek",
|
|
99421
99582
|
label: t("Seek", "Vai al punto"),
|
|
99422
|
-
phrase: t("Move {device} to {
|
|
99583
|
+
phrase: t("Move {device} to {position}", "Porta {device} al punto {position}"),
|
|
99423
99584
|
icon: "fast-forward",
|
|
99424
99585
|
openArgs: [{
|
|
99425
|
-
key: "
|
|
99586
|
+
key: "position",
|
|
99426
99587
|
label: t("Position", "Posizione"),
|
|
99427
99588
|
offer: {
|
|
99428
99589
|
kind: "number",
|
|
@@ -100475,7 +100636,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
100475
100636
|
state: [
|
|
100476
100637
|
{
|
|
100477
100638
|
kind: "numeric",
|
|
100478
|
-
field: "
|
|
100639
|
+
field: "co2",
|
|
100479
100640
|
quantity: t("the CO₂", "la CO₂"),
|
|
100480
100641
|
label: t("CO₂", "CO₂"),
|
|
100481
100642
|
unit: "ppm",
|
|
@@ -100483,7 +100644,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
100483
100644
|
},
|
|
100484
100645
|
{
|
|
100485
100646
|
kind: "numeric",
|
|
100486
|
-
field: "
|
|
100647
|
+
field: "voc",
|
|
100487
100648
|
quantity: t("the VOC level", "il livello di COV"),
|
|
100488
100649
|
label: t("VOC", "COV"),
|
|
100489
100650
|
unit: "ppb",
|
|
@@ -100519,7 +100680,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
100519
100680
|
noun: t("Light level", "Luminosità ambiente"),
|
|
100520
100681
|
state: [{
|
|
100521
100682
|
kind: "numeric",
|
|
100522
|
-
field: "
|
|
100683
|
+
field: "illuminance",
|
|
100523
100684
|
quantity: t("the light level", "la luminosità"),
|
|
100524
100685
|
label: t("Illuminance", "Illuminamento"),
|
|
100525
100686
|
unit: "lx",
|
|
@@ -100722,7 +100883,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
100722
100883
|
noun: t("Humidity", "Umidità"),
|
|
100723
100884
|
state: [{
|
|
100724
100885
|
kind: "numeric",
|
|
100725
|
-
field: "
|
|
100886
|
+
field: "humidity",
|
|
100726
100887
|
quantity: t("the humidity", "l’umidità"),
|
|
100727
100888
|
label: t("Humidity", "Umidità"),
|
|
100728
100889
|
unit: "%",
|
|
@@ -100770,7 +100931,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
100770
100931
|
state: [
|
|
100771
100932
|
{
|
|
100772
100933
|
kind: "numeric",
|
|
100773
|
-
field: "
|
|
100934
|
+
field: "power",
|
|
100774
100935
|
quantity: t("the power draw", "il consumo"),
|
|
100775
100936
|
label: t("Power", "Potenza"),
|
|
100776
100937
|
unit: "W",
|
|
@@ -100778,7 +100939,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
100778
100939
|
},
|
|
100779
100940
|
{
|
|
100780
100941
|
kind: "numeric",
|
|
100781
|
-
field: "
|
|
100942
|
+
field: "energy",
|
|
100782
100943
|
quantity: t("the energy used", "l’energia consumata"),
|
|
100783
100944
|
label: t("Energy", "Energia"),
|
|
100784
100945
|
unit: "kWh",
|
|
@@ -100786,7 +100947,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
100786
100947
|
},
|
|
100787
100948
|
{
|
|
100788
100949
|
kind: "numeric",
|
|
100789
|
-
field: "
|
|
100950
|
+
field: "voltage",
|
|
100790
100951
|
quantity: t("the voltage", "la tensione"),
|
|
100791
100952
|
label: t("Voltage", "Tensione"),
|
|
100792
100953
|
unit: "V",
|
|
@@ -100794,7 +100955,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
100794
100955
|
},
|
|
100795
100956
|
{
|
|
100796
100957
|
kind: "numeric",
|
|
100797
|
-
field: "
|
|
100958
|
+
field: "current",
|
|
100798
100959
|
quantity: t("the current", "la corrente"),
|
|
100799
100960
|
label: t("Current", "Corrente"),
|
|
100800
100961
|
unit: "A",
|
|
@@ -100811,13 +100972,6 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
100811
100972
|
label: t("Where", "Posizione"),
|
|
100812
100973
|
phrase: t("When {device} is at {value}", "Quando {device} è in {value}"),
|
|
100813
100974
|
icon: "map-pin"
|
|
100814
|
-
}, {
|
|
100815
|
-
kind: "numeric",
|
|
100816
|
-
field: "batteryPercent",
|
|
100817
|
-
quantity: t("the phone battery", "la batteria del telefono"),
|
|
100818
|
-
label: t("Battery", "Batteria"),
|
|
100819
|
-
unit: "%",
|
|
100820
|
-
icon: "battery"
|
|
100821
100975
|
}]
|
|
100822
100976
|
},
|
|
100823
100977
|
"pressure-sensor": {
|
|
@@ -100825,7 +100979,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
100825
100979
|
noun: t("Pressure", "Pressione"),
|
|
100826
100980
|
state: [{
|
|
100827
100981
|
kind: "numeric",
|
|
100828
|
-
field: "
|
|
100982
|
+
field: "pressure",
|
|
100829
100983
|
quantity: t("the pressure", "la pressione"),
|
|
100830
100984
|
label: t("Pressure", "Pressione"),
|
|
100831
100985
|
unit: "hPa",
|
|
@@ -100861,7 +101015,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
100861
101015
|
noun: t("Temperature", "Temperatura"),
|
|
100862
101016
|
state: [{
|
|
100863
101017
|
kind: "numeric",
|
|
100864
|
-
field: "
|
|
101018
|
+
field: "temperature",
|
|
100865
101019
|
quantity: t("the temperature", "la temperatura"),
|
|
100866
101020
|
label: t("Temperature", "Temperatura"),
|
|
100867
101021
|
unit: "°C",
|
|
@@ -100932,7 +101086,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
100932
101086
|
state: [
|
|
100933
101087
|
{
|
|
100934
101088
|
kind: "numeric",
|
|
100935
|
-
field: "
|
|
101089
|
+
field: "level",
|
|
100936
101090
|
quantity: t("the battery level", "il livello della batteria"),
|
|
100937
101091
|
label: t("Battery", "Batteria"),
|
|
100938
101092
|
unit: "%",
|
|
@@ -100974,7 +101128,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
100974
101128
|
icon: "battery-charging"
|
|
100975
101129
|
}
|
|
100976
101130
|
],
|
|
100977
|
-
events: { onStatusChanged: { notOperatorFacing: "the
|
|
101131
|
+
events: { onStatusChanged: { notOperatorFacing: "the level / sleeping / charging state triggers each read one field of this change" } },
|
|
100978
101132
|
extraEvents: [{
|
|
100979
101133
|
category: require_event_category.EventCategory.DeviceAwake,
|
|
100980
101134
|
label: t("Woke up", "Risvegliato"),
|
|
@@ -100992,7 +101146,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
100992
101146
|
noun: t("Network link", "Collegamento di rete"),
|
|
100993
101147
|
state: [{
|
|
100994
101148
|
kind: "numeric",
|
|
100995
|
-
field: "
|
|
101149
|
+
field: "signal",
|
|
100996
101150
|
quantity: t("the signal strength", "l’intensità del segnale"),
|
|
100997
101151
|
label: t("Signal", "Segnale"),
|
|
100998
101152
|
unit: "%",
|
|
@@ -121821,6 +121975,7 @@ exports.evaluateRose = require_composition.evaluateRose;
|
|
|
121821
121975
|
exports.evaluateSensorEdge = require_composition.evaluateSensorEdge;
|
|
121822
121976
|
exports.evaluateSensorLevelEdge = require_composition.evaluateSensorLevelEdge;
|
|
121823
121977
|
exports.evaluateStatefulExpressionCall = require_composition.evaluateStatefulExpressionCall;
|
|
121978
|
+
exports.evaluateStationaryTrigger = evaluateStationaryTrigger;
|
|
121824
121979
|
exports.evaluateStatistic = require_composition.evaluateStatistic;
|
|
121825
121980
|
exports.evaluateZoneRules = evaluateZoneRules;
|
|
121826
121981
|
exports.event = require_sleep.event;
|