@camstack/types 1.2.420 → 1.2.421

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/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_sleep = require("./sleep-YJ6FVsUu.js");
3
3
  const require_event_category = require("./event-category-Ave2VSxY.js");
4
- const require_composition = require("./composition-re1aezX9.js");
4
+ const require_composition = require("./composition-B3Hkb6v7.js");
5
5
  const require_node_color = require("./node-color.js");
6
- const require_err_msg = require("./err-msg-BhVbr0NX.js");
6
+ const require_err_msg = require("./err-msg-U7lLUMNz.js");
7
7
  const require_enums = require("./enums.js");
8
8
  let zod = require("zod");
9
9
  //#region src/media/media-owner-key.ts
@@ -13538,6 +13538,102 @@ function groupTemplate(id) {
13538
13538
  return BY_ID.get(id) ?? null;
13539
13539
  }
13540
13540
  //#endregion
13541
+ //#region src/composition/customize/scalar-expr.ts
13542
+ var UNSIGNED = /^\d+(?:\.\d+)?$/;
13543
+ var SIGNED = /^-?\d+(?:\.\d+)?$/;
13544
+ var DWELL = /^dwell\(x (>=|<=) (\d+(?:\.\d+)?), (\d+(?:\.\d+)?), (\d+(?:\.\d+)?)\)$/;
13545
+ var LINEAR_CLAMP = /^clamp\((x|number\(x\)) \* (-?\d+(?:\.\d+)?) \+ (-?\d+(?:\.\d+)?), (-?\d+(?:\.\d+)?), (-?\d+(?:\.\d+)?)\)$/;
13546
+ var LINEAR_BARE = /^(x|number\(x\)) \* (-?\d+(?:\.\d+)?) \+ (-?\d+(?:\.\d+)?)$/;
13547
+ var IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
13548
+ function unsignedToken(value) {
13549
+ return token(value, UNSIGNED);
13550
+ }
13551
+ function signedToken(value) {
13552
+ return token(value, SIGNED);
13553
+ }
13554
+ function constantExpr(value) {
13555
+ if (typeof value === "string") return JSON.stringify(value);
13556
+ if (typeof value === "number") return signedToken(value);
13557
+ if (typeof value === "boolean") return value ? "true" : "false";
13558
+ return "null";
13559
+ }
13560
+ function isIdentifier(path) {
13561
+ return IDENT.test(path);
13562
+ }
13563
+ function dwellExpr(comparison, threshold, start, stop) {
13564
+ return `dwell(x ${comparison === "gte" ? ">=" : "<="} ${threshold}, ${start}, ${stop})`;
13565
+ }
13566
+ function linearExpr(inner, scale, offset, clampMin, clampMax) {
13567
+ const arithmetic = `${inner} * ${scale} + ${offset}`;
13568
+ if (clampMin === null || clampMax === null) return arithmetic;
13569
+ return `clamp(${arithmetic}, ${clampMin}, ${clampMax})`;
13570
+ }
13571
+ function parseDwell(expr) {
13572
+ const match = DWELL.exec(expr);
13573
+ if (match === null) return null;
13574
+ const threshold = Number(match[2]);
13575
+ const startDwellMs = Number(match[3]);
13576
+ const stopDwellMs = Number(match[4]);
13577
+ if (!Number.isFinite(threshold) || !Number.isFinite(startDwellMs) || !Number.isFinite(stopDwellMs)) return null;
13578
+ return {
13579
+ comparison: match[1] === "<=" ? "lte" : "gte",
13580
+ threshold,
13581
+ startDwellMs,
13582
+ stopDwellMs
13583
+ };
13584
+ }
13585
+ function parseLinear(expr) {
13586
+ const clamped = LINEAR_CLAMP.exec(expr);
13587
+ if (clamped !== null) return linearFrom(clamped[1], clamped[2], clamped[3], clamped[4], clamped[5]);
13588
+ const bare = LINEAR_BARE.exec(expr);
13589
+ if (bare === null) return null;
13590
+ return linearFrom(bare[1], bare[2], bare[3], void 0, void 0);
13591
+ }
13592
+ function singleFrom(source) {
13593
+ if (source.kind === "from") return {
13594
+ cap: source.cap,
13595
+ fieldPath: source.fieldPath,
13596
+ addonId: source.source.addonId,
13597
+ stableId: source.source.stableId
13598
+ };
13599
+ if (source.kind !== "expression" || Object.keys(source.bindings).length !== 1) return null;
13600
+ const bound = source.bindings["x"];
13601
+ if (bound === void 0 || bound.kind !== "from") return null;
13602
+ return {
13603
+ cap: bound.cap,
13604
+ fieldPath: bound.fieldPath,
13605
+ addonId: bound.source.addonId,
13606
+ stableId: bound.source.stableId
13607
+ };
13608
+ }
13609
+ function token(value, pattern) {
13610
+ if (!Number.isFinite(value)) return null;
13611
+ const text = Object.is(value, -0) ? "0" : String(value);
13612
+ return pattern.test(text) ? text : null;
13613
+ }
13614
+ function linearFrom(inner, scale, offset, clampMin, clampMax) {
13615
+ if (inner !== "x" && inner !== "number(x)") return null;
13616
+ if (scale === void 0 || offset === void 0) return null;
13617
+ const parsedScale = Number(scale);
13618
+ const parsedOffset = Number(offset);
13619
+ if (!Number.isFinite(parsedScale) || !Number.isFinite(parsedOffset)) return null;
13620
+ if (clampMin === void 0 || clampMax === void 0) return {
13621
+ inner,
13622
+ scale: parsedScale,
13623
+ offset: parsedOffset
13624
+ };
13625
+ const parsedMin = Number(clampMin);
13626
+ const parsedMax = Number(clampMax);
13627
+ if (!Number.isFinite(parsedMin) || !Number.isFinite(parsedMax)) return null;
13628
+ return {
13629
+ inner,
13630
+ scale: parsedScale,
13631
+ offset: parsedOffset,
13632
+ clampMin: parsedMin,
13633
+ clampMax: parsedMax
13634
+ };
13635
+ }
13636
+ //#endregion
13541
13637
  //#region src/composition/helpers/actuator.ts
