@camstack/addon-provider-dreame 0.2.153 → 0.2.154

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 (3) hide show
  1. package/dist/addon.js +442 -64
  2. package/dist/addon.mjs +442 -64
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -8874,7 +8874,7 @@ function errMsg(err) {
8874
8874
  return String(err);
8875
8875
  }
8876
8876
  //#endregion
8877
- //#region ../types/dist/composition-xz-ud5EQ.mjs
8877
+ //#region ../types/dist/composition-BJ8iAZb3.mjs
8878
8878
  /**
8879
8879
  * The ffmpeg build every CamStack node downloads, per platform and arch.
8880
8880
  *
@@ -8934,7 +8934,7 @@ portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac
8934
8934
  * ONE table for everyone who asks "is this device HIGH?": the
8935
8935
  * recorder's `RecordingTriggers.sensorDeviceIds` trigger, the post-analysis
8936
8936
  * sensor markers, the orchestrator's camera-sensors bridge and
8937
- * the sources of a composite doorbell (`doorbellFeatureSources`, D671).
8937
+ * the sources of a pulse (`compilePulseOverride`, D936).
8938
8938
  * A second copy would diverge at the first cap added, and the symptom — "the
8939
8939
  * sensor rings the doorbell but does not record" — is exactly D62.
8940
8940
  *
@@ -54401,20 +54401,68 @@ var BatteryStatusSchema = object({
54401
54401
  */
54402
54402
  binary: boolean().optional()
54403
54403
  });
