@camstack/types 1.2.422 → 1.2.424

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.
Files changed (47) hide show
  1. package/dist/addon.js +1 -1
  2. package/dist/addon.mjs +1 -1
  3. package/dist/capabilities/composer.cap.d.ts +6 -0
  4. package/dist/capabilities/core-blocks.cap.d.ts +43 -0
  5. package/dist/capabilities/group-active.cap.d.ts +46 -0
  6. package/dist/capabilities/group-commands.cap.d.ts +1 -1
  7. package/dist/capabilities/group-status.cap.d.ts +1 -1
  8. package/dist/capabilities/index.d.ts +3 -1
  9. package/dist/capabilities/lock-control.cap.d.ts +1 -0
  10. package/dist/capabilities/notification-rules.cap.d.ts +12 -0
  11. package/dist/capabilities/switch.cap.d.ts +1 -1
  12. package/dist/client.js +17 -1
  13. package/dist/client.mjs +17 -1
  14. package/dist/composition/composition-report.d.ts +2 -0
  15. package/dist/composition/composition.d.ts +9 -0
  16. package/dist/composition/helpers/actuator-methods.d.ts +39 -0
  17. package/dist/composition/helpers/aggregate.d.ts +2 -0
  18. package/dist/composition/helpers/catalog.d.ts +7 -1
  19. package/dist/composition/helpers/compile-history.d.ts +16 -1
  20. package/dist/composition/helpers/compile-math.d.ts +9 -0
  21. package/dist/composition/helpers/history-math.d.ts +8 -0
  22. package/dist/composition/helpers/index.d.ts +6 -4
  23. package/dist/composition/helpers/source-pick.d.ts +8 -1
  24. package/dist/composition/helpers/spec.d.ts +49 -3
  25. package/dist/{composition-CpETZ4HL.mjs → composition-Bb8iwHEJ.mjs} +175 -5
  26. package/dist/{composition-CPRAP41h.js → composition-CpAc0YfQ.js} +228 -4
  27. package/dist/device/cap-vocabulary/entries/sensors.d.ts +1 -1
  28. package/dist/expression/builtins.d.ts +11 -2
  29. package/dist/expression/expression-source.d.ts +6 -1
  30. package/dist/generated/addon-api.d.ts +14 -0
  31. package/dist/generated/addon-cap-facts.d.ts +1 -1
  32. package/dist/generated/cap-status-types.d.ts +3 -1
  33. package/dist/generated/capability-router-map.d.ts +5 -2
  34. package/dist/generated/device-local-state.d.ts +3 -0
  35. package/dist/generated/device-proxy.d.ts +3 -0
  36. package/dist/generated/device-scoped-caps.d.ts +1 -1
  37. package/dist/generated/history-catalog.d.ts +1 -1
  38. package/dist/generated/method-access-map.d.ts +1 -1
  39. package/dist/generated/method-device-selectors.d.ts +1 -1
  40. package/dist/generated/runtime-state-durability.d.ts +1 -1
  41. package/dist/index.js +648 -34
  42. package/dist/index.mjs +625 -35
  43. package/dist/node.js +1 -1
  44. package/dist/node.mjs +1 -1
  45. package/dist/{sleep-0sD_LHLI.mjs → sleep--jXS9hWo.mjs} +10 -1
  46. package/dist/{sleep-CEKGW7A5.js → sleep-edKiOXLo.js} +10 -1
  47. package/package.json +1 -1