13542
13638
  /**
13543
13639
  * Actuator groups (D944 phase 1b). The active boolean is the binary reducer
@@ -13658,13 +13754,6 @@ function aggregateSource(template, spec, reads, publish) {
13658
13754
  empty: template.empty
13659
13755
  };
13660
13756
  }
13661
- function expressionSource(expr) {
13662
- return {
13663
- kind: "expression",
13664
- expr,
13665
- bindings: {}
13666
- };
13667
- }
13668
13757
  function draftOf$1(template, spec) {
13669
13758
  const reads = spec.members.map((member) => ({
13670
13759
  source: member.source,
@@ -13718,8 +13807,67 @@ function parseDraft(draft) {
13718
13807
  problems: [problem("helper-mismatch", "helper", `the template did not compile: ${parsed.error.issues.map((issue) => issue.message).join("; ")}`)]
13719
13808
  };
13720
13809
  }
13721
- /** Compile a group template to a new-device composition. Refusals are named. */
13810
+ function expressionSource(expr, bindings = {}) {
13811
+ return {
13812
+ kind: "expression",
13813
+ expr,
13814
+ bindings
13815
+ };
13816
+ }
13817
+ function compileAppliance(spec) {
13818
+ const member = spec.member;
13819
+ if (member.cap !== "power-meter" || member.fieldPath !== "watts") return {
13820
+ ok: false,
13821
+ problems: [problem("member-constraint", "helper.member", "an appliance helper reads power-meter.watts")]
13822
+ };
13823
+ const threshold = unsignedToken(spec.thresholdWatts);
13824
+ const start = unsignedToken(spec.startDwellMs);
13825
+ const stop = unsignedToken(spec.stopDwellMs);
13826
+ if (threshold === null || spec.thresholdWatts <= 0) return {
13827
+ ok: false,
13828
+ problems: [problem("kind-mismatch", "helper.thresholdWatts", "the threshold is watts above which the appliance is running")]
13829
+ };
13830
+ if (start === null || stop === null) return {
13831
+ ok: false,
13832
+ problems: [problem("kind-mismatch", "helper.startDwellMs", "start and stop dwells are a non-negative number of milliseconds")]
13833
+ };
13834
+ const read = {
13835
+ kind: "from",
13836
+ source: member.source,
13837
+ cap: member.cap,
13838
+ fieldPath: member.fieldPath
13839
+ };
13840
+ const dwell = dwellExpr("gte", threshold, start, stop);
13841
+ const clock = expressionSource("now");
13842
+ return parseDraft({
13843
+ target: {
13844
+ kind: "new",
13845
+ type: require_composition.applianceDeviceType(spec.appliance)
13846
+ },
13847
+ helper: spec,
13848
+ features: [{
13849
+ kind: "fields",
13850
+ cap: "running-state",
13851
+ fields: {
13852
+ running: expressionSource(dwell, { x: read }),
13853
+ source: expressionSource("\"assigned\""),
13854
+ phase: expressionSource("null"),
13855
+ lastFetchedAt: clock,
13856
+ since: expressionSource(`latchedAt(${dwell})`, { x: read })
13857
+ }
13858
+ }, {
13859
+ kind: "fields",
13860
+ cap: "power-meter",
13861
+ fields: {
13862
+ watts: read,
13863
+ lastFetchedAt: clock
13864
+ }
13865
+ }]
13866
+ });
13867
+ }
13868
+ /** Compile a helper template to a new-device composition. Refusals are named. */
13722
13869
  function compileHelper(spec) {
13870
+ if (spec.kind === "appliance") return compileAppliance(spec);
13723
13871
  const template = groupTemplate(spec.templateId);
13724
13872
  if (template === null) return {
13725
13873
  ok: false,
@@ -13749,10 +13897,15 @@ function sameHelperBody(stored, compiled) {
13749
13897
  return JSON.stringify(stored.target) === JSON.stringify(compiled.target) && JSON.stringify(stored.features) === JSON.stringify(compiled.features);
13750
13898
  }
13751
13899
  function templateDeviceType(spec) {
13900
+ if (spec.kind === "appliance") return require_composition.applianceDeviceType(spec.appliance);
13752
13901
  return groupTemplate(spec.templateId)?.memberDeviceType ?? null;
13753
13902
  }
13754
13903
  //#endregion
13755
13904
  //#region src/composition/helpers/save.ts
13905
+ /**
13906
+ * Save-time checks a pure compile cannot make: the compiled features must be
13907
+ * this spec's, and members that report a quantity must share one (D944 §4).
13908
+ */
13756
13909
  function quantityProblems(spec, facts) {
13757
13910
  const template = groupTemplate(spec.templateId);
13758
13911
  if (template === null || template.family !== "numeric") return [];
@@ -13787,8 +13940,17 @@ function helperSaveProblems(composition, facts) {
13787
13940
  const compiled = compileHelper(spec);
13788
13941
  if (!compiled.ok) return compiled.problems;
13789
13942
  if (!sameHelperBody(composition, compiled.composition)) return [problem("helper-mismatch", "helper", "the stored features are not this template — the helper spec is the only writer")];
13943
+ if (spec.kind === "appliance") return applianceMemberProblems(spec.member, facts);
13790
13944
  return [...quantityProblems(spec, facts), ...deviceTypeProblems(spec, facts)];
13791
13945
  }
13946
+ function applianceMemberProblems(member, facts) {
13947
+ const fact = facts.get(require_composition.compositionSourceKey(member.source));
13948
+ if (fact === void 0) return [];
13949
+ const problems = [];
13950
+ if (fact.deviceType !== void 0 && fact.deviceType !== require_err_msg.DeviceType.Sensor && fact.deviceType !== require_err_msg.DeviceType.Switch) problems.push(problem("member-constraint", "helper.member", `an appliance helper reads a sensor or a switch; this member is a ${fact.deviceType}`));
13951
+ if (fact.quantity !== void 0 && fact.quantity !== "power") problems.push(problem("mixed-quantity", "helper.member", `an appliance helper reads power (W); this member is ${fact.quantity}`));
13952
+ return problems;
13953
+ }
13792
13954
  //#endregion
13793
13955
  //#region src/composition/composition-graph.ts
13794
13956
  /**
@@ -14002,7 +14164,7 @@ function composableCap(feature, at, lookupCap) {
14002
14164
  */
14003
14165
  function actuatorCommandCaps(composition) {
14004
14166
  const spec = composition.helper;
14005
- if (spec === void 0) return /* @__PURE__ */ new Set();
14167
+ if (spec === void 0 || spec.kind !== "group") return /* @__PURE__ */ new Set();
14006
14168
  const template = groupTemplate(spec.templateId);
14007
14169
  if (template === null || template.family !== "actuator") return /* @__PURE__ */ new Set();
14008
14170
  return new Set(template.commandCaps);
@@ -70385,7 +70547,12 @@ var powerMeterCapability = {
70385
70547
  scope: "device",
70386
70548
  deviceNative: true,
70387
70549
  mode: "singleton",
70388
- deviceTypes: [require_err_msg.DeviceType.Sensor],
70550
+ deviceTypes: [
70551
+ require_err_msg.DeviceType.Sensor,
70552
+ require_err_msg.DeviceType.Washer,
70553
+ require_err_msg.DeviceType.Dryer,
70554
+ require_err_msg.DeviceType.Dishwasher
70555
+ ],
70389
70556
  methods: {},
70390
70557
  status: {
70391
70558
  schema: PowerMeterStatusSchema,
@@ -71293,6 +71460,8 @@ var runningCustomize = {
71293
71460
  stateField: "running"
71294
71461
  },
71295
71462
  deviceTypes: [
71463
+ require_err_msg.DeviceType.Washer,
71464
+ require_err_msg.DeviceType.Dryer,
71296
71465
  require_err_msg.DeviceType.Dishwasher,
71297
71466
  require_err_msg.DeviceType.Vacuum,
71298
71467
  require_err_msg.DeviceType.LawnMower,
@@ -79138,7 +79307,9 @@ var CAP_CUSTOMIZE_CATALOG = {
79138
79307
  require_err_msg.DeviceType.Container,
79139
79308
  require_err_msg.DeviceType.Image,
79140
79309
  require_err_msg.DeviceType.PetFeeder,
79141
- require_err_msg.DeviceType.Dishwasher
79310
+ require_err_msg.DeviceType.Dishwasher,
79311
+ require_err_msg.DeviceType.Washer,
79312
+ require_err_msg.DeviceType.Dryer
79142
79313
  ],
79143
79314
  apply: "add",
79144
79315
  vocabularyCap: "consumables",
@@ -79312,6 +79483,8 @@ var CAP_CUSTOMIZE_CATALOG = {
79312
79483
  id: "running",
79313
79484
  cardinality: "scalar",
79314
79485
  deviceTypes: [
79486
+ require_err_msg.DeviceType.Washer,
79487
+ require_err_msg.DeviceType.Dryer,
79315
79488
  require_err_msg.DeviceType.Dishwasher,
79316
79489
  require_err_msg.DeviceType.Vacuum,
79317
79490
  require_err_msg.DeviceType.LawnMower,
@@ -79430,102 +79603,6 @@ function isLiteral(source, expr) {
79430
79603
  return source?.kind === "expression" && source.expr === expr && Object.keys(source.bindings).length === 0;
79431
79604
  }
79432
79605
  //#endregion
79433
- //#region src/composition/customize/scalar-expr.ts
79434
- var UNSIGNED = /^\d+(?:\.\d+)?$/;
79435
- var SIGNED = /^-?\d+(?:\.\d+)?$/;
79436
- var DWELL = /^dwell\(x (>=|<=) (\d+(?:\.\d+)?), (\d+(?:\.\d+)?), (\d+(?:\.\d+)?)\)$/;
79437
- var LINEAR_CLAMP = /^clamp\((x|number\(x\)) \* (-?\d+(?:\.\d+)?) \+ (-?\d+(?:\.\d+)?), (-?\d+(?:\.\d+)?), (-?\d+(?:\.\d+)?)\)$/;
79438
- var LINEAR_BARE = /^(x|number\(x\)) \* (-?\d+(?:\.\d+)?) \+ (-?\d+(?:\.\d+)?)$/;
79439
- var IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
79440
- function unsignedToken(value) {
79441
- return token(value, UNSIGNED);
79442
- }
79443
- function signedToken(value) {
79444
- return token(value, SIGNED);
79445
- }
79446
- function constantExpr(value) {
79447
- if (typeof value === "string") return JSON.stringify(value);
79448
- if (typeof value === "number") return signedToken(value);
79449
- if (typeof value === "boolean") return value ? "true" : "false";
79450
- return "null";
79451
- }
79452
- function isIdentifier(path) {
79453
- return IDENT.test(path);
79454
- }
79455
- function dwellExpr(comparison, threshold, start, stop) {
79456
- return `dwell(x ${comparison === "gte" ? ">=" : "<="} ${threshold}, ${start}, ${stop})`;
79457
- }
79458
- function linearExpr(inner, scale, offset, clampMin, clampMax) {
79459
- const arithmetic = `${inner} * ${scale} + ${offset}`;
79460
- if (clampMin === null || clampMax === null) return arithmetic;
79461
- return `clamp(${arithmetic}, ${clampMin}, ${clampMax})`;
79462
- }
79463
- function parseDwell(expr) {
79464
- const match = DWELL.exec(expr);
79465
- if (match === null) return null;
79466
- const threshold = Number(match[2]);
79467
- const startDwellMs = Number(match[3]);
79468
- const stopDwellMs = Number(match[4]);
79469
- if (!Number.isFinite(threshold) || !Number.isFinite(startDwellMs) || !Number.isFinite(stopDwellMs)) return null;
79470
- return {
79471
- comparison: match[1] === "<=" ? "lte" : "gte",
79472
- threshold,
79473
- startDwellMs,
79474
- stopDwellMs
79475
- };
79476
- }
79477
- function parseLinear(expr) {
79478
- const clamped = LINEAR_CLAMP.exec(expr);
79479
- if (clamped !== null) return linearFrom(clamped[1], clamped[2], clamped[3], clamped[4], clamped[5]);
79480
- const bare = LINEAR_BARE.exec(expr);
79481
- if (bare === null) return null;
79482
- return linearFrom(bare[1], bare[2], bare[3], void 0, void 0);
79483
- }
79484
- function singleFrom(source) {
79485
- if (source.kind === "from") return {
79486
- cap: source.cap,
79487
- fieldPath: source.fieldPath,
79488
- addonId: source.source.addonId,
79489
- stableId: source.source.stableId
79490
- };
79491
- if (source.kind !== "expression" || Object.keys(source.bindings).length !== 1) return null;
79492
- const bound = source.bindings["x"];
79493
- if (bound === void 0 || bound.kind !== "from") return null;
79494
- return {
79495
- cap: bound.cap,
79496
- fieldPath: bound.fieldPath,
79497
- addonId: bound.source.addonId,
79498
- stableId: bound.source.stableId
79499
- };
79500
- }
79501
- function token(value, pattern) {
79502
- if (!Number.isFinite(value)) return null;
79503
- const text = Object.is(value, -0) ? "0" : String(value);
79504
- return pattern.test(text) ? text : null;
79505
- }
79506
- function linearFrom(inner, scale, offset, clampMin, clampMax) {
79507
- if (inner !== "x" && inner !== "number(x)") return null;
79508
- if (scale === void 0 || offset === void 0) return null;
79509
- const parsedScale = Number(scale);
79510
- const parsedOffset = Number(offset);
79511
- if (!Number.isFinite(parsedScale) || !Number.isFinite(parsedOffset)) return null;
79512
- if (clampMin === void 0 || clampMax === void 0) return {
79513
- inner,
79514
- scale: parsedScale,
79515
- offset: parsedOffset
79516
- };
79517
- const parsedMin = Number(clampMin);
79518
- const parsedMax = Number(clampMax);
79519
- if (!Number.isFinite(parsedMin) || !Number.isFinite(parsedMax)) return null;
79520
- return {
79521
- inner,
79522
- scale: parsedScale,
79523
- offset: parsedOffset,
79524
- clampMin: parsedMin,
79525
- clampMax: parsedMax
79526
- };
79527
- }
79528
- //#endregion
79529
79606
  //#region src/composition/customize/event-compile.ts
79530
79607
  var catalog$4 = CAP_CUSTOMIZE_CATALOG;
79531
79608
  function compilePulseOverride(input) {
@@ -82857,6 +82934,8 @@ var CONTAINER_CHILD_PRIORITY = [
82857
82934
  "fan",
82858
82935
  "vacuum",
82859
82936
  "lawn-mower",
82937
+ "washer",
82938
+ "dryer",
82860
82939
  "dishwasher",
82861
82940
  "light",
82862
82941
  "switch",
@@ -83471,7 +83550,9 @@ var DEVICE_TYPE_CONTROL_KIND = {
83471
83550
  [require_err_msg.DeviceType.Container]: "dummy",
83472
83551
  [require_err_msg.DeviceType.Image]: "image",
83473
83552
  [require_err_msg.DeviceType.PetFeeder]: "pet-feeder",
83474
- [require_err_msg.DeviceType.Dishwasher]: "dishwasher"
83553
+ [require_err_msg.DeviceType.Dishwasher]: "dishwasher",
83554
+ [require_err_msg.DeviceType.Washer]: "dummy",
83555
+ [require_err_msg.DeviceType.Dryer]: "dummy"
83475
83556
  };
83476
83557
  var DEVICE_TYPE_VALUES = new Set(Object.values(require_err_msg.DeviceType));
83477
83558
  /**
@@ -96244,7 +96325,39 @@ var APPLIANCE_CAP_VOCABULARY = {
96244
96325
  phrase: t("When {device} finishes", "Quando {device} finisce"),
96245
96326
  icon: "square"
96246
96327
  }
96247
- }
96328
+ },
96329
+ overrides: { byType: {
96330
+ [require_err_msg.DeviceType.Washer]: {
96331
+ state: { running: {
96332
+ whenTrue: t("When the washing machine starts", "Quando la lavatrice inizia"),
96333
+ whenFalse: t("When the washing machine has finished", "Quando la lavatrice ha finito")
96334
+ } },
96335
+ events: {
96336
+ onStarted: { phrase: t("When the washing machine starts", "Quando la lavatrice inizia") },
96337
+ onFinished: { phrase: t("When the washing machine has finished", "Quando la lavatrice ha finito") }
96338
+ }
96339
+ },
96340
+ [require_err_msg.DeviceType.Dryer]: {
96341
+ state: { running: {
96342
+ whenTrue: t("When the dryer starts", "Quando l'asciugatrice inizia"),
96343
+ whenFalse: t("When the dryer has finished", "Quando l'asciugatrice ha finito")
96344
+ } },
96345
+ events: {
96346
+ onStarted: { phrase: t("When the dryer starts", "Quando l'asciugatrice inizia") },
96347
+ onFinished: { phrase: t("When the dryer has finished", "Quando l'asciugatrice ha finito") }
96348
+ }
96349
+ },
96350
+ [require_err_msg.DeviceType.Dishwasher]: {
96351
+ state: { running: {
96352
+ whenTrue: t("When the dishwasher starts", "Quando la lavastoviglie inizia"),
96353
+ whenFalse: t("When the dishwasher has finished", "Quando la lavastoviglie ha finito")
96354
+ } },
96355
+ events: {
96356
+ onStarted: { phrase: t("When the dishwasher starts", "Quando la lavastoviglie inizia") },
96357
+ onFinished: { phrase: t("When the dishwasher has finished", "Quando la lavastoviglie ha finito") }
96358
+ }
96359
+ }
96360
+ } }
96248
96361
  },
96249
96362
  update: {
96250
96363
  scope: "device",
@@ -97669,7 +97782,9 @@ var DEVICE_TYPE_DO_CAPS = {
97669
97782
  [require_err_msg.DeviceType.Container]: [],
97670
97783
  [require_err_msg.DeviceType.Image]: [],
97671
97784
  [require_err_msg.DeviceType.PetFeeder]: ["pet-feeder"],
97672
- [require_err_msg.DeviceType.Dishwasher]: ["dishwasher"]
97785
+ [require_err_msg.DeviceType.Dishwasher]: ["dishwasher"],
97786
+ [require_err_msg.DeviceType.Washer]: [],
97787
+ [require_err_msg.DeviceType.Dryer]: []
97673
97788
  };
97674
97789
  /**
97675
97790
  * Whether a device type belongs in a Do / automation action device picker.
@@ -97866,6 +97981,12 @@ function stateWording(wordings, field) {
97866
97981
  ...w.state?.[field]
97867
97982
  }), {});
97868
97983
  }
97984
+ function eventWording(wordings, name) {
97985
+ return wordings.reduce((acc, w) => ({
97986
+ ...acc,
97987
+ ...w.events?.[name]
97988
+ }), {});
97989
+ }
97869
97990
  function accessOf(cap, method) {
97870
97991
  return METHOD_ACCESS_MAP[`${kebabToCamel(cap)}.${method}`]?.access;
97871
97992
  }
@@ -97976,18 +98097,19 @@ function resolveState(cap, trigger, wordings) {
97976
98097
  offer: trigger.offer ?? null
97977
98098
  };
97978
98099
  }
97979
- function eventTriggersOf(cap, entry) {
98100
+ function eventTriggersOf(cap, entry, wordings) {
97980
98101
  const out = [];
97981
- for (const ev of Object.values(entry.events ?? {})) {
98102
+ for (const [name, ev] of Object.entries(entry.events ?? {})) {
97982
98103
  if ("notOperatorFacing" in ev) continue;
98104
+ const wording = eventWording(wordings, name);
97983
98105
  out.push({
97984
98106
  kind: "event",
97985
98107
  id: `${cap}.event.${ev.category}`,
97986
98108
  cap,
97987
98109
  category: ev.category,
97988
- label: ev.label,
97989
- phrase: ev.phrase,
97990
- icon: ev.icon
98110
+ label: wording.label ?? ev.label,
98111
+ phrase: wording.phrase ?? ev.phrase,
98112
+ icon: wording.icon ?? ev.icon
97991
98113
  });
97992
98114
  }
97993
98115
  for (const ev of entry.extraEvents ?? []) out.push({
@@ -98011,7 +98133,7 @@ function triggersForDevice(bindings, deviceType, role) {
98011
98133
  for (const [cap, entry] of boundEntries(bindings)) {
98012
98134
  const wordings = wordingsFor(entry, deviceType, role);
98013
98135
  for (const trigger of entry.state ?? []) state.push(resolveState(cap, trigger, wordings));
98014
- events.push(...eventTriggersOf(cap, entry));
98136
+ events.push(...eventTriggersOf(cap, entry, wordings));
98015
98137
  }
98016
98138
  return [...state, ...events];
98017
98139
  }
@@ -98064,6 +98186,72 @@ function resolveDeviceProfile(features) {
98064
98186
  return null;
98065
98187
  }
98066
98188
  //#endregion
98189
+ //#region src/device/appliance-export.ts
98190
+ /**
98191
+ * How a washer, dryer or dishwasher leaves CamStack.
98192
+ *
98193
+ * Home Assistant has no laundry domain: the device is offered, and what
98194
+ * travels is its power reading. The others have a laundry type that this
98195
+ * export cannot honour yet, so they are named and withheld.
98196
+ */
98197
+ var PLUG_APPLIANCE_TYPES = [
98198
+ require_err_msg.DeviceType.Washer,
98199
+ require_err_msg.DeviceType.Dryer,
98200
+ require_err_msg.DeviceType.Dishwasher
98201
+ ];
98202
+ var HA_AS = "a power sensor (device_class power). Home Assistant has no laundry domain. running-state stays unexported until a null reading can be published as unavailable.";
98203
+ var HOMEKIT_REASON = "HomeKit has no laundry-appliance service. The export tab would pair an accessory with nothing on it: running-state is withheld until a null reading can be unavailable, and power is not a HomeKit service this export honours.";
98204
+ var ALEXA_REASON = "Alexa has no laundry category in this export, and neither power nor running has an Alexa interface.";
98205
+ var GOOGLE_REASON = "Google WASHER, DRYER and DISHWASHER require RunCycle. A power threshold is not that trait, and this export does not publish RunCycle.";
98206
+ var MATTER_REASON = "No Matter export is built. The closest device types are Laundry Washer (0x0073) and Laundry Dryer (0x0074); a power threshold is not the Laundry Washer Mode cluster.";
98207
+ function withheld(reason) {
98208
+ return {
98209
+ [require_err_msg.DeviceType.Washer]: {
98210
+ exported: false,
98211
+ reason
98212
+ },
98213
+ [require_err_msg.DeviceType.Dryer]: {
98214
+ exported: false,
98215
+ reason
98216
+ },
98217
+ [require_err_msg.DeviceType.Dishwasher]: {
98218
+ exported: false,
98219
+ reason
98220
+ }
98221
+ };
98222
+ }
98223
+ function offered(received) {
98224
+ return {
98225
+ [require_err_msg.DeviceType.Washer]: {
98226
+ exported: true,
98227
+ as: received
98228
+ },
98229
+ [require_err_msg.DeviceType.Dryer]: {
98230
+ exported: true,
98231
+ as: received
98232
+ },
98233
+ [require_err_msg.DeviceType.Dishwasher]: {
98234
+ exported: true,
98235
+ as: received
98236
+ }
98237
+ };
98238
+ }
98239
+ var APPLIANCE_EXPORT = {
98240
+ ha: offered(HA_AS),
98241
+ homekit: withheld(HOMEKIT_REASON),
98242
+ alexa: withheld(ALEXA_REASON),
98243
+ google: withheld(GOOGLE_REASON),
98244
+ matter: withheld(MATTER_REASON)
98245
+ };
98246
+ function isPlugApplianceType(deviceType) {
98247
+ return PLUG_APPLIANCE_TYPES.some((type) => type === deviceType);
98248
+ }
98249
+ /** The decision for this ecosystem, or null when the type is not an appliance. */
98250
+ function applianceExport(ecosystem, deviceType) {
98251
+ if (!isPlugApplianceType(deviceType)) return null;
98252
+ return APPLIANCE_EXPORT[ecosystem][deviceType];
98253
+ }
98254
+ //#endregion
98067
98255
  //#region src/device/path-util.ts
98068
98256
  /** Returns true when `x` is a non-null, non-array plain object. */
98069
98257
  function isRecord$1(x) {
@@ -115501,6 +115689,10 @@ exports.APPEARANCE_BLOCKS = APPEARANCE_BLOCKS;
115501
115689
  exports.APPEARANCE_DESCRIPTOR_VERSION = APPEARANCE_DESCRIPTOR_VERSION;
115502
115690
  exports.APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT = APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT;
115503
115691
  exports.APPLE_SA_TO_MACRO = APPLE_SA_TO_MACRO;
115692
+ exports.APPLIANCE_DEFAULTS = require_composition.APPLIANCE_DEFAULTS;
115693
+ exports.APPLIANCE_EXPORT = APPLIANCE_EXPORT;
115694
+ exports.APPLIANCE_KINDS = require_composition.APPLIANCE_KINDS;
115695
+ exports.APPLIANCE_TEMPLATE_ID = require_composition.APPLIANCE_TEMPLATE_ID;
115504
115696
  exports.APP_AUTH_CALLBACK_MAX_LENGTH = APP_AUTH_CALLBACK_MAX_LENGTH;
115505
115697
  exports.APP_AUTH_CALLBACK_URLS = APP_AUTH_CALLBACK_URLS;
115506
115698
  exports.APP_FLOW_DEFAULT_REDIRECT = APP_FLOW_DEFAULT_REDIRECT;
@@ -115564,6 +115756,7 @@ exports.AppLoginPathSchema = AppLoginPathSchema;
115564
115756
  exports.AppPageLangSchema = AppPageLangSchema;
115565
115757
  exports.AppleFmAvailabilitySchema = AppleFmAvailabilitySchema;
115566
115758
  exports.AppleFmUnavailableReasonSchema = AppleFmUnavailableReasonSchema;
115759
+ exports.ApplianceHelperSpecSchema = require_composition.ApplianceHelperSpecSchema;
115567
115760
  exports.ArchiveEntrySchema = ArchiveEntrySchema;
115568
115761
  exports.ArchiveManifestSchema = ArchiveManifestSchema;
115569
115762
  exports.ArchiveSqliteSnapshotSchema = ArchiveSqliteSnapshotSchema;
@@ -116836,6 +117029,7 @@ exports.PIPELINE_ANALYTICS_CAP_NAME = PIPELINE_ANALYTICS_CAP_NAME;
116836
117029
  exports.PIPELINE_FLOW_CAPABILITY_NAMES = PIPELINE_FLOW_CAPABILITY_NAMES;
116837
117030
  exports.PIPELINE_OWNER_CAPABILITY_NAMES = PIPELINE_OWNER_CAPABILITY_NAMES;
116838
117031
  exports.PIPELINE_RAMP = PIPELINE_RAMP;
117032
+ exports.PLUG_APPLIANCE_TYPES = PLUG_APPLIANCE_TYPES;
116839
117033
  exports.PLUS_CATALOG_CLASS_FLOORS = PLUS_CATALOG_CLASS_FLOORS;
116840
117034
  exports.PLUS_CATALOG_STOCK_CLASS_FLOORS = PLUS_CATALOG_STOCK_CLASS_FLOORS;
116841
117035
  exports.PLUS_CATALOG_TO_MACRO = PLUS_CATALOG_TO_MACRO;
@@ -117538,6 +117732,8 @@ exports.allowedSuppliedEffect = allowedSuppliedEffect;
117538
117732
  exports.ambientLightSensorCapability = ambientLightSensorCapability;
117539
117733
  exports.annotateClipsWithEventLabels = annotateClipsWithEventLabels;
117540
117734
  exports.appFlowLocation = appFlowLocation;
117735
+ exports.applianceDeviceType = require_composition.applianceDeviceType;
117736
+ exports.applianceExport = applianceExport;
117541
117737
  exports.applyBlockingEdit = applyBlockingEdit;
117542
117738
  exports.applyCropClassFloors = applyCropClassFloors;
117543
117739
  exports.applyModeDelaysEdits = applyModeDelaysEdits;
@@ -117909,6 +118105,7 @@ exports.hasModelBoundOverrides = hasModelBoundOverrides;
117909
118105
  exports.hasMotionTrigger = hasMotionTrigger;
117910
118106
  exports.hasUrlScheme = hasUrlScheme;
117911
118107
  exports.heatingValveCapability = heatingValveCapability;
118108
+ exports.helperMembers = require_composition.helperMembers;
117912
118109
  exports.helperSaveProblems = helperSaveProblems;
117913
118110
  exports.hfModelUrl = require_sleep.hfModelUrl;
117914
118111
  exports.historyFieldsFor = historyFieldsFor;
@@ -117943,6 +118140,7 @@ exports.isAlertEpisodeOpen = isAlertEpisodeOpen;
117943
118140
  exports.isAlertEpisodeSeverity = isAlertEpisodeSeverity;
117944
118141
  exports.isAppLinkBase = isAppLinkBase;
117945
118142
  exports.isAppStartRefusal = isAppStartRefusal;
118143
+ exports.isApplianceKind = require_composition.isApplianceKind;
117946
118144
  exports.isArrayOutputSchema = isArrayOutputSchema;
117947
118145
  exports.isAudioChunkFormat = require_sleep.isAudioChunkFormat;
117948
118146
  exports.isAudioClassName = isAudioClassName;
@@ -117983,6 +118181,7 @@ exports.isOauthLandingReason = isOauthLandingReason;
117983
118181
  exports.isObjectInput = isObjectInput;
117984
118182
  exports.isOccupancyRule = isOccupancyRule;
117985
118183
  exports.isOwnerOnlyEventCategory = require_event_category.isOwnerOnlyEventCategory;
118184
+ exports.isPlugApplianceType = isPlugApplianceType;
117986
118185
  exports.isReadingQuantity = require_sleep.isReadingQuantity;
117987
118186
  exports.isRemovedModelId = isRemovedModelId;
117988
118187
  exports.isRestoredCap = isRestoredCap;
@@ -118392,6 +118591,7 @@ exports.sliceChangedAt = require_composition.sliceChangedAt;
118392
118591
  exports.smokeCapability = smokeCapability;
118393
118592
  exports.smtpProviderCapability = smtpProviderCapability;
118394
118593
  exports.snapshotCapability = snapshotCapability;
118594
+ exports.sourcePickForAppliance = require_composition.sourcePickForAppliance;
118395
118595
  exports.sourcePickForTemplate = sourcePickForTemplate;
118396
118596
  exports.splitTranslationRef = splitTranslationRef;
118397
118597
  exports.squareAround = squareAround;