54404
+ var BATTERY_DEVICE_TYPES = [
54405
+ DeviceType.Camera,
54406
+ DeviceType.Sensor,
54407
+ DeviceType.Button,
54408
+ DeviceType.Switch,
54409
+ DeviceType.PetFeeder,
54410
+ DeviceType.Container,
54411
+ DeviceType.Thermostat
54412
+ ];
54413
+ /** Generic Customize target for the percentage reading (D936). */
54414
+ var batteryCustomize = {
54415
+ version: 1,
54416
+ targets: [{
54417
+ id: "percentage",
54418
+ cardinality: "scalar",
54419
+ vocabulary: {
54420
+ cap: "battery",
54421
+ stateField: "percentage"
54422
+ },
54423
+ deviceTypes: BATTERY_DEVICE_TYPES,
54424
+ apply: "add-or-replace",
54425
+ path: "percentage",
54426
+ sources: [{
54427
+ kind: "field",
54428
+ valueKinds: ["number", "string"],
54429
+ transforms: [{ kind: "identity" }, {
54430
+ kind: "linear",
54431
+ defaultScale: 1,
54432
+ defaultOffset: 0,
54433
+ clampMin: 0,
54434
+ clampMax: 100
54435
+ }]
54436
+ }],
54437
+ companions: [
54438
+ {
54439
+ kind: "constant",
54440
+ path: "charging",
54441
+ value: "unknown",
54442
+ when: "add"
54443
+ },
54444
+ {
54445
+ kind: "constant",
54446
+ path: "sleeping",
54447
+ value: false,
54448
+ when: "add"
54449
+ },
54450
+ {
54451
+ kind: "clock",
54452
+ path: "lastUpdated",
54453
+ when: "add"
54454
+ }
54455
+ ],
54456
+ sourceReport: { status: "assignment-status" },
54457
+ advancedExpression: true
54458
+ }]
54459
+ };
54404
54460
  var batteryCapability = {
54405
54461
  name: "battery",
54406
54462
  scope: "device",
54407
54463
  deviceNative: true,
54408
54464
  mode: "singleton",
54409
- deviceTypes: [
54410
- DeviceType.Camera,
54411
- DeviceType.Sensor,
54412
- DeviceType.Button,
54413
- DeviceType.Switch,
54414
- DeviceType.PetFeeder,
54415
- DeviceType.Container,
54416
- DeviceType.Thermostat
54417
- ],
54465
+ deviceTypes: BATTERY_DEVICE_TYPES,
54418
54466
  methods: {
54419
54467
  /**
54420
54468
  * Explicitly wake the camera from low-power sleep ahead of a
@@ -54498,6 +54546,7 @@ onStatusChanged: { data: object({
54498
54546
  kind: "gauge",
54499
54547
  unit: "%"
54500
54548
  } } },
54549
+ customize: batteryCustomize,
54501
54550
  /** Clock fields: written, but excluded from the compare that decides
54502
54551
  * whether persisting is worth a SQLite commit. */
54503
54552
  volatileStateFields: ["lastUpdated"]
@@ -55487,6 +55536,106 @@ var ConsumablesStatusSchema = object({
55487
55536
  items: array(ConsumableItemSchema),
55488
55537
  lastChangedAt: number()
55489
55538
  });
55539
+ /** Synthesize seed for a link-built status (no native provider).
55540
+ *
55541
+ * ⚠ NO LIVE CALLER since 2026-08-08 (device-links deleted; see
55542
+ * `CapabilityStatusSchema.empty`). The wiring guidance below was written for
55543
+ * exactly the case the operator is asking about again — a Dreame's HA
55544
+ * consumable-life sensors — so it is kept for that conversation, not because
55545
+ * anything reads it today. */
55546
+ var EMPTY_STATUS = {
55547
+ items: [],
55548
+ lastChangedAt: 0
55549
+ };
55550
+ /** Seed for a NEW link-built consumable item. `key`/`label` are overwritten
55551
+ * with the link's `itemKey` (see `status.itemArray`), so the placeholder
55552
+ * empty strings never survive into a validated item. Link-built items are
55553
+ * not resettable — there is no native provider to receive `reset()`.
55554
+ *
55555
+ * Wiring guidance (HA vacuum consumable-life sensors):
55556
+ * - a percent-remaining source (`numeric-sensor`, unit '%') → `level` via
55557
+ * `identity` (or `linear` scale 1 offset 0 clamp [0,100] to be safe);
55558
+ * - an hours-remaining source (e.g. Dreame 'Main Brush Time Left', unit 'h')
55559
+ * → `level` via `linear` with scale = 100 / max-hours for that item
55560
+ * (e.g. main brush 300 h → scale 0.3333) + clamp [0,100];
55561
+ * - a discrete source (`enum-sensor`, e.g. mop pad 'installed') → `status`
55562
+ * via `enum-map` (e.g. { installed: 'ok', missing: 'replace' }).
55563
+ * A numeric→'replace' THRESHOLD (level < 10 → 'replace') is NOT expressible
55564
+ * with the current transform kinds — leave `status` null (valid) until the
55565
+ * expression transform lands. */
55566
+ /** Generic Customize target for one consumable item (D936). Native rows stay read-only. */
55567
+ var consumablesCustomize = {
55568
+ version: 1,
55569
+ targets: [{
55570
+ id: "items",
55571
+ cardinality: "keyedArray",
55572
+ vocabulary: { cap: "consumables" },
55573
+ deviceTypes: Object.values(DeviceType),
55574
+ apply: "add",
55575
+ path: "items",
55576
+ keyField: "key",
55577
+ labelField: "label",
55578
+ itemSchema: ConsumableItemSchema,
55579
+ nativeItems: "read-only",
55580
+ sourceReport: { status: "assignment-status" },
55581
+ fields: [
55582
+ {
55583
+ path: "level",
55584
+ sources: [{
55585
+ kind: "numeric",
55586
+ transforms: [{ kind: "identity" }, {
55587
+ kind: "linear",
55588
+ defaultScale: 1,
55589
+ defaultOffset: 0,
55590
+ clampMin: 0,
55591
+ clampMax: 100
55592
+ }]
55593
+ }]
55594
+ },
55595
+ {
55596
+ path: "remaining.value",
55597
+ sources: [{
55598
+ kind: "numeric",
55599
+ transforms: [{ kind: "identity" }]
55600
+ }],
55601
+ literals: [{
55602
+ path: "remaining.unit",
55603
+ values: [...ConsumableRemainingUnitSchema.options]
55604
+ }]
55605
+ },
55606
+ {
55607
+ path: "status",
55608
+ sources: [{
55609
+ kind: "enum",
55610
+ transforms: [{
55611
+ kind: "enum-map",
55612
+ outputValues: [...ConsumableStatusSchema.options],
55613
+ unmapped: null
55614
+ }]
55615
+ }]
55616
+ }
55617
+ ],
55618
+ traits: [{
55619
+ path: "kind",
55620
+ values: [...ConsumableKindSchema.options],
55621
+ defaultValue: "wear"
55622
+ }, {
55623
+ path: "form",
55624
+ values: [...ConsumableFormSchema.options],
55625
+ defaultValue: "part"
55626
+ }],
55627
+ companions: [{
55628
+ kind: "clock",
55629
+ path: "lastChangedAt",
55630
+ when: "add"
55631
+ }, {
55632
+ kind: "clock",
55633
+ path: "lastFetchedAt",
55634
+ when: "add"
55635
+ }],
55636
+ advancedExpression: true
55637
+ }]
55638
+ };
55490
55639
  var consumablesCapability = {
55491
55640
  name: "consumables",
55492
55641
  scope: "device",
@@ -55524,10 +55673,7 @@ reset: method(object({
55524
55673
  status: {
55525
55674
  schema: ConsumablesStatusSchema,
55526
55675
  kind: "push",
55527
- empty: {
55528
- items: [],
55529
- lastChangedAt: 0
55530
- },
55676
+ empty: EMPTY_STATUS,
55531
55677
  itemArray: {
55532
55678
  path: "items",
55533
55679
  keyField: "key",
@@ -55563,6 +55709,7 @@ reset: method(object({
55563
55709
  * brush's remaining life and a dishwasher's salt are each recorded the moment
55564
55710
  * their provider reports them; an item that appears later gets its series.
55565
55711
  */
55712
+ customize: consumablesCustomize,
55566
55713
  history: {
55567
55714
  fields: {},
55568
55715
  items: {
@@ -56305,6 +56452,36 @@ var dishwasherCapability = {
56305
56452
  powerOn: { kind: "boolean" }
56306
56453
  } }
56307
56454
  };
56455
+ /** Pulse override: a rising edge advances `lastPressedAt` and the press count. */
56456
+ var doorbellCustomize = {
56457
+ version: 1,
56458
+ targets: [{
56459
+ id: "pressed",
56460
+ cardinality: "event",
56461
+ shape: "pulse",
56462
+ vocabulary: {
56463
+ cap: "doorbell",
56464
+ eventName: "onPressed"
56465
+ },
56466
+ deviceTypes: [DeviceType.Camera],
56467
+ apply: "add",
56468
+ applyAuthority: { kind: "composition-claim" },
56469
+ eventName: "onPressed",
56470
+ edges: ["rising"],
56471
+ timestampPath: "lastPressedAt",
56472
+ counterPath: "pressCountSinceStart",
56473
+ highWater: "composer-advance",
56474
+ combine: "any",
56475
+ debounce: {
56476
+ kind: "rising-edge",
56477
+ defaultDebounceMs: 2e3,
56478
+ changedAtRequired: true
56479
+ },
56480
+ sources: [{ kind: "binary" }, { kind: "switch" }],
56481
+ sourceReport: { status: "assignment-status" },
56482
+ advancedExpression: true
56483
+ }]
56484
+ };
56308
56485
  var DoorbellStatusSchema = object({
56309
56486
  /** Ms epoch of the last press. null = never observed since this provider started. */
56310
56487
  lastPressedAt: number().nullable(),
@@ -56371,7 +56548,8 @@ onPressed: {
56371
56548
  /**
56372
56549
  * History (D850): none — `pressCountSinceStart` resets with the addon, so it is not a monotonic meter; presses live in the events ledger.
56373
56550
  */
56374
- history: "none"
56551
+ history: "none",
56552
+ customize: doorbellCustomize
56375
56553
  };
56376
56554
  /**
56377
56555
  * Enum-state sensor — a string value picked from a finite option set.
@@ -58767,6 +58945,38 @@ authKey: string().optional() }), object({
58767
58945
  /** Human-readable error when `ok: false`. */
58768
58946
  error: string().optional()
58769
58947
  }), { kind: "mutation" });
58948
+ /** Span override: start on a rising edge, end on a fall or this timeout. */
58949
+ var motionCustomize = {
58950
+ version: 1,
58951
+ targets: [{
58952
+ id: "detected",
58953
+ cardinality: "event",
58954
+ shape: "span",
58955
+ vocabulary: {
58956
+ cap: "motion",
58957
+ eventName: "onMotionChanged"
58958
+ },
58959
+ deviceTypes: [DeviceType.Camera],
58960
+ apply: "replace",
58961
+ applyAuthority: {
58962
+ kind: "camera-sensors",
58963
+ timeoutField: "motionCooldownMs"
58964
+ },
58965
+ eventName: "onMotionChanged",
58966
+ start: "rising-edge",
58967
+ end: "falling-edge-or-timeout",
58968
+ timeoutMs: 3e4,
58969
+ combine: "any",
58970
+ sourceModes: [
58971
+ "in-addition",
58972
+ "confirm",
58973
+ "replace"
58974
+ ],
58975
+ sources: [{ kind: "binary" }, { kind: "switch" }],
58976
+ sourceReport: { status: "assignment-status" },
58977
+ advancedExpression: false
58978
+ }]
58979
+ };
58770
58980
  var MotionStatusSchema = object({
58771
58981
  detected: boolean(),
58772
58982
  /** Ms epoch of the last detected-true observation. Null if never detected. */
@@ -58906,7 +59116,8 @@ onMotionChanged: { data: MotionOnMotionChangedDataSchema } },
58906
59116
  history: { fields: { detected: {
58907
59117
  kind: "boolean",
58908
59118
  excludeDeviceTypes: [DeviceType.Camera]
58909
- } } }
59119
+ } } },
59120
+ customize: motionCustomize
58910
59121
  };
58911
59122
  /**
58912
59123
  * Motion-trigger toggle for accessory devices.
@@ -61780,6 +61991,16 @@ var ptzAutotrackCapability = {
61780
61991
  */
61781
61992
  history: "none"
61782
61993
  };
61994
+ var RUNNING_POWER_START_DELAY_MS = 3e4;
61995
+ var RUNNING_POWER_STOP_DELAY_MS = 18e4;
61996
+ /** Before any provider reading — all fields unknown (D933). */
61997
+ var RUNNING_STATE_COLD = {
61998
+ running: null,
61999
+ since: null,
62000
+ source: "native",
62001
+ phase: null,
62002
+ lastFetchedAt: null
62003
+ };
61783
62004
  var RunningStateSourceSchema = _enum(["native", "assigned"]);
61784
62005
  var RunningStateStatusSchema = object({
61785
62006
  /** null = unknown — never coerced to false (D393). */
@@ -61788,19 +62009,100 @@ var RunningStateStatusSchema = object({
61788
62009
  since: number().nullable(),
61789
62010
  source: RunningStateSourceSchema,
61790
62011
  phase: string().nullable(),
61791
- lastFetchedAt: number()
62012
+ /** null until a reading has been published (D393). */
62013
+ lastFetchedAt: number().nullable()
61792
62014
  });
61793
62015
  var RunningFlipEventDataSchema = object({
61794
62016
  deviceId: number(),
61795
62017
  since: number().nullable(),
61796
62018
  phase: string().nullable()
61797
62019
  });
62020
+ var RUNNING_DEVICE_TYPES = Object.values(DeviceType);
62021
+ /** Generic Customize target for the running boolean (D936). */
62022
+ var runningCustomize = {
62023
+ version: 1,
62024
+ targets: [{
62025
+ id: "running",
62026
+ cardinality: "scalar",
62027
+ vocabulary: {
62028
+ cap: "running-state",
62029
+ stateField: "running"
62030
+ },
62031
+ deviceTypes: [
62032
+ DeviceType.Dishwasher,
62033
+ DeviceType.Vacuum,
62034
+ DeviceType.LawnMower,
62035
+ DeviceType.MediaPlayer,
62036
+ DeviceType.WaterHeater,
62037
+ DeviceType.Humidifier,
62038
+ DeviceType.Fan,
62039
+ DeviceType.Climate,
62040
+ DeviceType.Thermostat,
62041
+ DeviceType.PetFeeder
62042
+ ],
62043
+ apply: "add-or-replace",
62044
+ path: "running",
62045
+ sources: [
62046
+ {
62047
+ kind: "switch",
62048
+ transforms: [{ kind: "identity" }]
62049
+ },
62050
+ {
62051
+ kind: "binary",
62052
+ transforms: [{ kind: "identity" }, { kind: "invert" }]
62053
+ },
62054
+ {
62055
+ kind: "numeric",
62056
+ requiredUnit: "W",
62057
+ transforms: [{
62058
+ kind: "threshold",
62059
+ comparison: "gte",
62060
+ defaultThreshold: 5,
62061
+ defaultStartDwellMs: RUNNING_POWER_START_DELAY_MS,
62062
+ defaultStopDwellMs: RUNNING_POWER_STOP_DELAY_MS,
62063
+ thresholdUnit: "W"
62064
+ }]
62065
+ }
62066
+ ],
62067
+ companions: [
62068
+ {
62069
+ kind: "constant",
62070
+ path: "source",
62071
+ value: "assigned",
62072
+ when: "always"
62073
+ },
62074
+ {
62075
+ kind: "constant",
62076
+ path: "phase",
62077
+ value: null,
62078
+ when: "always"
62079
+ },
62080
+ {
62081
+ kind: "clock",
62082
+ path: "lastFetchedAt",
62083
+ when: "always"
62084
+ },
62085
+ {
62086
+ kind: "transition-time",
62087
+ path: "since",
62088
+ sourceChangedAt: "prefer"
62089
+ }
62090
+ ],
62091
+ sourceReport: {
62092
+ status: "assignment-status",
62093
+ runtimeField: "source",
62094
+ nativeValue: "native",
62095
+ assignedValue: "assigned"
62096
+ },
62097
+ advancedExpression: true
62098
+ }]
62099
+ };
61798
62100
  var runningStateCapability = {
61799
62101
  name: "running-state",
61800
62102
  scope: "device",
61801
62103
  deviceNative: true,
61802
62104
  mode: "singleton",
61803
- deviceTypes: Object.values(DeviceType),
62105
+ deviceTypes: RUNNING_DEVICE_TYPES,
61804
62106
  methods: {},
61805
62107
  events: {
61806
62108
  onStarted: { data: RunningFlipEventDataSchema },
@@ -61813,7 +62115,8 @@ var runningStateCapability = {
61813
62115
  runtimeState: RunningStateStatusSchema,
61814
62116
  durability: "restored",
61815
62117
  history: { fields: { running: { kind: "boolean" } } },
61816
- volatileStateFields: ["lastFetchedAt"]
62118
+ volatileStateFields: ["lastFetchedAt"],
62119
+ customize: runningCustomize
61817
62120
  };
61818
62121
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
61819
62122
  kind: "mutation",
@@ -66445,31 +66748,35 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
66445
66748
  kind: "mutation",
66446
66749
  auth: "admin"
66447
66750
  });
66448
- function resolveSince(running, previous, sinceMs) {
66751
+ function resolveSince(running, previous, transitionAtMs, bootstrapSinceMs) {
66449
66752
  if (running === null) return null;
66450
- if ((previous?.running ?? null) !== running) return sinceMs;
66451
- return previous?.since ?? sinceMs;
66753
+ const prevRunning = previous?.running ?? null;
66754
+ if (prevRunning === null) return bootstrapSinceMs;
66755
+ if (prevRunning !== running) return transitionAtMs;
66756
+ return previous?.since ?? null;
66452
66757
  }
66453
- function nativeSlice(running, phase, previous, sinceMs, now) {
66758
+ function nativeSlice(running, phase, previous, transitionAtMs, fetchedAt, bootstrapSinceMs) {
66454
66759
  return {
66455
66760
  running,
66456
- since: resolveSince(running, previous, sinceMs),
66761
+ since: resolveSince(running, previous, transitionAtMs, bootstrapSinceMs),
66457
66762
  source: "native",
66458
66763
  phase: running === null ? null : phase,
66459
- lastFetchedAt: now
66764
+ lastFetchedAt: fetchedAt
66460
66765
  };
66461
66766
  }
66462
- function runningFromVacuumState(state, previous, now) {
66463
- if (state === null) return nativeSlice(null, null, previous, now, now);
66464
- if (state === "cleaning" || state === "returning" || state === "drying") return nativeSlice(true, state, previous, now, now);
66465
- if (state === "paused") return nativeSlice(false, "paused", previous, now, now);
66466
- return nativeSlice(false, state, previous, now, now);
66767
+ function runningFromVacuumState(state, previous, fetchedAt, transitionAt) {
66768
+ const transitionAtMs = transitionAt ?? fetchedAt;
66769
+ if (state === null) return nativeSlice(null, null, previous, transitionAtMs, fetchedAt, null);
66770
+ if (state === "cleaning" || state === "returning" || state === "drying") return nativeSlice(true, state, previous, transitionAtMs, fetchedAt, null);
66771
+ if (state === "paused") return nativeSlice(false, "paused", previous, transitionAtMs, fetchedAt, null);
66772
+ return nativeSlice(false, state, previous, transitionAtMs, fetchedAt, null);
66467
66773
  }
66468
- function runningFromLawnMower(activity, previous, now) {
66469
- if (activity === null) return nativeSlice(null, null, previous, now, now);
66470
- if (activity === "mowing") return nativeSlice(true, activity, previous, now, now);
66471
- if (activity === "paused") return nativeSlice(false, "paused", previous, now, now);
66472
- return nativeSlice(false, activity, previous, now, now);
66774
+ function runningFromLawnMower(activity, previous, fetchedAt, transitionAt) {
66775
+ const transitionAtMs = transitionAt ?? fetchedAt;
66776
+ if (activity === null) return nativeSlice(null, null, previous, transitionAtMs, fetchedAt, null);
66777
+ if (activity === "mowing") return nativeSlice(true, activity, previous, transitionAtMs, fetchedAt, null);
66778
+ if (activity === "paused") return nativeSlice(false, "paused", previous, transitionAtMs, fetchedAt, null);
66779
+ return nativeSlice(false, activity, previous, transitionAtMs, fetchedAt, null);
66473
66780
  }
66474
66781
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
66475
66782
  Object.freeze({
@@ -84371,6 +84678,96 @@ Object.freeze({
84371
84678
  "smtp-provider": "email"
84372
84679
  });
84373
84680
  DeviceType.Camera;
84681
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Camera, DeviceType.Camera, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
84682
+ /**
84683
+ * A saved Customize assignment (D936). The schema is the type: callers use
84684
+ * `z.infer`, they do not restate the shape.
84685
+ */
84686
+ var OverrideSourceSchema = object({
84687
+ addonId: string().min(1),
84688
+ stableId: string().min(1),
84689
+ cap: string().min(1),
84690
+ fieldPath: string().min(1)
84691
+ });
84692
+ var CustomizeTransformDraftSchema = discriminatedUnion("kind", [
84693
+ object({ kind: literal("identity") }),
84694
+ object({ kind: literal("invert") }),
84695
+ object({
84696
+ kind: literal("threshold"),
84697
+ comparison: _enum(["gte", "lte"]),
84698
+ threshold: number(),
84699
+ startDwellMs: number().nonnegative(),
84700
+ stopDwellMs: number().nonnegative()
84701
+ }),
84702
+ object({
84703
+ kind: literal("linear"),
84704
+ scale: number(),
84705
+ offset: number(),
84706
+ clampMin: number().optional(),
84707
+ clampMax: number().optional()
84708
+ }),
84709
+ object({
84710
+ kind: literal("enum-map"),
84711
+ mapping: record(string(), string().nullable())
84712
+ }),
84713
+ object({ kind: literal("number") })
84714
+ ]);
84715
+ var ScalarOverrideDraftSchema = object({
84716
+ kind: literal("scalar"),
84717
+ cap: string().min(1),
84718
+ targetId: string().min(1),
84719
+ source: OverrideSourceSchema,
84720
+ transform: CustomizeTransformDraftSchema
84721
+ });
84722
+ var KeyedArrayOverrideDraftSchema = object({
84723
+ kind: literal("keyedArray"),
84724
+ cap: string().min(1),
84725
+ targetId: string().min(1),
84726
+ key: string().min(1),
84727
+ label: string().min(1),
84728
+ fieldPath: string().min(1),
84729
+ source: OverrideSourceSchema,
84730
+ transform: CustomizeTransformDraftSchema
84731
+ });
84732
+ var PulseOverrideDraftSchema = object({
84733
+ kind: literal("pulse"),
84734
+ cap: string().min(1),
84735
+ targetId: string().min(1),
84736
+ enabled: boolean(),
84737
+ debounceMs: number().nonnegative(),
84738
+ sources: array(OverrideSourceSchema).min(1)
84739
+ });
84740
+ var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
84741
+ "in-addition",
84742
+ "confirm",
84743
+ "replace"
84744
+ ]).optional() });
84745
+ discriminatedUnion("kind", [
84746
+ ScalarOverrideDraftSchema,
84747
+ KeyedArrayOverrideDraftSchema,
84748
+ PulseOverrideDraftSchema,
84749
+ object({
84750
+ kind: literal("span"),
84751
+ cap: string().min(1),
84752
+ targetId: string().min(1),
84753
+ timeoutMs: number().nonnegative(),
84754
+ cooldownMs: number().nonnegative().optional(),
84755
+ sources: array(SpanSourceDraftSchema)
84756
+ })
84757
+ ]);
84758
+ object({
84759
+ targetId: string().min(1),
84760
+ configured: boolean(),
84761
+ source: _enum(["native", "assigned"]),
84762
+ state: _enum([
84763
+ "active",
84764
+ "stale",
84765
+ "released",
84766
+ "unavailable"
84767
+ ]),
84768
+ reason: string().nullable()
84769
+ });
84770
+ String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
84374
84771
  var G711_SCALE_CORRECTION_DB = {
84375
84772
  PCMU: 20 * Math.log10(4),
84376
84773
  PCMA: 20 * Math.log10(8)
@@ -188494,13 +188891,6 @@ var MowerChargingEdge = class {
188494
188891
  //#region src/devices/dreame-mower-device.ts
188495
188892
  var CAP_NAME$9 = "lawn-mower-control";
188496
188893
  var RUNNING_CAP$1 = "running-state";
188497
- var RUNNING_COLD$1 = {
188498
- running: null,
188499
- since: null,
188500
- source: "native",
188501
- phase: null,
188502
- lastFetchedAt: 0
188503
- };
188504
188894
  /**
188505
188895
  * The CMS consumable custom-action only answers while the mower is momentarily
188506
188896
  * awake and ACTIVELY charging — which it is the instant it docks with a depleted
@@ -188585,12 +188975,9 @@ var DreameMowerDevice = class extends DreameChildDevice {
188585
188975
  await this.requireMower().dock();
188586
188976
  }
188587
188977
  });
188588
- this.ctx.registerNativeCap(runningStateCapability, { getStatus: async () => this.runtimeState.getCapState(RUNNING_CAP$1) ?? RUNNING_COLD$1 });
188978
+ this.ctx.registerNativeCap(runningStateCapability, { getStatus: async () => this.runtimeState.getCapState(RUNNING_CAP$1) ?? RUNNING_STATE_COLD });
188589
188979
  this.runtimeState.setCapState(CAP_NAME$9, COLD_START);
188590
- this.runtimeState.setCapState(RUNNING_CAP$1, {
188591
- ...RUNNING_COLD$1,
188592
- lastFetchedAt: Date.now()
188593
- });
188980
+ this.runtimeState.setCapState(RUNNING_CAP$1, RUNNING_STATE_COLD);
188594
188981
  this.ctx.registerNativeCap(consumablesCapability, {
188595
188982
  getStatus: async () => this.runtimeState.getCapState("consumables") ?? CONSUMABLES_COLD_START,
188596
188983
  reset: async ({ key }) => {
@@ -188653,9 +189040,10 @@ var DreameMowerDevice = class extends DreameChildDevice {
188653
189040
  lastChangedAt: Date.now()
188654
189041
  };
188655
189042
  this.runtimeState.setCapState(CAP_NAME$9, slice);
189043
+ const fetchedAt = Date.now();
188656
189044
  const previous = this.runtimeState.getCapState(RUNNING_CAP$1);
188657
189045
  const activityForRunning = status === null ? null : mapMowerStatusToActivity(status);
188658
- this.runtimeState.setCapState(RUNNING_CAP$1, runningFromLawnMower(activityForRunning, previous, slice.lastChangedAt));
189046
+ this.runtimeState.setCapState(RUNNING_CAP$1, runningFromLawnMower(activityForRunning, previous, fetchedAt, slice.lastChangedAt));
188659
189047
  if (this.#chargingEdge.observe(status, mower.charging)) this.#consumables.refresh("charging");
188660
189048
  }
188661
189049
  /** The live typed mower handle, or throw a clear error when disconnected. */
@@ -189403,13 +189791,6 @@ function resetMeta(result) {
189403
189791
  //#region src/devices/dreame-vacuum-device.ts
189404
189792
  var CAP_NAME$1 = "vacuum-control";
189405
189793
  var RUNNING_CAP = "running-state";
189406
- var RUNNING_COLD = {
189407
- running: null,
189408
- since: null,
189409
- source: "native",
189410
- phase: null,
189411
- lastFetchedAt: 0
189412
- };
189413
189794
  var CONTROL_COLD_START = {
189414
189795
  state: "idle",
189415
189796
  batteryLevel: null,
@@ -189540,10 +189921,7 @@ var DreameVacuumDevice = class extends DreameChildDevice {
189540
189921
  });
189541
189922
  this.registerRunningCap();
189542
189923
  this.runtimeState.setCapState(CAP_NAME$1, CONTROL_COLD_START);
189543
- this.runtimeState.setCapState(RUNNING_CAP, {
189544
- ...RUNNING_COLD,
189545
- lastFetchedAt: Date.now()
189546
- });
189924
+ this.runtimeState.setCapState(RUNNING_CAP, RUNNING_STATE_COLD);
189547
189925
  this.registerConsumablesCap();
189548
189926
  }
189549
189927
  /**
@@ -189579,7 +189957,7 @@ var DreameVacuumDevice = class extends DreameChildDevice {
189579
189957
  };
189580
189958
  const toWrite = nextVacuumControlSlice(this.runtimeState.getCapState(CAP_NAME$1), controlSlice);
189581
189959
  if (toWrite !== null) this.runtimeState.setCapState(CAP_NAME$1, toWrite);
189582
- this.publishRunningSlice(miotStatus, controlSlice.lastChangedAt);
189960
+ this.publishRunningSlice(miotStatus, Date.now(), controlSlice.lastChangedAt);
189583
189961
  this.consumables.onPush(controlSlice.state, tanks);
189584
189962
  }
189585
189963
  /**
@@ -189604,11 +189982,11 @@ var DreameVacuumDevice = class extends DreameChildDevice {
189604
189982
  };
189605
189983
  }
189606
189984
  registerRunningCap() {
189607
- this.ctx.registerNativeCap(runningStateCapability, { getStatus: async () => this.runtimeState.getCapState(RUNNING_CAP) ?? RUNNING_COLD });
189985
+ this.ctx.registerNativeCap(runningStateCapability, { getStatus: async () => this.runtimeState.getCapState(RUNNING_CAP) ?? RUNNING_STATE_COLD });
189608
189986
  }
189609
- publishRunningSlice(miotStatus, sinceMs) {
189987
+ publishRunningSlice(miotStatus, fetchedAt, transitionAt) {
189610
189988
  const previous = this.runtimeState.getCapState(RUNNING_CAP);
189611
- const next = runningFromVacuumState(miotStatus === null ? null : mapMiotStateToVacuumState(miotStatus), previous, sinceMs);
189989
+ const next = runningFromVacuumState(miotStatus === null ? null : mapMiotStateToVacuumState(miotStatus), previous, fetchedAt, transitionAt);
189612
189990
  this.runtimeState.setCapState(RUNNING_CAP, next);
189613
189991
  }
189614
189992
  /** The live typed vacuum handle, or throw a clear error when disconnected. */
package/dist/addon.mjs CHANGED
@@ -8871,7 +8871,7 @@ function errMsg(err) {
8871
8871
  return String(err);
8872
8872
  }
8873
8873
  //#endregion
8874
- //#region ../types/dist/composition-xz-ud5EQ.mjs
8874
+ //#region ../types/dist/composition-BJ8iAZb3.mjs
8875
8875
  /**
8876
8876
  * The ffmpeg build every CamStack node downloads, per platform and arch.
8877
8877
  *
@@ -8931,7 +8931,7 @@ portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac
8931
8931
  * ONE table for everyone who asks "is this device HIGH?": the
8932
8932
  * recorder's `RecordingTriggers.sensorDeviceIds` trigger, the post-analysis
8933
8933
  * sensor markers, the orchestrator's camera-sensors bridge and
8934
- * the sources of a composite doorbell (`doorbellFeatureSources`, D671).
8934
+ * the sources of a pulse (`compilePulseOverride`, D936).
8935
8935
  * A second copy would diverge at the first cap added, and the symptom — "the
8936
8936
  * sensor rings the doorbell but does not record" — is exactly D62.
8937
8937
  *
@@ -54398,20 +54398,68 @@ var BatteryStatusSchema = object({
54398
54398
  */
54399
54399
  binary: boolean().optional()
54400
54400
  });
54401
+ var BATTERY_DEVICE_TYPES = [
54402
+ DeviceType.Camera,
54403
+ DeviceType.Sensor,
54404
+ DeviceType.Button,
54405
+ DeviceType.Switch,
54406
+ DeviceType.PetFeeder,
54407
+ DeviceType.Container,
54408
+ DeviceType.Thermostat
54409
+ ];
54410
+ /** Generic Customize target for the percentage reading (D936). */
54411
+ var batteryCustomize = {
54412
+ version: 1,
54413
+ targets: [{
54414
+ id: "percentage",
54415
+ cardinality: "scalar",
54416
+ vocabulary: {
54417
+ cap: "battery",
54418
+ stateField: "percentage"
54419
+ },
54420
+ deviceTypes: BATTERY_DEVICE_TYPES,
54421
+ apply: "add-or-replace",
54422
+ path: "percentage",
54423
+ sources: [{
54424
+ kind: "field",
54425
+ valueKinds: ["number", "string"],
54426
+ transforms: [{ kind: "identity" }, {
54427
+ kind: "linear",
54428
+ defaultScale: 1,
54429
+ defaultOffset: 0,
54430
+ clampMin: 0,
54431
+ clampMax: 100
54432
+ }]
54433
+ }],
54434
+ companions: [
54435
+ {
54436
+ kind: "constant",
54437
+ path: "charging",
54438
+ value: "unknown",
54439
+ when: "add"
54440
+ },
54441
+ {
54442
+ kind: "constant",
54443
+ path: "sleeping",
54444
+ value: false,
54445
+ when: "add"
54446
+ },
54447
+ {
54448
+ kind: "clock",
54449
+ path: "lastUpdated",
54450
+ when: "add"
54451
+ }
54452
+ ],
54453
+ sourceReport: { status: "assignment-status" },
54454
+ advancedExpression: true
54455
+ }]
54456
+ };
54401
54457
  var batteryCapability = {
54402
54458
  name: "battery",
54403
54459
  scope: "device",
54404
54460
  deviceNative: true,
54405
54461
  mode: "singleton",
54406
- deviceTypes: [
54407
- DeviceType.Camera,
54408
- DeviceType.Sensor,
54409
- DeviceType.Button,
54410
- DeviceType.Switch,
54411
- DeviceType.PetFeeder,
54412
- DeviceType.Container,
54413
- DeviceType.Thermostat
54414
- ],
54462
+ deviceTypes: BATTERY_DEVICE_TYPES,
54415
54463
  methods: {
54416
54464
  /**
54417
54465
  * Explicitly wake the camera from low-power sleep ahead of a
@@ -54495,6 +54543,7 @@ onStatusChanged: { data: object({
54495
54543
  kind: "gauge",
54496
54544
  unit: "%"
54497
54545
  } } },
54546
+ customize: batteryCustomize,
54498
54547
  /** Clock fields: written, but excluded from the compare that decides
54499
54548
  * whether persisting is worth a SQLite commit. */
54500
54549
  volatileStateFields: ["lastUpdated"]
@@ -55484,6 +55533,106 @@ var ConsumablesStatusSchema = object({
55484
55533
  items: array(ConsumableItemSchema),
55485
55534
  lastChangedAt: number()
55486
55535
  });
55536
+ /** Synthesize seed for a link-built status (no native provider).
55537
+ *
55538
+ * ⚠ NO LIVE CALLER since 2026-08-08 (device-links deleted; see
55539
+ * `CapabilityStatusSchema.empty`). The wiring guidance below was written for
55540
+ * exactly the case the operator is asking about again — a Dreame's HA
55541
+ * consumable-life sensors — so it is kept for that conversation, not because
55542
+ * anything reads it today. */
55543
+ var EMPTY_STATUS = {
55544
+ items: [],
55545
+ lastChangedAt: 0
55546
+ };
55547
+ /** Seed for a NEW link-built consumable item. `key`/`label` are overwritten
55548
+ * with the link's `itemKey` (see `status.itemArray`), so the placeholder
55549
+ * empty strings never survive into a validated item. Link-built items are
55550
+ * not resettable — there is no native provider to receive `reset()`.
55551
+ *
55552
+ * Wiring guidance (HA vacuum consumable-life sensors):
55553
+ * - a percent-remaining source (`numeric-sensor`, unit '%') → `level` via
55554
+ * `identity` (or `linear` scale 1 offset 0 clamp [0,100] to be safe);
55555
+ * - an hours-remaining source (e.g. Dreame 'Main Brush Time Left', unit 'h')
55556
+ * → `level` via `linear` with scale = 100 / max-hours for that item
55557
+ * (e.g. main brush 300 h → scale 0.3333) + clamp [0,100];
55558
+ * - a discrete source (`enum-sensor`, e.g. mop pad 'installed') → `status`
55559
+ * via `enum-map` (e.g. { installed: 'ok', missing: 'replace' }).
55560
+ * A numeric→'replace' THRESHOLD (level < 10 → 'replace') is NOT expressible
55561
+ * with the current transform kinds — leave `status` null (valid) until the
55562
+ * expression transform lands. */
55563
+ /** Generic Customize target for one consumable item (D936). Native rows stay read-only. */
55564
+ var consumablesCustomize = {
55565
+ version: 1,
55566
+ targets: [{
55567
+ id: "items",
55568
+ cardinality: "keyedArray",
55569
+ vocabulary: { cap: "consumables" },
55570
+ deviceTypes: Object.values(DeviceType),
55571
+ apply: "add",
55572
+ path: "items",
55573
+ keyField: "key",
55574
+ labelField: "label",
55575
+ itemSchema: ConsumableItemSchema,
55576
+ nativeItems: "read-only",
55577
+ sourceReport: { status: "assignment-status" },
55578
+ fields: [
55579
+ {
55580
+ path: "level",
55581
+ sources: [{
55582
+ kind: "numeric",
55583
+ transforms: [{ kind: "identity" }, {
55584
+ kind: "linear",
55585
+ defaultScale: 1,
55586
+ defaultOffset: 0,
55587
+ clampMin: 0,
55588
+ clampMax: 100
55589
+ }]
55590
+ }]
55591
+ },
55592
+ {
55593
+ path: "remaining.value",
55594
+ sources: [{
55595
+ kind: "numeric",
55596
+ transforms: [{ kind: "identity" }]
55597
+ }],
55598
+ literals: [{
55599
+ path: "remaining.unit",
55600
+ values: [...ConsumableRemainingUnitSchema.options]
55601
+ }]
55602
+ },
55603
+ {
55604
+ path: "status",
55605
+ sources: [{
55606
+ kind: "enum",
55607
+ transforms: [{
55608
+ kind: "enum-map",
55609
+ outputValues: [...ConsumableStatusSchema.options],
55610
+ unmapped: null
55611
+ }]
55612
+ }]
55613
+ }
55614
+ ],
55615
+ traits: [{
55616
+ path: "kind",
55617
+ values: [...ConsumableKindSchema.options],
55618
+ defaultValue: "wear"
55619
+ }, {
55620
+ path: "form",
55621
+ values: [...ConsumableFormSchema.options],
55622
+ defaultValue: "part"
55623
+ }],
55624
+ companions: [{
55625
+ kind: "clock",
55626
+ path: "lastChangedAt",
55627
+ when: "add"
55628
+ }, {
55629
+ kind: "clock",
55630
+ path: "lastFetchedAt",
55631
+ when: "add"
55632
+ }],
55633
+ advancedExpression: true
55634
+ }]
55635
+ };
55487
55636
  var consumablesCapability = {
55488
55637
  name: "consumables",
55489
55638
  scope: "device",
@@ -55521,10 +55670,7 @@ reset: method(object({
55521
55670
  status: {
55522
55671
  schema: ConsumablesStatusSchema,
55523
55672
  kind: "push",
55524
- empty: {
55525
- items: [],
55526
- lastChangedAt: 0
55527
- },
55673
+ empty: EMPTY_STATUS,
55528
55674
  itemArray: {
55529
55675
  path: "items",
55530
55676
  keyField: "key",
@@ -55560,6 +55706,7 @@ reset: method(object({
55560
55706
  * brush's remaining life and a dishwasher's salt are each recorded the moment
55561
55707
  * their provider reports them; an item that appears later gets its series.
55562
55708
  */
55709
+ customize: consumablesCustomize,
55563
55710
  history: {
55564
55711
  fields: {},
55565
55712
  items: {
@@ -56302,6 +56449,36 @@ var dishwasherCapability = {
56302
56449
  powerOn: { kind: "boolean" }
56303
56450
  } }
56304
56451
  };
56452
+ /** Pulse override: a rising edge advances `lastPressedAt` and the press count. */
56453
+ var doorbellCustomize = {
56454
+ version: 1,
56455
+ targets: [{
56456
+ id: "pressed",
56457
+ cardinality: "event",
56458
+ shape: "pulse",
56459
+ vocabulary: {
56460
+ cap: "doorbell",
56461
+ eventName: "onPressed"
56462
+ },
56463
+ deviceTypes: [DeviceType.Camera],
56464
+ apply: "add",
56465
+ applyAuthority: { kind: "composition-claim" },
56466
+ eventName: "onPressed",
56467
+ edges: ["rising"],
56468
+ timestampPath: "lastPressedAt",
56469
+ counterPath: "pressCountSinceStart",
56470
+ highWater: "composer-advance",
56471
+ combine: "any",
56472
+ debounce: {
56473
+ kind: "rising-edge",
56474
+ defaultDebounceMs: 2e3,
56475
+ changedAtRequired: true
56476
+ },
56477
+ sources: [{ kind: "binary" }, { kind: "switch" }],
56478
+ sourceReport: { status: "assignment-status" },
56479
+ advancedExpression: true
56480
+ }]
56481
+ };
56305
56482
  var DoorbellStatusSchema = object({
56306
56483
  /** Ms epoch of the last press. null = never observed since this provider started. */
56307
56484
  lastPressedAt: number().nullable(),
@@ -56368,7 +56545,8 @@ onPressed: {
56368
56545
  /**
56369
56546
  * History (D850): none — `pressCountSinceStart` resets with the addon, so it is not a monotonic meter; presses live in the events ledger.
56370
56547
  */
56371
- history: "none"
56548
+ history: "none",
56549
+ customize: doorbellCustomize
56372
56550
  };
56373
56551
  /**
56374
56552
  * Enum-state sensor — a string value picked from a finite option set.
@@ -58764,6 +58942,38 @@ authKey: string().optional() }), object({
58764
58942
  /** Human-readable error when `ok: false`. */
58765
58943
  error: string().optional()
58766
58944
  }), { kind: "mutation" });
58945
+ /** Span override: start on a rising edge, end on a fall or this timeout. */
58946
+ var motionCustomize = {
58947
+ version: 1,
58948
+ targets: [{
58949
+ id: "detected",
58950
+ cardinality: "event",
58951
+ shape: "span",
58952
+ vocabulary: {
58953
+ cap: "motion",
58954
+ eventName: "onMotionChanged"
58955
+ },
58956
+ deviceTypes: [DeviceType.Camera],
58957
+ apply: "replace",
58958
+ applyAuthority: {
58959
+ kind: "camera-sensors",
58960
+ timeoutField: "motionCooldownMs"
58961
+ },
58962
+ eventName: "onMotionChanged",
58963
+ start: "rising-edge",
58964
+ end: "falling-edge-or-timeout",
58965
+ timeoutMs: 3e4,
58966
+ combine: "any",
58967
+ sourceModes: [
58968
+ "in-addition",
58969
+ "confirm",
58970
+ "replace"
58971
+ ],
58972
+ sources: [{ kind: "binary" }, { kind: "switch" }],
58973
+ sourceReport: { status: "assignment-status" },
58974
+ advancedExpression: false
58975
+ }]
58976
+ };
58767
58977
  var MotionStatusSchema = object({
58768
58978
  detected: boolean(),
58769
58979
  /** Ms epoch of the last detected-true observation. Null if never detected. */
@@ -58903,7 +59113,8 @@ onMotionChanged: { data: MotionOnMotionChangedDataSchema } },
58903
59113
  history: { fields: { detected: {
58904
59114
  kind: "boolean",
58905
59115
  excludeDeviceTypes: [DeviceType.Camera]
58906
- } } }
59116
+ } } },
59117
+ customize: motionCustomize
58907
59118
  };
58908
59119
  /**
58909
59120
  * Motion-trigger toggle for accessory devices.
@@ -61777,6 +61988,16 @@ var ptzAutotrackCapability = {
61777
61988
  */
61778
61989
  history: "none"
61779
61990
  };
61991
+ var RUNNING_POWER_START_DELAY_MS = 3e4;
61992
+ var RUNNING_POWER_STOP_DELAY_MS = 18e4;
61993
+ /** Before any provider reading — all fields unknown (D933). */
61994
+ var RUNNING_STATE_COLD = {
61995
+ running: null,
61996
+ since: null,
61997
+ source: "native",
61998
+ phase: null,
61999
+ lastFetchedAt: null
62000
+ };
61780
62001
  var RunningStateSourceSchema = _enum(["native", "assigned"]);
61781
62002
  var RunningStateStatusSchema = object({
61782
62003
  /** null = unknown — never coerced to false (D393). */
@@ -61785,19 +62006,100 @@ var RunningStateStatusSchema = object({
61785
62006
  since: number().nullable(),
61786
62007
  source: RunningStateSourceSchema,
61787
62008
  phase: string().nullable(),
61788
- lastFetchedAt: number()
62009
+ /** null until a reading has been published (D393). */
62010
+ lastFetchedAt: number().nullable()
61789
62011
  });
61790
62012
  var RunningFlipEventDataSchema = object({
61791
62013
  deviceId: number(),
61792
62014
  since: number().nullable(),
61793
62015
  phase: string().nullable()
61794
62016
  });
62017
+ var RUNNING_DEVICE_TYPES = Object.values(DeviceType);
62018
+ /** Generic Customize target for the running boolean (D936). */
62019
+ var runningCustomize = {
62020
+ version: 1,
62021
+ targets: [{
62022
+ id: "running",
62023
+ cardinality: "scalar",
62024
+ vocabulary: {
62025
+ cap: "running-state",
62026
+ stateField: "running"
62027
+ },
62028
+ deviceTypes: [
62029
+ DeviceType.Dishwasher,
62030
+ DeviceType.Vacuum,
62031
+ DeviceType.LawnMower,
62032
+ DeviceType.MediaPlayer,
62033
+ DeviceType.WaterHeater,
62034
+ DeviceType.Humidifier,
62035
+ DeviceType.Fan,
62036
+ DeviceType.Climate,
62037
+ DeviceType.Thermostat,
62038
+ DeviceType.PetFeeder
62039
+ ],
62040
+ apply: "add-or-replace",
62041
+ path: "running",
62042
+ sources: [
62043
+ {
62044
+ kind: "switch",
62045
+ transforms: [{ kind: "identity" }]
62046
+ },
62047
+ {
62048
+ kind: "binary",
62049
+ transforms: [{ kind: "identity" }, { kind: "invert" }]
62050
+ },
62051
+ {
62052
+ kind: "numeric",
62053
+ requiredUnit: "W",
62054
+ transforms: [{
62055
+ kind: "threshold",
62056
+ comparison: "gte",
62057
+ defaultThreshold: 5,
62058
+ defaultStartDwellMs: RUNNING_POWER_START_DELAY_MS,
62059
+ defaultStopDwellMs: RUNNING_POWER_STOP_DELAY_MS,
62060
+ thresholdUnit: "W"
62061
+ }]
62062
+ }
62063
+ ],
62064
+ companions: [
62065
+ {
62066
+ kind: "constant",
62067
+ path: "source",
62068
+ value: "assigned",
62069
+ when: "always"
62070
+ },
62071
+ {
62072
+ kind: "constant",
62073
+ path: "phase",
62074
+ value: null,
62075
+ when: "always"
62076
+ },
62077
+ {
62078
+ kind: "clock",
62079
+ path: "lastFetchedAt",
62080
+ when: "always"
62081
+ },
62082
+ {
62083
+ kind: "transition-time",
62084
+ path: "since",
62085
+ sourceChangedAt: "prefer"
62086
+ }
62087
+ ],
62088
+ sourceReport: {
62089
+ status: "assignment-status",
62090
+ runtimeField: "source",
62091
+ nativeValue: "native",
62092
+ assignedValue: "assigned"
62093
+ },
62094
+ advancedExpression: true
62095
+ }]
62096
+ };
61795
62097
  var runningStateCapability = {
61796
62098
  name: "running-state",
61797
62099
  scope: "device",
61798
62100
  deviceNative: true,
61799
62101
  mode: "singleton",
61800
- deviceTypes: Object.values(DeviceType),
62102
+ deviceTypes: RUNNING_DEVICE_TYPES,
61801
62103
  methods: {},
61802
62104
  events: {
61803
62105
  onStarted: { data: RunningFlipEventDataSchema },
@@ -61810,7 +62112,8 @@ var runningStateCapability = {
61810
62112
  runtimeState: RunningStateStatusSchema,
61811
62113
  durability: "restored",
61812
62114
  history: { fields: { running: { kind: "boolean" } } },
61813
- volatileStateFields: ["lastFetchedAt"]
62115
+ volatileStateFields: ["lastFetchedAt"],
62116
+ customize: runningCustomize
61814
62117
  };
61815
62118
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
61816
62119
  kind: "mutation",
@@ -66442,31 +66745,35 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
66442
66745
  kind: "mutation",
66443
66746
  auth: "admin"
66444
66747
  });
66445
- function resolveSince(running, previous, sinceMs) {
66748
+ function resolveSince(running, previous, transitionAtMs, bootstrapSinceMs) {
66446
66749
  if (running === null) return null;
66447
- if ((previous?.running ?? null) !== running) return sinceMs;
66448
- return previous?.since ?? sinceMs;
66750
+ const prevRunning = previous?.running ?? null;
66751
+ if (prevRunning === null) return bootstrapSinceMs;
66752
+ if (prevRunning !== running) return transitionAtMs;
66753
+ return previous?.since ?? null;
66449
66754
  }
66450
- function nativeSlice(running, phase, previous, sinceMs, now) {
66755
+ function nativeSlice(running, phase, previous, transitionAtMs, fetchedAt, bootstrapSinceMs) {
66451
66756
  return {
66452
66757
  running,
66453
- since: resolveSince(running, previous, sinceMs),
66758
+ since: resolveSince(running, previous, transitionAtMs, bootstrapSinceMs),
66454
66759
  source: "native",
66455
66760
  phase: running === null ? null : phase,
66456
- lastFetchedAt: now
66761
+ lastFetchedAt: fetchedAt
66457
66762
  };
66458
66763
  }
66459
- function runningFromVacuumState(state, previous, now) {
66460
- if (state === null) return nativeSlice(null, null, previous, now, now);
66461
- if (state === "cleaning" || state === "returning" || state === "drying") return nativeSlice(true, state, previous, now, now);
66462
- if (state === "paused") return nativeSlice(false, "paused", previous, now, now);
66463
- return nativeSlice(false, state, previous, now, now);
66764
+ function runningFromVacuumState(state, previous, fetchedAt, transitionAt) {
66765
+ const transitionAtMs = transitionAt ?? fetchedAt;
66766
+ if (state === null) return nativeSlice(null, null, previous, transitionAtMs, fetchedAt, null);
66767
+ if (state === "cleaning" || state === "returning" || state === "drying") return nativeSlice(true, state, previous, transitionAtMs, fetchedAt, null);
66768
+ if (state === "paused") return nativeSlice(false, "paused", previous, transitionAtMs, fetchedAt, null);
66769
+ return nativeSlice(false, state, previous, transitionAtMs, fetchedAt, null);
66464
66770
  }
66465
- function runningFromLawnMower(activity, previous, now) {
66466
- if (activity === null) return nativeSlice(null, null, previous, now, now);
66467
- if (activity === "mowing") return nativeSlice(true, activity, previous, now, now);
66468
- if (activity === "paused") return nativeSlice(false, "paused", previous, now, now);
66469
- return nativeSlice(false, activity, previous, now, now);
66771
+ function runningFromLawnMower(activity, previous, fetchedAt, transitionAt) {
66772
+ const transitionAtMs = transitionAt ?? fetchedAt;
66773
+ if (activity === null) return nativeSlice(null, null, previous, transitionAtMs, fetchedAt, null);
66774
+ if (activity === "mowing") return nativeSlice(true, activity, previous, transitionAtMs, fetchedAt, null);
66775
+ if (activity === "paused") return nativeSlice(false, "paused", previous, transitionAtMs, fetchedAt, null);
66776
+ return nativeSlice(false, activity, previous, transitionAtMs, fetchedAt, null);
66470
66777
  }
66471
66778
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
66472
66779
  Object.freeze({
@@ -84368,6 +84675,96 @@ Object.freeze({
84368
84675
  "smtp-provider": "email"
84369
84676
  });
84370
84677
  DeviceType.Camera;
84678
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Camera, DeviceType.Camera, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
84679
+ /**
84680
+ * A saved Customize assignment (D936). The schema is the type: callers use
84681
+ * `z.infer`, they do not restate the shape.
84682
+ */
84683
+ var OverrideSourceSchema = object({
84684
+ addonId: string().min(1),
84685
+ stableId: string().min(1),
84686
+ cap: string().min(1),
84687
+ fieldPath: string().min(1)
84688
+ });
84689
+ var CustomizeTransformDraftSchema = discriminatedUnion("kind", [
84690
+ object({ kind: literal("identity") }),
84691
+ object({ kind: literal("invert") }),
84692
+ object({
84693
+ kind: literal("threshold"),
84694
+ comparison: _enum(["gte", "lte"]),
84695
+ threshold: number(),
84696
+ startDwellMs: number().nonnegative(),
84697
+ stopDwellMs: number().nonnegative()
84698
+ }),
84699
+ object({
84700
+ kind: literal("linear"),
84701
+ scale: number(),
84702
+ offset: number(),
84703
+ clampMin: number().optional(),
84704
+ clampMax: number().optional()
84705
+ }),
84706
+ object({
84707
+ kind: literal("enum-map"),
84708
+ mapping: record(string(), string().nullable())
84709
+ }),
84710
+ object({ kind: literal("number") })
84711
+ ]);
84712
+ var ScalarOverrideDraftSchema = object({
84713
+ kind: literal("scalar"),
84714
+ cap: string().min(1),
84715
+ targetId: string().min(1),
84716
+ source: OverrideSourceSchema,
84717
+ transform: CustomizeTransformDraftSchema
84718
+ });
84719
+ var KeyedArrayOverrideDraftSchema = object({
84720
+ kind: literal("keyedArray"),
84721
+ cap: string().min(1),
84722
+ targetId: string().min(1),
84723
+ key: string().min(1),
84724
+ label: string().min(1),
84725
+ fieldPath: string().min(1),
84726
+ source: OverrideSourceSchema,
84727
+ transform: CustomizeTransformDraftSchema
84728
+ });
84729
+ var PulseOverrideDraftSchema = object({
84730
+ kind: literal("pulse"),
84731
+ cap: string().min(1),
84732
+ targetId: string().min(1),
84733
+ enabled: boolean(),
84734
+ debounceMs: number().nonnegative(),
84735
+ sources: array(OverrideSourceSchema).min(1)
84736
+ });
84737
+ var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
84738
+ "in-addition",
84739
+ "confirm",
84740
+ "replace"
84741
+ ]).optional() });
84742
+ discriminatedUnion("kind", [
84743
+ ScalarOverrideDraftSchema,
84744
+ KeyedArrayOverrideDraftSchema,
84745
+ PulseOverrideDraftSchema,
84746
+ object({
84747
+ kind: literal("span"),
84748
+ cap: string().min(1),
84749
+ targetId: string().min(1),
84750
+ timeoutMs: number().nonnegative(),
84751
+ cooldownMs: number().nonnegative().optional(),
84752
+ sources: array(SpanSourceDraftSchema)
84753
+ })
84754
+ ]);
84755
+ object({
84756
+ targetId: string().min(1),
84757
+ configured: boolean(),
84758
+ source: _enum(["native", "assigned"]),
84759
+ state: _enum([
84760
+ "active",
84761
+ "stale",
84762
+ "released",
84763
+ "unavailable"
84764
+ ]),
84765
+ reason: string().nullable()
84766
+ });
84767
+ String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
84371
84768
  var G711_SCALE_CORRECTION_DB = {
84372
84769
  PCMU: 20 * Math.log10(4),
84373
84770
  PCMA: 20 * Math.log10(8)
@@ -188491,13 +188888,6 @@ var MowerChargingEdge = class {
188491
188888
  //#region src/devices/dreame-mower-device.ts
188492
188889
  var CAP_NAME$9 = "lawn-mower-control";
188493
188890
  var RUNNING_CAP$1 = "running-state";
188494
- var RUNNING_COLD$1 = {
188495
- running: null,
188496
- since: null,
188497
- source: "native",
188498
- phase: null,
188499
- lastFetchedAt: 0
188500
- };
188501
188891
  /**
188502
188892
  * The CMS consumable custom-action only answers while the mower is momentarily
188503
188893
  * awake and ACTIVELY charging — which it is the instant it docks with a depleted
@@ -188582,12 +188972,9 @@ var DreameMowerDevice = class extends DreameChildDevice {
188582
188972
  await this.requireMower().dock();
188583
188973
  }
188584
188974
  });
188585
- this.ctx.registerNativeCap(runningStateCapability, { getStatus: async () => this.runtimeState.getCapState(RUNNING_CAP$1) ?? RUNNING_COLD$1 });
188975
+ this.ctx.registerNativeCap(runningStateCapability, { getStatus: async () => this.runtimeState.getCapState(RUNNING_CAP$1) ?? RUNNING_STATE_COLD });
188586
188976
  this.runtimeState.setCapState(CAP_NAME$9, COLD_START);
188587
- this.runtimeState.setCapState(RUNNING_CAP$1, {
188588
- ...RUNNING_COLD$1,
188589
- lastFetchedAt: Date.now()
188590
- });
188977
+ this.runtimeState.setCapState(RUNNING_CAP$1, RUNNING_STATE_COLD);
188591
188978
  this.ctx.registerNativeCap(consumablesCapability, {
188592
188979
  getStatus: async () => this.runtimeState.getCapState("consumables") ?? CONSUMABLES_COLD_START,
188593
188980
  reset: async ({ key }) => {
@@ -188650,9 +189037,10 @@ var DreameMowerDevice = class extends DreameChildDevice {
188650
189037
  lastChangedAt: Date.now()
188651
189038
  };
188652
189039
  this.runtimeState.setCapState(CAP_NAME$9, slice);
189040
+ const fetchedAt = Date.now();
188653
189041
  const previous = this.runtimeState.getCapState(RUNNING_CAP$1);
188654
189042
  const activityForRunning = status === null ? null : mapMowerStatusToActivity(status);
188655
- this.runtimeState.setCapState(RUNNING_CAP$1, runningFromLawnMower(activityForRunning, previous, slice.lastChangedAt));
189043
+ this.runtimeState.setCapState(RUNNING_CAP$1, runningFromLawnMower(activityForRunning, previous, fetchedAt, slice.lastChangedAt));
188656
189044
  if (this.#chargingEdge.observe(status, mower.charging)) this.#consumables.refresh("charging");
188657
189045
  }
188658
189046
  /** The live typed mower handle, or throw a clear error when disconnected. */
@@ -189400,13 +189788,6 @@ function resetMeta(result) {
189400
189788
  //#region src/devices/dreame-vacuum-device.ts
189401
189789
  var CAP_NAME$1 = "vacuum-control";
189402
189790
  var RUNNING_CAP = "running-state";
189403
- var RUNNING_COLD = {
189404
- running: null,
189405
- since: null,
189406
- source: "native",
189407
- phase: null,
189408
- lastFetchedAt: 0
189409
- };
189410
189791
  var CONTROL_COLD_START = {
189411
189792
  state: "idle",
189412
189793
  batteryLevel: null,
@@ -189537,10 +189918,7 @@ var DreameVacuumDevice = class extends DreameChildDevice {
189537
189918
  });
189538
189919
  this.registerRunningCap();
189539
189920
  this.runtimeState.setCapState(CAP_NAME$1, CONTROL_COLD_START);
189540
- this.runtimeState.setCapState(RUNNING_CAP, {
189541
- ...RUNNING_COLD,
189542
- lastFetchedAt: Date.now()
189543
- });
189921
+ this.runtimeState.setCapState(RUNNING_CAP, RUNNING_STATE_COLD);
189544
189922
  this.registerConsumablesCap();
189545
189923
  }
189546
189924
  /**
@@ -189576,7 +189954,7 @@ var DreameVacuumDevice = class extends DreameChildDevice {
189576
189954
  };
189577
189955
  const toWrite = nextVacuumControlSlice(this.runtimeState.getCapState(CAP_NAME$1), controlSlice);
189578
189956
  if (toWrite !== null) this.runtimeState.setCapState(CAP_NAME$1, toWrite);
189579
- this.publishRunningSlice(miotStatus, controlSlice.lastChangedAt);
189957
+ this.publishRunningSlice(miotStatus, Date.now(), controlSlice.lastChangedAt);
189580
189958
  this.consumables.onPush(controlSlice.state, tanks);
189581
189959
  }
189582
189960
  /**
@@ -189601,11 +189979,11 @@ var DreameVacuumDevice = class extends DreameChildDevice {
189601
189979
  };
189602
189980
  }
189603
189981
  registerRunningCap() {
189604
- this.ctx.registerNativeCap(runningStateCapability, { getStatus: async () => this.runtimeState.getCapState(RUNNING_CAP) ?? RUNNING_COLD });
189982
+ this.ctx.registerNativeCap(runningStateCapability, { getStatus: async () => this.runtimeState.getCapState(RUNNING_CAP) ?? RUNNING_STATE_COLD });
189605
189983
  }
189606
- publishRunningSlice(miotStatus, sinceMs) {
189984
+ publishRunningSlice(miotStatus, fetchedAt, transitionAt) {
189607
189985
  const previous = this.runtimeState.getCapState(RUNNING_CAP);
189608
- const next = runningFromVacuumState(miotStatus === null ? null : mapMiotStateToVacuumState(miotStatus), previous, sinceMs);
189986
+ const next = runningFromVacuumState(miotStatus === null ? null : mapMiotStateToVacuumState(miotStatus), previous, fetchedAt, transitionAt);
189609
189987
  this.runtimeState.setCapState(RUNNING_CAP, next);
189610
189988
  }
189611
189989
  /** The live typed vacuum handle, or throw a clear error when disconnected. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-dreame",
3
- "version": "0.2.153",
3
+ "version": "0.2.154",
4
4
  "description": "Dreame robot-vacuum / lawn-mower device-provider addon for CamStack — wraps the @apocaliss92/nodedreame Dreamehome cloud client",
5
5
  "keywords": [
6
6
  "camstack",