@@ -1138,7 +1138,7 @@ var ExpressionEvalError = class extends Error {
1138
1138
  //#endregion
1139
1139
  //#region src/expression/builtins.ts
1140
1140
  function isStatefulBuiltin(name) {
1141
- return name === "rose" || name === "count" || name === "dwell" || name === "latchedAt";
1141
+ return name === "rose" || name === "count" || name === "dwell" || name === "latchedAt" || name === "schmitt";
1142
1142
  }
1143
1143
  function asFiniteNumber(value, name, index) {
1144
1144
  if (typeof value !== "number" || !Number.isFinite(value)) throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a finite number`);
@@ -1266,6 +1266,41 @@ function evaluateDwell(activeRaw, onForMsRaw, offForMsRaw, call, nowMs, hooks) {
1266
1266
  hooks?.noteDeadline?.(call.pendingSince + delay);
1267
1267
  return call.latched;
1268
1268
  }
1269
+ /**
1270
+ * `schmitt(x, high, low[, onForMs, offForMs])`: true once `x >= high`, false
1271
+ * once `x <= low`, and the last answer in between. A transition waits its delay
1272
+ * and is dropped if `x` returns to the held side first. The first answer needs
1273
+ * a crossing, or the restored output as `seed`: a reading inside the band with
1274
+ * nothing to hold is unknown, never off (D393). A null reading throws, so the
1275
+ * field is unavailable and the latch is kept.
1276
+ */
1277
+ function evaluateSchmitt(args, call, nowMs, seed, hooks) {
1278
+ const x = asFiniteNumber(args[0], "schmitt", 0);
1279
+ const high = asFiniteNumber(args[1], "schmitt", 1);
1280
+ const low = asFiniteNumber(args[2], "schmitt", 2);
1281
+ const onForMs = args.length > 3 ? asNonNegativeMs(args[3], "schmitt", 3) : 0;
1282
+ const offForMs = args.length > 4 ? asNonNegativeMs(args[4], "schmitt", 4) : 0;
1283
+ if (low > high) throw new ExpressionEvalError("schmitt: the low edge is above the high edge");
1284
+ if (call.latched === null && seed !== null) call.latched = seed;
1285
+ const wants = x >= high ? true : x <= low ? false : call.latched;
1286
+ if (call.latched === null) {
1287
+ if (wants !== null) call.latched = wants;
1288
+ return call.latched;
1289
+ }
1290
+ if (wants === null || wants === call.latched) {
1291
+ call.pendingSince = null;
1292
+ return call.latched;
1293
+ }
1294
+ if (call.pendingSince === null) call.pendingSince = nowMs;
1295
+ const delay = wants ? onForMs : offForMs;
1296
+ if (nowMs - call.pendingSince >= delay) {
1297
+ call.latched = wants;
1298
+ call.pendingSince = null;
1299
+ return call.latched;
1300
+ }
1301
+ hooks?.noteDeadline?.(call.pendingSince + delay);
1302
+ return call.latched;
1303
+ }
1269
1304
  /** `latchedAt(value)`: epoch ms when `value` last changed; null clears the stamp. */
1270
1305
  function evaluateLatchedAt(value, call, nowMs) {
1271
1306
  if (value === null) {
@@ -1463,6 +1498,13 @@ var table = {
1463
1498
  apply: () => {
1464
1499
  throw new ExpressionEvalError("latchedAt: stateful evaluation context is required");
1465
1500
  }
1501
+ },
1502
+ schmitt: {
1503
+ minArgs: 3,
1504
+ maxArgs: 5,
1505
+ apply: () => {
1506
+ throw new ExpressionEvalError("schmitt: stateful evaluation context is required");
1507
+ }
1466
1508
  }
1467
1509
  };
1468
1510
  /** Frozen, null-prototype builtin table. */
@@ -2072,6 +2114,16 @@ function evaluateStatefulExpressionCall(invocation, memory, env, hooks) {
2072
2114
  memory.calls.set(invocation.path, call);
2073
2115
  return evaluateDwell(invocation.args[0], invocation.args[1], invocation.args[2], call, env.nowMs, hooks);
2074
2116
  }
2117
+ case "schmitt": {
2118
+ const call = existing?.kind === "schmitt" ? existing : {
2119
+ kind: "schmitt",
2120
+ latched: null,
2121
+ pendingSince: null
2122
+ };
2123
+ memory.calls.set(invocation.path, call);
2124
+ const seed = typeof env.currentOutput === "boolean" ? env.currentOutput : null;
2125
+ return evaluateSchmitt(invocation.args, call, env.nowMs, seed, hooks);
2126
+ }
2075
2127
  case "latchedAt": {
2076
2128
  const call = existing?.kind === "latchedAt" ? existing : {
2077
2129
  kind: "latchedAt",
@@ -2442,6 +2494,58 @@ function integrateReading(read, scale) {
2442
2494
  for (const segment of covered.segments) unitMs += segment.value * segment.ms;
2443
2495
  return numberValue(scale === "energy-kwh" ? wattMillisecondsToKwh(unitMs) : unitMs / 1e3, covered.included, covered.gaps, GAP_REASON);
2444
2496
  }
2497
+ function samplesOf(read) {
2498
+ const out = [];
2499
+ if (read.before !== null && finite(read.before.value)) out.push({
2500
+ at: read.from,
2501
+ value: read.before.value
2502
+ });
2503
+ for (const point of read.points) {
2504
+ if (point.t < read.from || point.t > read.to) continue;
2505
+ const value = pointValue(point);
2506
+ if (value === null) continue;
2507
+ const at = read.tier === "raw" || !finite(point.twMs) ? point.t : point.t + point.twMs;
2508
+ out.push({
2509
+ at: Math.min(at, read.to),
2510
+ value
2511
+ });
2512
+ }
2513
+ return out.toSorted((a, b) => a.at - b.at);
2514
+ }
2515
+ /** A sample-less tail or head longer than this share of the window is counted as a gap. */
2516
+ var UNCOVERED_LIMIT = .1;
2517
+ var DERIVATIVE_GAP_REASON = "history stops short of the window — the rate is over the part that was measured";
2518
+ function derivativeValue(change, spanMs, perMs, included, windowMs) {
2519
+ return numberValue(change / (spanMs / perMs), included, windowMs - spanMs > windowMs * UNCOVERED_LIMIT ? 1 : 0, DERIVATIVE_GAP_REASON);
2520
+ }
2521
+ /**
2522
+ * Rate of change over a window, per `perMs`. The slope is taken between the
2523
+ * first and the last measured sample, so a hole inside the window neither
2524
+ * counts as zero nor stretches the elapsed time. A gauge that reported
2525
+ * nothing after the value held at the start is flat (rate 0); fewer than two
2526
+ * samples and no held value is a known gap (D393).
2527
+ */
2528
+ function evaluateDerivative(read, perMs) {
2529
+ if (read.truncated) return hold("the history query was truncated — the last rate is kept");
2530
+ if (read.to <= read.from) return gapValue("the range is empty", 0);
2531
+ const windowMs = read.to - read.from;
2532
+ const samples = samplesOf(read);
2533
+ const first = samples[0];
2534
+ const last = samples[samples.length - 1];
2535
+ if (first === void 0 || last === void 0) return gapValue("history has no reading in this window", 0);
2536
+ if (read.kind === "counter") {
2537
+ const heldAtStart = read.before !== null && finite(read.before.value);
2538
+ const startsAt = heldAtStart ? read.from : first.at;
2539
+ const deltas = read.points.flatMap((point) => point.t >= read.from && point.t <= read.to && finite(point.delta) && (heldAtStart || point.t > first.at) ? [point.delta] : []);
2540
+ const spanMs = last.at - startsAt;
2541
+ if (deltas.length === 0 || spanMs <= 0) return gapValue("the counter has no growth in this window", samples.length);
2542
+ return derivativeValue(deltas.reduce((sum, delta) => sum + delta, 0), spanMs, perMs, samples.length, windowMs);
2543
+ }
2544
+ if (samples.length === 1 && first.at === read.from && read.before !== null) return numberValue(0, 1, 0, null);
2545
+ const spanMs = last.at - first.at;
2546
+ if (samples.length < 2 || spanMs <= 0) return gapValue("the rate needs two readings in the window", samples.length);
2547
+ return derivativeValue(last.value - first.value, spanMs, perMs, samples.length, windowMs);
2548
+ }
2445
2549
  //#endregion
2446
2550
  //#region src/composition/helpers/history-window.ts
2447
2551
  /**
@@ -2805,7 +2909,13 @@ var GROUP_TEMPLATE_IDS = [
2805
2909
  "occupancy",
2806
2910
  "binary",
2807
2911
  "switch",
2808
- "light"
2912
+ "light",
2913
+ "fan",
2914
+ "cover",
2915
+ "valve",
2916
+ "lock",
2917
+ "media-player",
2918
+ "climate"
2809
2919
  ];
2810
2920
  var NUMERIC_AGGREGATES = [
2811
2921
  "mean",
@@ -2868,19 +2978,73 @@ var integralHelperObject = z.object({
2868
2978
  source: HelperMemberSchema
2869
2979
  });
2870
2980
  var IntegralHelperSpecSchema = integralHelperObject;
2981
+ var thresholdHelperObject = z.object({
2982
+ kind: z.literal("threshold"),
2983
+ source: HelperMemberSchema,
2984
+ high: z.number(),
2985
+ low: z.number(),
2986
+ onForMs: z.number().int().nonnegative(),
2987
+ offForMs: z.number().int().nonnegative()
2988
+ });
2989
+ var ThresholdHelperSpecSchema = thresholdHelperObject;
2990
+ var ARITHMETIC_OPS = [
2991
+ "sum",
2992
+ "difference",
2993
+ "ratio"
2994
+ ];
2995
+ var arithmeticHelperObject = z.object({
2996
+ kind: z.literal("arithmetic"),
2997
+ op: z.enum(ARITHMETIC_OPS),
2998
+ inputs: z.array(HelperMemberSchema).min(2).max(64)
2999
+ });
3000
+ var ArithmeticHelperSpecSchema = arithmeticHelperObject;
3001
+ var EXPRESSION_OUTPUTS = ["number", "boolean"];
3002
+ var expressionHelperObject = z.object({
3003
+ kind: z.literal("expression"),
3004
+ expr: z.string().min(1),
3005
+ inputs: z.array(z.object({
3006
+ name: z.string().min(1),
3007
+ member: HelperMemberSchema
3008
+ })).min(1).max(64),
3009
+ output: z.enum(EXPRESSION_OUTPUTS)
3010
+ });
3011
+ var ExpressionHelperSpecSchema = expressionHelperObject;
3012
+ var DERIVATIVE_UNITS = [
3013
+ "second",
3014
+ "minute",
3015
+ "hour"
3016
+ ];
3017
+ var derivativeHelperObject = z.object({
3018
+ kind: z.literal("derivative"),
3019
+ window: RollingWindowSchema,
3020
+ per: z.enum(DERIVATIVE_UNITS),
3021
+ source: HelperMemberSchema
3022
+ });
3023
+ var DerivativeHelperSpecSchema = derivativeHelperObject;
2871
3024
  var HelperSpecSchema = z.discriminatedUnion("kind", [
2872
3025
  GroupHelperSpecSchema,
2873
3026
  ApplianceHelperSpecSchema,
2874
3027
  statisticHelperObject,
2875
- integralHelperObject
3028
+ integralHelperObject,
3029
+ thresholdHelperObject,
3030
+ arithmeticHelperObject,
3031
+ expressionHelperObject,
3032
+ derivativeHelperObject
2876
3033
  ]);
3034
+ function isHistoryHelper(spec) {
3035
+ return spec.kind === "statistic" || spec.kind === "integral" || spec.kind === "derivative";
3036
+ }
2877
3037
  /** Every member a helper reads. A group has many; the others have one. */
2878
3038
  function helperMembers(spec) {
2879
3039
  switch (spec.kind) {
2880
3040
  case "group": return spec.members;
2881
3041
  case "appliance": return [spec.member];
2882
3042
  case "statistic":
2883
- case "integral": return [spec.source];
3043
+ case "integral":
3044
+ case "threshold":
3045
+ case "derivative": return [spec.source];
3046
+ case "arithmetic": return spec.inputs;
3047
+ case "expression": return spec.inputs.map((input) => input.member);
2884
3048
  }
2885
3049
  }
2886
3050
  //#endregion
@@ -2978,6 +3142,12 @@ var CompositionAggregateSourceSchema = z.object({
2978
3142
  "all"
2979
3143
  ]),
2980
3144
  reads: z.array(CompositionFieldReadSchema).min(1).max(64),
3145
+ /**
3146
+ * Set when the reads are an enum state, not a yes/no: the listed states
3147
+ * count as active (a cover is `open`). A state outside the list is inactive;
3148
+ * a read that is not a state at all is left out. Only `any` / `all` use it.
3149
+ */
3150
+ activeWhen: z.array(z.string().min(1)).min(1).optional(),
2981
3151
  /** `value` is the reading; the others are the member counts. */
2982
3152
  publish: z.enum([
2983
3153
  "value",
@@ -3168,4 +3338,4 @@ function featureSources(feature) {
3168
3338
  return featureFieldEntries(feature).map((e) => e.source);
3169
3339
  }
3170
3340
  //#endregion
3171
- export { NUMERIC_AGGREGATES as $, MAX_EXPRESSION_AST_NODES as $t, composedDeviceStableId as A, Fmp4BoxSplitter as An, KWH_PER_WATT_MS as At, AGGREGATE_REDUCERS as B, pickVideoEncoder as Bn, createStatefulExpressionMemory as Bt, CompositionSeriesSourceSchema as C, FFMPEG_PATH_ENV as Cn, planStatisticTier as Ct, MAX_COMPOSITION_FIELDS_PER_FEATURE as D, getFfmpegArtifact as Dn, STATISTIC_STATS as Dt, MAX_COMPOSITION_FEATURES as E, FFMPEG_RELEASE as En, SERIES_KINDS as Et, compositionSourceKey as F, buildInputArgs as Fn, applianceDeviceType as Ft, GROUP_TEMPLATE_IDS as G, createExpressionScope as Gt, AggregateReducerSchema as H, evaluateStatefulExpressionCall as Ht, featureFieldEntries as I, buildVideoArgs as In, isApplianceKind as It, HelperMemberSchema as J, compileExpressionSafe as Jt, GroupHelperSpecSchema as K, evaluateAst as Kt, featureSources as L, invocationFromEncodeProfile as Ln, sourcePickForAppliance as Lt, compositionItemFieldPath as M, audioPlanFromEncodeProfile as Mn, APPLIANCE_DEFAULTS as Mt, compositionReadKey as N, buildAudioArgs as Nn, APPLIANCE_KINDS as Nt, MAX_COMPOSITION_ITEMS as O, getFfmpegDownloadUrl as On, evaluateStatistic as Ot, compositionSliceKey as P, buildFfmpegArgs as Pn, APPLIANCE_TEMPLATE_ID as Pt, MAX_HELPER_MEMBERS as Q, EXPRESSION_IDENTIFIER_RE as Qt, fieldReads as R, isSoftwareDecode as Rn, EXPRESSION_INJECTED_NOW as Rt, CompositionSchema as S, sliceChangedAt as Sn, planResetTier as St, MAX_COMPOSITION_CODE_LENGTH as T, ffmpegBinaryName as Tn, statisticCadenceMs as Tt, ApplianceHelperSpecSchema as U, toExpressionValue as Ut, ActiveCriterionSchema as V, resolveStreamMaps as Vn, evaluateExpressionSource as Vt, BINARY_AGGREGATES as W, validateExpressionSource as Wt, INTEGRAL_OUTPUTS as X, tokenize as Xt, HelperSpecSchema as Y, parseExpression as Yt, IntegralHelperSpecSchema as Z, EXPRESSION_COMPILE_CACHE_CAPACITY as Zt, CompositionFromSourceSchema as _, SOURCE_DEVICE_TYPES as _n, integralCadenceMs as _t, CUSTOMIZATION_BLOCK_NAME_PREFIX as a, EXPRESSION_BUILTINS as an, DAY_MS as at, CompositionNewTargetSchema as b, isSourceCap as bn, openReset as bt, CompositionCodeSourceSchema as c, evaluateCount as cn, HISTORY_SLOW_MS as ct, CompositionExpressionSourceSchema as d, ExpressionEvalError as dn, MINUTE_MS as dt, MAX_EXPRESSION_BINDINGS as en, StatisticHelperSpecSchema as et, CompositionFeatureSchema as f, ExpressionParseError as fn, MIN_CUSTOM_MINUTES as ft, CompositionForwardCommandSchema as g, SOURCE_CAP_CHANGED_AT_FIELD as gn, StatisticWindowSchema as gt, CompositionFieldsFeatureSchema as h, SOURCE_CAP_ACTIVE_FIELD as hn, RollingWindowSchema as ht, COMPOSITION_ITEM_KEY_RE as i, RESERVED_BINDING_NAMES as in, CalendarWindowSchema as it, compositionFeatureEnabled as j, AUDIO_PRESETS as jn, wattMillisecondsToKwh as jt, composedDeviceRef as k, canonicalHash as kn, integrateReading as kt, CompositionCommandTargetSchema as l, evaluateRose as ln, HISTORY_TIERS as lt, CompositionFieldSourceSchema as m, SOURCE_CAPS as mn, ROLLING_SPANS as mt, COMPOSER_ADDON_ID as n, MAX_EXPRESSION_EVAL_STEPS as nn, CompositionSourceRefSchema as nt, CompositionAggregateSourceSchema as o, EXPRESSION_BUILTIN_NAMES as on, DEFAULT_HISTORY_RETENTION as ot, CompositionFieldReadSchema as p, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as pn, RESET_PERIODS as pt, GroupTemplateIdSchema as q, compileExpression as qt, COMPOSITION_ITEM_ARRAY_PATH as r, MAX_EXPRESSION_SOURCE_LENGTH as rn, CALENDAR_PERIODS as rt, CompositionBindingSchema as s, disarmRoseMemory as sn, HISTORY_FAST_MS as st, COMPOSED_DEVICE_STABLE_ID_PREFIX as t, MAX_EXPRESSION_CALL_ARGS as tn, helperMembers as tt, CompositionExistingTargetSchema as u, isStatefulBuiltin as un, HOUR_MS as ut, CompositionItemEntrySchema as v, evaluateSensorEdge as vn, maxDurationMs as vt, CompositionTargetSchema as w, chooseFfmpegBinary as wn, rollingDurationMs as wt, CompositionPassthroughFeatureSchema as x, sliceActiveValue as xn, openStatistic as xt, CompositionLiteralBindingSchema as y, evaluateSensorLevelEdge as yn, nextLocalMidnight as yt, ACTIVE_CRITERIA as z, logBannerArgs as zn, cloneStatefulExpressionMemory as zt };
3341
+ export { GroupTemplateIdSchema as $, validateExpressionSource as $t, composedDeviceStableId as A, sliceActiveValue as An, planResetTier as At, AGGREGATE_REDUCERS as B, AUDIO_PRESETS as Bn, wattMillisecondsToKwh as Bt, CompositionSeriesSourceSchema as C, SOURCE_CAPS as Cn, RollingWindowSchema as Ct, MAX_COMPOSITION_FIELDS_PER_FEATURE as D, evaluateSensorEdge as Dn, nextLocalMidnight as Dt, MAX_COMPOSITION_FEATURES as E, SOURCE_DEVICE_TYPES as En, maxDurationMs as Et, compositionSourceKey as F, FFMPEG_RELEASE as Fn, STATISTIC_STATS as Ft, ArithmeticHelperSpecSchema as G, buildVideoArgs as Gn, isApplianceKind as Gt, ActiveCriterionSchema as H, buildAudioArgs as Hn, APPLIANCE_KINDS as Ht, featureFieldEntries as I, getFfmpegArtifact as In, evaluateDerivative as It, DerivativeHelperSpecSchema as J, logBannerArgs as Jn, cloneStatefulExpressionMemory as Jt, BINARY_AGGREGATES as K, invocationFromEncodeProfile as Kn, sourcePickForAppliance as Kt, featureSources as L, getFfmpegDownloadUrl as Ln, evaluateStatistic as Lt, compositionItemFieldPath as M, FFMPEG_PATH_ENV as Mn, rollingDurationMs as Mt, compositionReadKey as N, chooseFfmpegBinary as Nn, statisticCadenceMs as Nt, MAX_COMPOSITION_ITEMS as O, evaluateSensorLevelEdge as On, openReset as Ot, compositionSliceKey as P, ffmpegBinaryName as Pn, SERIES_KINDS as Pt, GroupHelperSpecSchema as Q, toExpressionValue as Qt, fieldReads as R, canonicalHash as Rn, integrateReading as Rt, CompositionSchema as S, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as Sn, ROLLING_SPANS as St, MAX_COMPOSITION_CODE_LENGTH as T, SOURCE_CAP_CHANGED_AT_FIELD as Tn, integralCadenceMs as Tt, AggregateReducerSchema as U, buildFfmpegArgs as Un, APPLIANCE_TEMPLATE_ID as Ut, ARITHMETIC_OPS as V, audioPlanFromEncodeProfile as Vn, APPLIANCE_DEFAULTS as Vt, ApplianceHelperSpecSchema as W, buildInputArgs as Wn, applianceDeviceType as Wt, ExpressionHelperSpecSchema as X, resolveStreamMaps as Xn, evaluateExpressionSource as Xt, EXPRESSION_OUTPUTS as Y, pickVideoEncoder as Yn, createStatefulExpressionMemory as Yt, GROUP_TEMPLATE_IDS as Z, evaluateStatefulExpressionCall as Zt, CompositionFromSourceSchema as _, evaluateCount as _n, HISTORY_TIERS as _t, CUSTOMIZATION_BLOCK_NAME_PREFIX as a, tokenize as an, NUMERIC_AGGREGATES as at, CompositionNewTargetSchema as b, ExpressionEvalError as bn, MIN_CUSTOM_MINUTES as bt, CompositionCodeSourceSchema as c, MAX_EXPRESSION_AST_NODES as cn, helperMembers as ct, CompositionExpressionSourceSchema as d, MAX_EXPRESSION_EVAL_STEPS as dn, CALENDAR_PERIODS as dt, createExpressionScope as en, HelperMemberSchema as et, CompositionFeatureSchema as f, MAX_EXPRESSION_SOURCE_LENGTH as fn, CalendarWindowSchema as ft, CompositionForwardCommandSchema as g, disarmRoseMemory as gn, HISTORY_SLOW_MS as gt, CompositionFieldsFeatureSchema as h, EXPRESSION_BUILTIN_NAMES as hn, HISTORY_FAST_MS as ht, COMPOSITION_ITEM_KEY_RE as i, parseExpression as in, MAX_HELPER_MEMBERS as it, compositionFeatureEnabled as j, sliceChangedAt as jn, planStatisticTier as jt, composedDeviceRef as k, isSourceCap as kn, openStatistic as kt, CompositionCommandTargetSchema as l, MAX_EXPRESSION_BINDINGS as ln, isHistoryHelper as lt, CompositionFieldSourceSchema as m, EXPRESSION_BUILTINS as mn, DEFAULT_HISTORY_RETENTION as mt, COMPOSER_ADDON_ID as n, compileExpression as nn, INTEGRAL_OUTPUTS as nt, CompositionAggregateSourceSchema as o, EXPRESSION_COMPILE_CACHE_CAPACITY as on, StatisticHelperSpecSchema as ot, CompositionFieldReadSchema as p, RESERVED_BINDING_NAMES as pn, DAY_MS as pt, DERIVATIVE_UNITS as q, isSoftwareDecode as qn, EXPRESSION_INJECTED_NOW as qt, COMPOSITION_ITEM_ARRAY_PATH as r, compileExpressionSafe as rn, IntegralHelperSpecSchema as rt, CompositionBindingSchema as s, EXPRESSION_IDENTIFIER_RE as sn, ThresholdHelperSpecSchema as st, COMPOSED_DEVICE_STABLE_ID_PREFIX as t, evaluateAst as tn, HelperSpecSchema as tt, CompositionExistingTargetSchema as u, MAX_EXPRESSION_CALL_ARGS as un, CompositionSourceRefSchema as ut, CompositionItemEntrySchema as v, evaluateRose as vn, HOUR_MS as vt, CompositionTargetSchema as w, SOURCE_CAP_ACTIVE_FIELD as wn, StatisticWindowSchema as wt, CompositionPassthroughFeatureSchema as x, ExpressionParseError as xn, RESET_PERIODS as xt, CompositionLiteralBindingSchema as y, isStatefulBuiltin as yn, MINUTE_MS as yt, ACTIVE_CRITERIA as z, Fmp4BoxSplitter as zn, KWH_PER_WATT_MS as zt };
@@ -1138,7 +1138,7 @@ var ExpressionEvalError = class extends Error {
1138
1138
  //#endregion
1139
1139
  //#region src/expression/builtins.ts
1140
1140
  function isStatefulBuiltin(name) {
1141
- return name === "rose" || name === "count" || name === "dwell" || name === "latchedAt";
1141
+ return name === "rose" || name === "count" || name === "dwell" || name === "latchedAt" || name === "schmitt";
1142
1142
  }
1143
1143
  function asFiniteNumber(value, name, index) {
1144
1144
  if (typeof value !== "number" || !Number.isFinite(value)) throw new ExpressionEvalError(`${name}: argument ${index + 1} must be a finite number`);
@@ -1266,6 +1266,41 @@ function evaluateDwell(activeRaw, onForMsRaw, offForMsRaw, call, nowMs, hooks) {
1266
1266
  hooks?.noteDeadline?.(call.pendingSince + delay);
1267
1267
  return call.latched;
1268
1268
  }
1269
+ /**
1270
+ * `schmitt(x, high, low[, onForMs, offForMs])`: true once `x >= high`, false
1271
+ * once `x <= low`, and the last answer in between. A transition waits its delay
1272
+ * and is dropped if `x` returns to the held side first. The first answer needs
1273
+ * a crossing, or the restored output as `seed`: a reading inside the band with
1274
+ * nothing to hold is unknown, never off (D393). A null reading throws, so the
1275
+ * field is unavailable and the latch is kept.
1276
+ */
1277
+ function evaluateSchmitt(args, call, nowMs, seed, hooks) {
1278
+ const x = asFiniteNumber(args[0], "schmitt", 0);
1279
+ const high = asFiniteNumber(args[1], "schmitt", 1);
1280
+ const low = asFiniteNumber(args[2], "schmitt", 2);
1281
+ const onForMs = args.length > 3 ? asNonNegativeMs(args[3], "schmitt", 3) : 0;
1282
+ const offForMs = args.length > 4 ? asNonNegativeMs(args[4], "schmitt", 4) : 0;
1283
+ if (low > high) throw new ExpressionEvalError("schmitt: the low edge is above the high edge");
1284
+ if (call.latched === null && seed !== null) call.latched = seed;
1285
+ const wants = x >= high ? true : x <= low ? false : call.latched;
1286
+ if (call.latched === null) {
1287
+ if (wants !== null) call.latched = wants;
1288
+ return call.latched;
1289
+ }
1290
+ if (wants === null || wants === call.latched) {
1291
+ call.pendingSince = null;
1292
+ return call.latched;
1293
+ }
1294
+ if (call.pendingSince === null) call.pendingSince = nowMs;
1295
+ const delay = wants ? onForMs : offForMs;
1296
+ if (nowMs - call.pendingSince >= delay) {
1297
+ call.latched = wants;
1298
+ call.pendingSince = null;
1299
+ return call.latched;
1300
+ }
1301
+ hooks?.noteDeadline?.(call.pendingSince + delay);
1302
+ return call.latched;
1303
+ }
1269
1304
  /** `latchedAt(value)`: epoch ms when `value` last changed; null clears the stamp. */
1270
1305
  function evaluateLatchedAt(value, call, nowMs) {
1271
1306
  if (value === null) {
@@ -1463,6 +1498,13 @@ var table = {
1463
1498
  apply: () => {
1464
1499
  throw new ExpressionEvalError("latchedAt: stateful evaluation context is required");
1465
1500
  }
1501
+ },
1502
+ schmitt: {
1503
+ minArgs: 3,
1504
+ maxArgs: 5,
1505
+ apply: () => {
1506
+ throw new ExpressionEvalError("schmitt: stateful evaluation context is required");
1507
+ }
1466
1508
  }
1467
1509
  };
1468
1510
  /** Frozen, null-prototype builtin table. */
@@ -2072,6 +2114,16 @@ function evaluateStatefulExpressionCall(invocation, memory, env, hooks) {
2072
2114
  memory.calls.set(invocation.path, call);
2073
2115
  return evaluateDwell(invocation.args[0], invocation.args[1], invocation.args[2], call, env.nowMs, hooks);
2074
2116
  }
2117
+ case "schmitt": {
2118
+ const call = existing?.kind === "schmitt" ? existing : {
2119
+ kind: "schmitt",
2120
+ latched: null,
2121
+ pendingSince: null
2122
+ };
2123
+ memory.calls.set(invocation.path, call);
2124
+ const seed = typeof env.currentOutput === "boolean" ? env.currentOutput : null;
2125
+ return evaluateSchmitt(invocation.args, call, env.nowMs, seed, hooks);
2126
+ }
2075
2127
  case "latchedAt": {
2076
2128
  const call = existing?.kind === "latchedAt" ? existing : {
2077
2129
  kind: "latchedAt",
@@ -2442,6 +2494,58 @@ function integrateReading(read, scale) {
2442
2494
  for (const segment of covered.segments) unitMs += segment.value * segment.ms;
2443
2495
  return numberValue(scale === "energy-kwh" ? wattMillisecondsToKwh(unitMs) : unitMs / 1e3, covered.included, covered.gaps, GAP_REASON);
2444
2496
  }
2497
+ function samplesOf(read) {
2498
+ const out = [];
2499
+ if (read.before !== null && finite(read.before.value)) out.push({
2500
+ at: read.from,
2501
+ value: read.before.value
2502
+ });
2503
+ for (const point of read.points) {
2504
+ if (point.t < read.from || point.t > read.to) continue;
2505
+ const value = pointValue(point);
2506
+ if (value === null) continue;
2507
+ const at = read.tier === "raw" || !finite(point.twMs) ? point.t : point.t + point.twMs;
2508
+ out.push({
2509
+ at: Math.min(at, read.to),
2510
+ value
2511
+ });
2512
+ }
2513
+ return out.toSorted((a, b) => a.at - b.at);
2514
+ }
2515
+ /** A sample-less tail or head longer than this share of the window is counted as a gap. */
2516
+ var UNCOVERED_LIMIT = .1;
2517
+ var DERIVATIVE_GAP_REASON = "history stops short of the window — the rate is over the part that was measured";
2518
+ function derivativeValue(change, spanMs, perMs, included, windowMs) {
2519
+ return numberValue(change / (spanMs / perMs), included, windowMs - spanMs > windowMs * UNCOVERED_LIMIT ? 1 : 0, DERIVATIVE_GAP_REASON);
2520
+ }
2521
+ /**
2522
+ * Rate of change over a window, per `perMs`. The slope is taken between the
2523
+ * first and the last measured sample, so a hole inside the window neither
2524
+ * counts as zero nor stretches the elapsed time. A gauge that reported
2525
+ * nothing after the value held at the start is flat (rate 0); fewer than two
2526
+ * samples and no held value is a known gap (D393).
2527
+ */
2528
+ function evaluateDerivative(read, perMs) {
2529
+ if (read.truncated) return hold("the history query was truncated — the last rate is kept");
2530
+ if (read.to <= read.from) return gapValue("the range is empty", 0);
2531
+ const windowMs = read.to - read.from;
2532
+ const samples = samplesOf(read);
2533
+ const first = samples[0];
2534
+ const last = samples[samples.length - 1];
2535
+ if (first === void 0 || last === void 0) return gapValue("history has no reading in this window", 0);
2536
+ if (read.kind === "counter") {
2537
+ const heldAtStart = read.before !== null && finite(read.before.value);
2538
+ const startsAt = heldAtStart ? read.from : first.at;
2539
+ const deltas = read.points.flatMap((point) => point.t >= read.from && point.t <= read.to && finite(point.delta) && (heldAtStart || point.t > first.at) ? [point.delta] : []);
2540
+ const spanMs = last.at - startsAt;
2541
+ if (deltas.length === 0 || spanMs <= 0) return gapValue("the counter has no growth in this window", samples.length);
2542
+ return derivativeValue(deltas.reduce((sum, delta) => sum + delta, 0), spanMs, perMs, samples.length, windowMs);
2543
+ }
2544
+ if (samples.length === 1 && first.at === read.from && read.before !== null) return numberValue(0, 1, 0, null);
2545
+ const spanMs = last.at - first.at;
2546
+ if (samples.length < 2 || spanMs <= 0) return gapValue("the rate needs two readings in the window", samples.length);
2547
+ return derivativeValue(last.value - first.value, spanMs, perMs, samples.length, windowMs);
2548
+ }
2445
2549
  //#endregion
2446
2550
  //#region src/composition/helpers/history-window.ts
2447
2551
  /**
@@ -2805,7 +2909,13 @@ var GROUP_TEMPLATE_IDS = [
2805
2909
  "occupancy",
2806
2910
  "binary",
2807
2911
  "switch",
2808
- "light"
2912
+ "light",
2913
+ "fan",
2914
+ "cover",
2915
+ "valve",
2916
+ "lock",
2917
+ "media-player",
2918
+ "climate"
2809
2919
  ];
2810
2920
  var NUMERIC_AGGREGATES = [
2811
2921
  "mean",
@@ -2868,19 +2978,73 @@ var integralHelperObject = zod.z.object({
2868
2978
  source: HelperMemberSchema
2869
2979
  });
2870
2980
  var IntegralHelperSpecSchema = integralHelperObject;
2981
+ var thresholdHelperObject = zod.z.object({
2982
+ kind: zod.z.literal("threshold"),
2983
+ source: HelperMemberSchema,
2984
+ high: zod.z.number(),
2985
+ low: zod.z.number(),
2986
+ onForMs: zod.z.number().int().nonnegative(),
2987
+ offForMs: zod.z.number().int().nonnegative()
2988
+ });
2989
+ var ThresholdHelperSpecSchema = thresholdHelperObject;
2990
+ var ARITHMETIC_OPS = [
2991
+ "sum",
2992
+ "difference",
2993
+ "ratio"
2994
+ ];
2995
+ var arithmeticHelperObject = zod.z.object({
2996
+ kind: zod.z.literal("arithmetic"),
2997
+ op: zod.z.enum(ARITHMETIC_OPS),
2998
+ inputs: zod.z.array(HelperMemberSchema).min(2).max(64)
2999
+ });
3000
+ var ArithmeticHelperSpecSchema = arithmeticHelperObject;
3001
+ var EXPRESSION_OUTPUTS = ["number", "boolean"];
3002
+ var expressionHelperObject = zod.z.object({
3003
+ kind: zod.z.literal("expression"),
3004
+ expr: zod.z.string().min(1),
3005
+ inputs: zod.z.array(zod.z.object({
3006
+ name: zod.z.string().min(1),
3007
+ member: HelperMemberSchema
3008
+ })).min(1).max(64),
3009
+ output: zod.z.enum(EXPRESSION_OUTPUTS)
3010
+ });
3011
+ var ExpressionHelperSpecSchema = expressionHelperObject;
3012
+ var DERIVATIVE_UNITS = [
3013
+ "second",
3014
+ "minute",
3015
+ "hour"
3016
+ ];
3017
+ var derivativeHelperObject = zod.z.object({
3018
+ kind: zod.z.literal("derivative"),
3019
+ window: RollingWindowSchema,
3020
+ per: zod.z.enum(DERIVATIVE_UNITS),
3021
+ source: HelperMemberSchema
3022
+ });
3023
+ var DerivativeHelperSpecSchema = derivativeHelperObject;
2871
3024
  var HelperSpecSchema = zod.z.discriminatedUnion("kind", [
2872
3025
  GroupHelperSpecSchema,
2873
3026
  ApplianceHelperSpecSchema,
2874
3027
  statisticHelperObject,
2875
- integralHelperObject
3028
+ integralHelperObject,
3029
+ thresholdHelperObject,
3030
+ arithmeticHelperObject,
3031
+ expressionHelperObject,
3032
+ derivativeHelperObject
2876
3033
  ]);
3034
+ function isHistoryHelper(spec) {
3035
+ return spec.kind === "statistic" || spec.kind === "integral" || spec.kind === "derivative";
3036
+ }
2877
3037
  /** Every member a helper reads. A group has many; the others have one. */
2878
3038
  function helperMembers(spec) {
2879
3039
  switch (spec.kind) {
2880
3040
  case "group": return spec.members;
2881
3041
  case "appliance": return [spec.member];
2882
3042
  case "statistic":
2883
- case "integral": return [spec.source];
3043
+ case "integral":
3044
+ case "threshold":
3045
+ case "derivative": return [spec.source];
3046
+ case "arithmetic": return spec.inputs;
3047
+ case "expression": return spec.inputs.map((input) => input.member);
2884
3048
  }
2885
3049
  }
2886
3050
  //#endregion
@@ -2978,6 +3142,12 @@ var CompositionAggregateSourceSchema = zod.z.object({
2978
3142
  "all"
2979
3143
  ]),
2980
3144
  reads: zod.z.array(CompositionFieldReadSchema).min(1).max(64),
3145
+ /**
3146
+ * Set when the reads are an enum state, not a yes/no: the listed states
3147
+ * count as active (a cover is `open`). A state outside the list is inactive;
3148
+ * a read that is not a state at all is left out. Only `any` / `all` use it.
3149
+ */
3150
+ activeWhen: zod.z.array(zod.z.string().min(1)).min(1).optional(),
2981
3151
  /** `value` is the reading; the others are the member counts. */
2982
3152
  publish: zod.z.enum([
2983
3153
  "value",
@@ -3198,6 +3368,12 @@ Object.defineProperty(exports, "APPLIANCE_TEMPLATE_ID", {
3198
3368
  return APPLIANCE_TEMPLATE_ID;
3199
3369
  }
3200
3370
  });
3371
+ Object.defineProperty(exports, "ARITHMETIC_OPS", {
3372
+ enumerable: true,
3373
+ get: function() {
3374
+ return ARITHMETIC_OPS;
3375
+ }
3376
+ });
3201
3377
  Object.defineProperty(exports, "AUDIO_PRESETS", {
3202
3378
  enumerable: true,
3203
3379
  get: function() {
@@ -3222,6 +3398,12 @@ Object.defineProperty(exports, "ApplianceHelperSpecSchema", {
3222
3398
  return ApplianceHelperSpecSchema;
3223
3399
  }
3224
3400
  });
3401
+ Object.defineProperty(exports, "ArithmeticHelperSpecSchema", {
3402
+ enumerable: true,
3403
+ get: function() {
3404
+ return ArithmeticHelperSpecSchema;
3405
+ }
3406
+ });
3225
3407
  Object.defineProperty(exports, "BINARY_AGGREGATES", {
3226
3408
  enumerable: true,
3227
3409
  get: function() {
@@ -3408,6 +3590,18 @@ Object.defineProperty(exports, "DEFAULT_HISTORY_RETENTION", {
3408
3590
  return DEFAULT_HISTORY_RETENTION;
3409
3591
  }
3410
3592
  });
3593
+ Object.defineProperty(exports, "DERIVATIVE_UNITS", {
3594
+ enumerable: true,
3595
+ get: function() {
3596
+ return DERIVATIVE_UNITS;
3597
+ }
3598
+ });
3599
+ Object.defineProperty(exports, "DerivativeHelperSpecSchema", {
3600
+ enumerable: true,
3601
+ get: function() {
3602
+ return DerivativeHelperSpecSchema;
3603
+ }
3604
+ });
3411
3605
  Object.defineProperty(exports, "EXPRESSION_BUILTINS", {
3412
3606
  enumerable: true,
3413
3607
  get: function() {
@@ -3438,12 +3632,24 @@ Object.defineProperty(exports, "EXPRESSION_INJECTED_NOW", {
3438
3632
  return EXPRESSION_INJECTED_NOW;
3439
3633
  }
3440
3634
  });
3635
+ Object.defineProperty(exports, "EXPRESSION_OUTPUTS", {
3636
+ enumerable: true,
3637
+ get: function() {
3638
+ return EXPRESSION_OUTPUTS;
3639
+ }
3640
+ });
3441
3641
  Object.defineProperty(exports, "ExpressionEvalError", {
3442
3642
  enumerable: true,
3443
3643
  get: function() {
3444
3644
  return ExpressionEvalError;
3445
3645
  }
3446
3646
  });
3647
+ Object.defineProperty(exports, "ExpressionHelperSpecSchema", {
3648
+ enumerable: true,
3649
+ get: function() {
3650
+ return ExpressionHelperSpecSchema;
3651
+ }
3652
+ });
3447
3653
  Object.defineProperty(exports, "ExpressionParseError", {
3448
3654
  enumerable: true,
3449
3655
  get: function() {
@@ -3690,6 +3896,12 @@ Object.defineProperty(exports, "StatisticWindowSchema", {
3690
3896
  return StatisticWindowSchema;
3691
3897
  }
3692
3898
  });
3899
+ Object.defineProperty(exports, "ThresholdHelperSpecSchema", {
3900
+ enumerable: true,
3901
+ get: function() {
3902
+ return ThresholdHelperSpecSchema;
3903
+ }
3904
+ });
3693
3905
  Object.defineProperty(exports, "applianceDeviceType", {
3694
3906
  enumerable: true,
3695
3907
  get: function() {
@@ -3828,6 +4040,12 @@ Object.defineProperty(exports, "evaluateCount", {
3828
4040
  return evaluateCount;
3829
4041
  }
3830
4042
  });
4043
+ Object.defineProperty(exports, "evaluateDerivative", {
4044
+ enumerable: true,
4045
+ get: function() {
4046
+ return evaluateDerivative;
4047
+ }
4048
+ });
3831
4049
  Object.defineProperty(exports, "evaluateExpressionSource", {
3832
4050
  enumerable: true,
3833
4051
  get: function() {
@@ -3930,6 +4148,12 @@ Object.defineProperty(exports, "isApplianceKind", {
3930
4148
  return isApplianceKind;
3931
4149
  }
3932
4150
  });
4151
+ Object.defineProperty(exports, "isHistoryHelper", {
4152
+ enumerable: true,
4153
+ get: function() {
4154
+ return isHistoryHelper;
4155
+ }
4156
+ });
3933
4157
  Object.defineProperty(exports, "isSoftwareDecode", {
3934
4158
  enumerable: true,
3935
4159
  get: function() {
@@ -1,5 +1,5 @@
1
1
  import type { CapVocabularySlice } from '../types.js';
2
- type SensorCap = 'air-quality-sensor' | 'ambient-light-sensor' | 'binary' | 'carbon-monoxide' | 'connectivity' | 'contact' | 'enum-sensor' | 'flood' | 'gas' | 'group-commands' | 'group-status' | 'humidity-sensor' | 'meter' | 'motion' | 'numeric-sensor' | 'power-meter' | 'presence' | 'pressure-sensor' | 'smoke' | 'tamper' | 'temperature-sensor' | 'vibration' | 'weather' | 'battery' | 'network-link' | 'device-status' | 'doorbell' | 'event-emitter';
2
+ type SensorCap = 'air-quality-sensor' | 'ambient-light-sensor' | 'binary' | 'carbon-monoxide' | 'connectivity' | 'contact' | 'enum-sensor' | 'flood' | 'gas' | 'group-active' | 'group-commands' | 'group-status' | 'humidity-sensor' | 'meter' | 'motion' | 'numeric-sensor' | 'power-meter' | 'presence' | 'pressure-sensor' | 'smoke' | 'tamper' | 'temperature-sensor' | 'vibration' | 'weather' | 'battery' | 'network-link' | 'device-status' | 'doorbell' | 'event-emitter';
3
3
  /** The sensor family of the vocabulary. */
4
4
  export declare const SENSOR_CAP_VOCABULARY: CapVocabularySlice<SensorCap>;
5
5
  export {};
@@ -16,7 +16,7 @@
16
16
  * closed rather than emitting a garbage value.
17
17
  */
18
18
  import type { ExpressionValue } from './ast.js';
19
- import type { CountCallState, DwellCallState, LatchedAtCallState, RoseCallState, StatefulExpressionMemory } from './expression-source.js';
19
+ import type { CountCallState, DwellCallState, LatchedAtCallState, RoseCallState, SchmittCallState, StatefulExpressionMemory } from './expression-source.js';
20
20
  /** Optional runtime hooks the evaluator threads through to builtins. */
21
21
  export interface ExpressionEvalHooks {
22
22
  /** Convert `value` from unit `from` to unit `to`; `null` when the pair is
@@ -41,7 +41,7 @@ export interface ExpressionBuiltin {
41
41
  readonly maxArgs: number;
42
42
  readonly apply: (args: readonly ExpressionValue[], hooks: ExpressionEvalHooks) => ExpressionValue;
43
43
  }
44
- export type StatefulExpressionBuiltin = 'rose' | 'count' | 'dwell' | 'latchedAt';
44
+ export type StatefulExpressionBuiltin = 'rose' | 'count' | 'dwell' | 'latchedAt' | 'schmitt';
45
45
  export declare function isStatefulBuiltin(name: string): name is StatefulExpressionBuiltin;
46
46
  export declare function evaluateRose(level: unknown, changedAt: unknown, debounceMs: unknown, call: RoseCallState, field: StatefulExpressionMemory, env: {
47
47
  readonly currentOutput: number | null;
@@ -55,6 +55,15 @@ export declare function disarmRoseMemory(memory: StatefulExpressionMemory): void
55
55
  * the latched boolean through gaps shorter than the opposing delay (D933).
56
56
  */
57
57
  export declare function evaluateDwell(activeRaw: unknown, onForMsRaw: unknown, offForMsRaw: unknown, call: DwellCallState, nowMs: number, hooks?: ExpressionEvalHooks): boolean | null;
58
+ /**
59
+ * `schmitt(x, high, low[, onForMs, offForMs])`: true once `x >= high`, false
60
+ * once `x <= low`, and the last answer in between. A transition waits its delay
61
+ * and is dropped if `x` returns to the held side first. The first answer needs
62
+ * a crossing, or the restored output as `seed`: a reading inside the band with
63
+ * nothing to hold is unknown, never off (D393). A null reading throws, so the
64
+ * field is unavailable and the latch is kept.
65
+ */
66
+ export declare function evaluateSchmitt(args: readonly unknown[], call: SchmittCallState, nowMs: number, seed: boolean | null, hooks?: ExpressionEvalHooks): boolean | null;
58
67
  /** `latchedAt(value)`: epoch ms when `value` last changed; null clears the stamp. */
59
68
  export declare function evaluateLatchedAt(value: unknown, call: LatchedAtCallState, nowMs: number): number | null;
60
69
  export declare function evaluateCount(input: unknown, call: CountCallState, currentOutput: unknown): number;
@@ -39,7 +39,12 @@ export interface LatchedAtCallState {
39
39
  last: ExpressionValue | undefined;
40
40
  at: number | null;
41
41
  }
42
- export type StatefulCallState = RoseCallState | CountCallState | DwellCallState | LatchedAtCallState;
42
+ export interface SchmittCallState {
43
+ readonly kind: 'schmitt';
44
+ latched: boolean | null;
45
+ pendingSince: number | null;
46
+ }
47
+ export type StatefulCallState = RoseCallState | CountCallState | DwellCallState | LatchedAtCallState | SchmittCallState;
43
48
  export interface StatefulExpressionMemory {
44
49
  readonly calls: Map<string, StatefulCallState>;
45
50
  acceptedEdgeAt: number | null;