@camstack/addon-provider-dreame 0.2.159 → 0.2.161
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/addon.js +1146 -562
- package/dist/addon.mjs +1147 -563
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -52902,7 +52902,7 @@ var DreameCameraStream = class extends TypedEmitter {
|
|
|
52902
52902
|
}
|
|
52903
52903
|
};
|
|
52904
52904
|
//#endregion
|
|
52905
|
-
//#region ../types/dist/event-category-
|
|
52905
|
+
//#region ../types/dist/event-category-DmZDPDf_.mjs
|
|
52906
52906
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
52907
52907
|
EventCategory["SystemBoot"] = "system.boot";
|
|
52908
52908
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -53529,7 +53529,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
53529
53529
|
* (a track was promoted to a stationary-registry entry) or departs (the
|
|
53530
53530
|
* object moved / was removed). Telemetry (D8): lossy, drives a UI refresh of
|
|
53531
53531
|
* the dedicated "Stationary" section — never the live event feed. Payload:
|
|
53532
|
-
* `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp }`.
|
|
53532
|
+
* `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp, observedAt, zones }`.
|
|
53533
53533
|
*/
|
|
53534
53534
|
EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
|
|
53535
53535
|
/**
|
|
@@ -53667,6 +53667,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
53667
53667
|
*/
|
|
53668
53668
|
EventCategory["AccessoriesChanged"] = "accessories.onAccessoriesChanged";
|
|
53669
53669
|
/**
|
|
53670
|
+
* An action run started or ended. Emitted by the `actions` surface.
|
|
53671
|
+
* Payload: `ActionRunChanged` (`{ actionId, runId, isRunning, outcome?, lastRunAt, lastError }`).
|
|
53672
|
+
*/
|
|
53673
|
+
EventCategory["ActionsRunChanged"] = "actions.onRunChanged";
|
|
53674
|
+
/**
|
|
53670
53675
|
* Progress update from a running model conversion job.
|
|
53671
53676
|
* Payload: `{ kind: 'model-convert', phase, sessionId?, pct?, detail? }`.
|
|
53672
53677
|
* Emitted by `addon-model-studio` on the converting node.
|
|
@@ -58345,7 +58350,7 @@ var ZodIssueCode = {
|
|
|
58345
58350
|
var ZodFirstPartyTypeKind;
|
|
58346
58351
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
58347
58352
|
//#endregion
|
|
58348
|
-
//#region ../types/dist/sleep-
|
|
58353
|
+
//#region ../types/dist/sleep-Crlmu_tO.mjs
|
|
58349
58354
|
/**
|
|
58350
58355
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
58351
58356
|
* window to float samples (D455).
|
|
@@ -61465,6 +61470,8 @@ Object.freeze({ "doorbell": "lastPressedAt" });
|
|
|
61465
61470
|
/** Every capability name, so addon code can check a name without the schema graph. */
|
|
61466
61471
|
var CAP_NAMES = Object.freeze([
|
|
61467
61472
|
"accessories",
|
|
61473
|
+
"action-source",
|
|
61474
|
+
"actions",
|
|
61468
61475
|
"addon-pages",
|
|
61469
61476
|
"addon-pages-source",
|
|
61470
61477
|
"addon-routes",
|
|
@@ -61653,11 +61660,11 @@ var CAP_NAMES = Object.freeze([
|
|
|
61653
61660
|
Object.freeze({
|
|
61654
61661
|
"air-quality-sensor": [
|
|
61655
61662
|
{
|
|
61656
|
-
"path": "
|
|
61663
|
+
"path": "co2",
|
|
61657
61664
|
"valueType": "number"
|
|
61658
61665
|
},
|
|
61659
61666
|
{
|
|
61660
|
-
"path": "
|
|
61667
|
+
"path": "voc",
|
|
61661
61668
|
"valueType": "number"
|
|
61662
61669
|
},
|
|
61663
61670
|
{
|
|
@@ -61707,7 +61714,7 @@ Object.freeze({
|
|
|
61707
61714
|
}
|
|
61708
61715
|
],
|
|
61709
61716
|
"ambient-light-sensor": [{
|
|
61710
|
-
"path": "
|
|
61717
|
+
"path": "illuminance",
|
|
61711
61718
|
"valueType": "number"
|
|
61712
61719
|
}],
|
|
61713
61720
|
"audio-metrics": [
|
|
@@ -61768,7 +61775,7 @@ Object.freeze({
|
|
|
61768
61775
|
],
|
|
61769
61776
|
"battery": [
|
|
61770
61777
|
{
|
|
61771
|
-
"path": "
|
|
61778
|
+
"path": "level",
|
|
61772
61779
|
"valueType": "number"
|
|
61773
61780
|
},
|
|
61774
61781
|
{
|
|
@@ -61790,7 +61797,7 @@ Object.freeze({
|
|
|
61790
61797
|
"valueType": "number"
|
|
61791
61798
|
},
|
|
61792
61799
|
{
|
|
61793
|
-
"path": "
|
|
61800
|
+
"path": "voltage",
|
|
61794
61801
|
"valueType": "number"
|
|
61795
61802
|
},
|
|
61796
61803
|
{
|
|
@@ -61811,7 +61818,7 @@ Object.freeze({
|
|
|
61811
61818
|
"valueType": "boolean"
|
|
61812
61819
|
}],
|
|
61813
61820
|
"brightness": [{
|
|
61814
|
-
"path": "
|
|
61821
|
+
"path": "level",
|
|
61815
61822
|
"valueType": "number"
|
|
61816
61823
|
}],
|
|
61817
61824
|
"broker": [{
|
|
@@ -62012,7 +62019,7 @@ Object.freeze({
|
|
|
62012
62019
|
"valueType": "number"
|
|
62013
62020
|
},
|
|
62014
62021
|
{
|
|
62015
|
-
"path": "
|
|
62022
|
+
"path": "colorTemperature",
|
|
62016
62023
|
"valueType": "number"
|
|
62017
62024
|
}
|
|
62018
62025
|
],
|
|
@@ -62191,15 +62198,15 @@ Object.freeze({
|
|
|
62191
62198
|
"valueType": "string"
|
|
62192
62199
|
},
|
|
62193
62200
|
{
|
|
62194
|
-
"path": "
|
|
62201
|
+
"path": "remaining",
|
|
62195
62202
|
"valueType": "number"
|
|
62196
62203
|
},
|
|
62197
62204
|
{
|
|
62198
|
-
"path": "
|
|
62205
|
+
"path": "delayedStart",
|
|
62199
62206
|
"valueType": "number"
|
|
62200
62207
|
},
|
|
62201
62208
|
{
|
|
62202
|
-
"path": "
|
|
62209
|
+
"path": "progress",
|
|
62203
62210
|
"valueType": "number"
|
|
62204
62211
|
},
|
|
62205
62212
|
{
|
|
@@ -62313,7 +62320,7 @@ Object.freeze({
|
|
|
62313
62320
|
],
|
|
62314
62321
|
"fan-control": [
|
|
62315
62322
|
{
|
|
62316
|
-
"path": "
|
|
62323
|
+
"path": "speed",
|
|
62317
62324
|
"valueType": "number"
|
|
62318
62325
|
},
|
|
62319
62326
|
{
|
|
@@ -62413,7 +62420,7 @@ Object.freeze({
|
|
|
62413
62420
|
}
|
|
62414
62421
|
],
|
|
62415
62422
|
"humidity-sensor": [{
|
|
62416
|
-
"path": "
|
|
62423
|
+
"path": "humidity",
|
|
62417
62424
|
"valueType": "number"
|
|
62418
62425
|
}],
|
|
62419
62426
|
"image": [{
|
|
@@ -62523,7 +62530,7 @@ Object.freeze({
|
|
|
62523
62530
|
"valueType": "number"
|
|
62524
62531
|
},
|
|
62525
62532
|
{
|
|
62526
|
-
"path": "
|
|
62533
|
+
"path": "progress",
|
|
62527
62534
|
"valueType": "number"
|
|
62528
62535
|
},
|
|
62529
62536
|
{
|
|
@@ -62602,11 +62609,11 @@ Object.freeze({
|
|
|
62602
62609
|
"valueType": "string"
|
|
62603
62610
|
},
|
|
62604
62611
|
{
|
|
62605
|
-
"path": "
|
|
62612
|
+
"path": "position",
|
|
62606
62613
|
"valueType": "number"
|
|
62607
62614
|
},
|
|
62608
62615
|
{
|
|
62609
|
-
"path": "
|
|
62616
|
+
"path": "duration",
|
|
62610
62617
|
"valueType": "number"
|
|
62611
62618
|
},
|
|
62612
62619
|
{
|
|
@@ -62708,11 +62715,11 @@ Object.freeze({
|
|
|
62708
62715
|
]
|
|
62709
62716
|
},
|
|
62710
62717
|
{
|
|
62711
|
-
"path": "
|
|
62718
|
+
"path": "signal",
|
|
62712
62719
|
"valueType": "number"
|
|
62713
62720
|
},
|
|
62714
62721
|
{
|
|
62715
|
-
"path": "
|
|
62722
|
+
"path": "rssi",
|
|
62716
62723
|
"valueType": "number"
|
|
62717
62724
|
},
|
|
62718
62725
|
{
|
|
@@ -62843,23 +62850,23 @@ Object.freeze({
|
|
|
62843
62850
|
],
|
|
62844
62851
|
"power-meter": [
|
|
62845
62852
|
{
|
|
62846
|
-
"path": "
|
|
62853
|
+
"path": "power",
|
|
62847
62854
|
"valueType": "number"
|
|
62848
62855
|
},
|
|
62849
62856
|
{
|
|
62850
|
-
"path": "
|
|
62857
|
+
"path": "energy",
|
|
62851
62858
|
"valueType": "number"
|
|
62852
62859
|
},
|
|
62853
62860
|
{
|
|
62854
|
-
"path": "
|
|
62861
|
+
"path": "voltage",
|
|
62855
62862
|
"valueType": "number"
|
|
62856
62863
|
},
|
|
62857
62864
|
{
|
|
62858
|
-
"path": "
|
|
62865
|
+
"path": "current",
|
|
62859
62866
|
"valueType": "number"
|
|
62860
62867
|
},
|
|
62861
62868
|
{
|
|
62862
|
-
"path": "
|
|
62869
|
+
"path": "apparentPower",
|
|
62863
62870
|
"valueType": "number"
|
|
62864
62871
|
},
|
|
62865
62872
|
{
|
|
@@ -62887,14 +62894,10 @@ Object.freeze({
|
|
|
62887
62894
|
{
|
|
62888
62895
|
"path": "gps.accuracyMeters",
|
|
62889
62896
|
"valueType": "number"
|
|
62890
|
-
},
|
|
62891
|
-
{
|
|
62892
|
-
"path": "batteryPercent",
|
|
62893
|
-
"valueType": "number"
|
|
62894
62897
|
}
|
|
62895
62898
|
],
|
|
62896
62899
|
"pressure-sensor": [{
|
|
62897
|
-
"path": "
|
|
62900
|
+
"path": "pressure",
|
|
62898
62901
|
"valueType": "number"
|
|
62899
62902
|
}],
|
|
62900
62903
|
"privacy-mask": [{
|
|
@@ -63208,7 +63211,7 @@ Object.freeze({
|
|
|
63208
63211
|
"valueType": "boolean"
|
|
63209
63212
|
}],
|
|
63210
63213
|
"temperature-sensor": [{
|
|
63211
|
-
"path": "
|
|
63214
|
+
"path": "temperature",
|
|
63212
63215
|
"valueType": "number"
|
|
63213
63216
|
}],
|
|
63214
63217
|
"update": [
|
|
@@ -63256,7 +63259,7 @@ Object.freeze({
|
|
|
63256
63259
|
"valueType": "string"
|
|
63257
63260
|
},
|
|
63258
63261
|
{
|
|
63259
|
-
"path": "
|
|
63262
|
+
"path": "progress",
|
|
63260
63263
|
"valueType": "number"
|
|
63261
63264
|
},
|
|
63262
63265
|
{
|
|
@@ -63755,7 +63758,7 @@ function errMsg(err) {
|
|
|
63755
63758
|
return String(err);
|
|
63756
63759
|
}
|
|
63757
63760
|
//#endregion
|
|
63758
|
-
//#region ../types/dist/composition-
|
|
63761
|
+
//#region ../types/dist/composition-CZImLpOI.mjs
|
|
63759
63762
|
/**
|
|
63760
63763
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
63761
63764
|
*
|
|
@@ -68519,6 +68522,14 @@ var AutomationConditionLeafSchema = object({
|
|
|
68519
68522
|
array(union([string(), number()]))
|
|
68520
68523
|
])
|
|
68521
68524
|
});
|
|
68525
|
+
var AutomationConditionStationaryPresentSchema = object({
|
|
68526
|
+
kind: literal("stationary-present"),
|
|
68527
|
+
deviceId: number().int().nonnegative(),
|
|
68528
|
+
/** Class names; a macro class matches every raw class that maps to it. */
|
|
68529
|
+
classes: array(string().min(1)).min(1).optional(),
|
|
68530
|
+
/** The parked object must be in at least one of these zones. */
|
|
68531
|
+
zones: array(string().min(1)).min(1).optional()
|
|
68532
|
+
});
|
|
68522
68533
|
/**
|
|
68523
68534
|
* The expression leaf, declared as a plain object rather than an intersection
|
|
68524
68535
|
* with {@link ExpressionSourceSchema}: a discriminated union has to be able to
|
|
@@ -68552,7 +68563,8 @@ var AutomationConditionSchema = lazy(() => discriminatedUnion("kind", [
|
|
|
68552
68563
|
child: AutomationConditionSchema
|
|
68553
68564
|
}),
|
|
68554
68565
|
AutomationConditionLeafSchema,
|
|
68555
|
-
AutomationConditionExpressionSchema
|
|
68566
|
+
AutomationConditionExpressionSchema,
|
|
68567
|
+
AutomationConditionStationaryPresentSchema
|
|
68556
68568
|
]));
|
|
68557
68569
|
/**
|
|
68558
68570
|
* What starts a run.
|
|
@@ -68593,7 +68605,34 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
|
|
|
68593
68605
|
kind: literal("schedule"),
|
|
68594
68606
|
cron: string().min(1).max(120)
|
|
68595
68607
|
}),
|
|
68596
|
-
object({ kind: literal("manual") })
|
|
68608
|
+
object({ kind: literal("manual") }),
|
|
68609
|
+
object({
|
|
68610
|
+
kind: literal("detection"),
|
|
68611
|
+
/** Class names. A macro class (`animal`, `vehicle`, `person`, `package`)
|
|
68612
|
+
* matches every raw class that maps to it; any other name matches
|
|
68613
|
+
* exactly. */
|
|
68614
|
+
classes: array(string().min(1)).min(1),
|
|
68615
|
+
deviceIds: array(number().int().nonnegative()).min(1),
|
|
68616
|
+
/** The track must have visited at least one of these zones. */
|
|
68617
|
+
zones: array(string().min(1)).min(1).optional(),
|
|
68618
|
+
/** Best-so-far confidence the track must reach. */
|
|
68619
|
+
minScore: number().min(0).max(1).optional()
|
|
68620
|
+
}),
|
|
68621
|
+
object({
|
|
68622
|
+
kind: literal("stationary"),
|
|
68623
|
+
deviceIds: array(number().int().nonnegative()).min(1),
|
|
68624
|
+
/** Class names; a macro class matches like the detection trigger. */
|
|
68625
|
+
classes: array(string().min(1)).min(1).optional(),
|
|
68626
|
+
/** The parked object must be in at least one of these zones. */
|
|
68627
|
+
zones: array(string().min(1)).min(1).optional(),
|
|
68628
|
+
/** `appeared`: it became parked. `departed`: the registry retired it.
|
|
68629
|
+
* The vocabulary of `PipelineAnalyticsStationaryChanged`. */
|
|
68630
|
+
phase: _enum(["appeared", "departed"])
|
|
68631
|
+
}),
|
|
68632
|
+
object({
|
|
68633
|
+
kind: literal("interval"),
|
|
68634
|
+
minutes: number().int().min(1).max(1440)
|
|
68635
|
+
})
|
|
68597
68636
|
]);
|
|
68598
68637
|
/**
|
|
68599
68638
|
* One action step.
|
|
@@ -68613,28 +68652,47 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
|
|
|
68613
68652
|
* is called. The runner still receives a number and is untouched; the
|
|
68614
68653
|
* resolution is the recipe's job, not the runner's.
|
|
68615
68654
|
*/
|
|
68655
|
+
var AutomationWaitStepSchema = object({
|
|
68656
|
+
kind: literal("wait"),
|
|
68657
|
+
seconds: number().min(0).max(300)
|
|
68658
|
+
});
|
|
68659
|
+
var AutomationCapStepSchema = object({
|
|
68660
|
+
kind: literal("cap"),
|
|
68661
|
+
/**
|
|
68662
|
+
* One device, several same-family devices (locks / climate family / …),
|
|
68663
|
+
* or `{ $var }` for the device that triggered the run. An array is
|
|
68664
|
+
* fan-out at codegen — each id gets the same cap.method call.
|
|
68665
|
+
*/
|
|
68666
|
+
deviceId: union([
|
|
68667
|
+
number().int(),
|
|
68668
|
+
array(number().int()).min(1),
|
|
68669
|
+
object({ $var: string().min(1) })
|
|
68670
|
+
]),
|
|
68671
|
+
cap: string().min(1),
|
|
68672
|
+
method: string().min(1),
|
|
68673
|
+
/** Values may carry `{{vars.x}}` slots, or be exactly `{ $var: 'name' }`, which
|
|
68674
|
+
* SUBSTITUTE and do not evaluate (§3.2.3). Anything beyond substitution is
|
|
68675
|
+
* the expression leaf. */
|
|
68676
|
+
args: record(string(), unknown()).optional()
|
|
68677
|
+
});
|
|
68678
|
+
/**
|
|
68679
|
+
* The steps an internal Action may contain: cap and wait, and nothing that can
|
|
68680
|
+
* call back into an Action — so an Action can never reach itself (no cycles).
|
|
68681
|
+
*/
|
|
68682
|
+
var ActionStepSchema = discriminatedUnion("kind", [AutomationWaitStepSchema, AutomationCapStepSchema]);
|
|
68616
68683
|
var AutomationActionSchema = discriminatedUnion("kind", [
|
|
68684
|
+
AutomationWaitStepSchema,
|
|
68685
|
+
AutomationCapStepSchema,
|
|
68617
68686
|
object({
|
|
68618
|
-
kind: literal("
|
|
68619
|
-
|
|
68620
|
-
|
|
68621
|
-
|
|
68622
|
-
|
|
68623
|
-
|
|
68624
|
-
|
|
68625
|
-
|
|
68626
|
-
|
|
68627
|
-
*/
|
|
68628
|
-
deviceId: union([
|
|
68629
|
-
number().int(),
|
|
68630
|
-
array(number().int()).min(1),
|
|
68631
|
-
object({ $var: string().min(1) })
|
|
68632
|
-
]),
|
|
68633
|
-
cap: string().min(1),
|
|
68634
|
-
method: string().min(1),
|
|
68635
|
-
/** Values may carry `{{vars.x}}` slots, which SUBSTITUTE and do not
|
|
68636
|
-
* evaluate (§3.2.3). Anything beyond substitution is the expression leaf. */
|
|
68637
|
-
args: record(string(), unknown()).optional()
|
|
68687
|
+
kind: literal("action"),
|
|
68688
|
+
/** An entry of the `actions` surface (`internal:<uuid>` or `<addonId>:<localId>`). */
|
|
68689
|
+
actionId: string().min(1),
|
|
68690
|
+
/** Values for the action's declared variables; the others take their default. */
|
|
68691
|
+
variables: record(string(), union([
|
|
68692
|
+
string(),
|
|
68693
|
+
number(),
|
|
68694
|
+
boolean()
|
|
68695
|
+
])).optional()
|
|
68638
68696
|
}),
|
|
68639
68697
|
object({
|
|
68640
68698
|
kind: literal("code"),
|
|
@@ -68643,10 +68701,357 @@ var AutomationActionSchema = discriminatedUnion("kind", [
|
|
|
68643
68701
|
code: string().min(1).max(2e4)
|
|
68644
68702
|
})
|
|
68645
68703
|
]);
|
|
68704
|
+
/**
|
|
68705
|
+
* How a trigger that arrives while the automation is busy (or cooling down) is
|
|
68706
|
+
* handled. One of the automation's execution modes, never a property of its
|
|
68707
|
+
* actions. Absent means `single`.
|
|
68708
|
+
*
|
|
68709
|
+
* - `single` — drop the trigger while a run is in progress (traced + logged).
|
|
68710
|
+
* - `restart` — abort the run in progress (at its next step boundary), start fresh.
|
|
68711
|
+
* - `queued` — queue up to `max` triggers, drop the rest (traced).
|
|
68712
|
+
* - `parallel` — up to `max` runs at once, drop the rest (traced).
|
|
68713
|
+
* - `cooldown` — `single`, and no new run until `minutes` after the last run
|
|
68714
|
+
* STARTED; persisted across a restart. A manual trigger bypasses only this
|
|
68715
|
+
* wait, never the single/queued limits.
|
|
68716
|
+
*/
|
|
68717
|
+
var AutomationRunModeSchema = discriminatedUnion("mode", [
|
|
68718
|
+
object({ mode: literal("single") }),
|
|
68719
|
+
object({ mode: literal("restart") }),
|
|
68720
|
+
object({
|
|
68721
|
+
mode: literal("queued"),
|
|
68722
|
+
max: number().int().min(1).max(10)
|
|
68723
|
+
}),
|
|
68724
|
+
object({
|
|
68725
|
+
mode: literal("parallel"),
|
|
68726
|
+
max: number().int().min(1).max(10)
|
|
68727
|
+
}),
|
|
68728
|
+
object({
|
|
68729
|
+
mode: literal("cooldown"),
|
|
68730
|
+
minutes: number().int().min(1).max(1440)
|
|
68731
|
+
})
|
|
68732
|
+
]);
|
|
68646
68733
|
var AutomationRecipeSchema = object({
|
|
68647
68734
|
triggers: array(AutomationTriggerSchema),
|
|
68648
68735
|
conditions: AutomationConditionSchema.optional(),
|
|
68649
|
-
actions: array(AutomationActionSchema)
|
|
68736
|
+
actions: array(AutomationActionSchema),
|
|
68737
|
+
runMode: AutomationRunModeSchema.optional(),
|
|
68738
|
+
/** How many run traces the automation keeps (default {@link DEFAULT_TRACE_KEEP}). */
|
|
68739
|
+
keepTraces: number().int().min(1).max(100).optional()
|
|
68740
|
+
});
|
|
68741
|
+
new Set([
|
|
68742
|
+
{
|
|
68743
|
+
id: "person",
|
|
68744
|
+
name: "Person"
|
|
68745
|
+
},
|
|
68746
|
+
{
|
|
68747
|
+
id: "vehicle",
|
|
68748
|
+
name: "Vehicle"
|
|
68749
|
+
},
|
|
68750
|
+
{
|
|
68751
|
+
id: "animal",
|
|
68752
|
+
name: "Animal"
|
|
68753
|
+
},
|
|
68754
|
+
{
|
|
68755
|
+
id: "package",
|
|
68756
|
+
name: "Package"
|
|
68757
|
+
}
|
|
68758
|
+
].map((l) => l.id));
|
|
68759
|
+
var COCO_TO_MACRO = {
|
|
68760
|
+
mapping: {
|
|
68761
|
+
person: "person",
|
|
68762
|
+
bicycle: "vehicle",
|
|
68763
|
+
car: "vehicle",
|
|
68764
|
+
motorcycle: "vehicle",
|
|
68765
|
+
airplane: "vehicle",
|
|
68766
|
+
bus: "vehicle",
|
|
68767
|
+
train: "vehicle",
|
|
68768
|
+
truck: "vehicle",
|
|
68769
|
+
boat: "vehicle",
|
|
68770
|
+
bird: "animal",
|
|
68771
|
+
cat: "animal",
|
|
68772
|
+
dog: "animal",
|
|
68773
|
+
horse: "animal",
|
|
68774
|
+
sheep: "animal",
|
|
68775
|
+
cow: "animal",
|
|
68776
|
+
elephant: "animal",
|
|
68777
|
+
bear: "animal",
|
|
68778
|
+
zebra: "animal",
|
|
68779
|
+
giraffe: "animal",
|
|
68780
|
+
suitcase: "package",
|
|
68781
|
+
backpack: "package",
|
|
68782
|
+
handbag: "package"
|
|
68783
|
+
},
|
|
68784
|
+
preserveOriginal: false
|
|
68785
|
+
};
|
|
68786
|
+
new Set(Object.values(COCO_TO_MACRO.mapping));
|
|
68787
|
+
/**
|
|
68788
|
+
* Run traces — what an automation (or, later, an Action run) DID, node by node,
|
|
68789
|
+
* so the editor can paint every block green or red with its reason.
|
|
68790
|
+
*
|
|
68791
|
+
* A trace is data, not a log: it covers the WHOLE tree — the trigger stage,
|
|
68792
|
+
* every condition node (containers and leaves, including the ones a
|
|
68793
|
+
* short-circuit never reached), the run-mode verdict and every action step —
|
|
68794
|
+
* each addressed by a stable path (`conditions.0.1`, `actions.2`, or `steps.N`
|
|
68795
|
+
* for an Action run). Skipped
|
|
68796
|
+
* evaluations are traced too, and identical consecutive skips collapse into a
|
|
68797
|
+
* repeat count so a trigger that fails every 5 minutes cannot flush real runs.
|
|
68798
|
+
*
|
|
68799
|
+
* {@link AutomationStepTraceSchema} is deliberately free of automation
|
|
68800
|
+
* vocabulary so an Action run records the same step shape.
|
|
68801
|
+
*/
|
|
68802
|
+
/** A value a trace may show: JSON primitives and short lists, nothing deeper. */
|
|
68803
|
+
var TraceValueSchema = union([
|
|
68804
|
+
string(),
|
|
68805
|
+
number(),
|
|
68806
|
+
boolean(),
|
|
68807
|
+
_null(),
|
|
68808
|
+
array(union([string(), number()]))
|
|
68809
|
+
]);
|
|
68810
|
+
var AutomationRunOutcomeSchema = _enum([
|
|
68811
|
+
"ran",
|
|
68812
|
+
"skipped-trigger",
|
|
68813
|
+
"skipped-conditions",
|
|
68814
|
+
"skipped-cooldown",
|
|
68815
|
+
"skipped-overlap",
|
|
68816
|
+
"failed",
|
|
68817
|
+
"aborted"
|
|
68818
|
+
]);
|
|
68819
|
+
/** `not-evaluated` = a short-circuit never reached the node. */
|
|
68820
|
+
var TraceNodeOutcomeSchema = _enum([
|
|
68821
|
+
"pass",
|
|
68822
|
+
"fail",
|
|
68823
|
+
"not-evaluated"
|
|
68824
|
+
]);
|
|
68825
|
+
var AutomationTriggerTraceSchema = object({
|
|
68826
|
+
kind: string().min(1),
|
|
68827
|
+
outcome: _enum(["pass", "fail"]),
|
|
68828
|
+
/** Why an event was rejected ("class person not in [dog]") or what fired. */
|
|
68829
|
+
reason: string(),
|
|
68830
|
+
/** deviceId, className, trackId, score … — only what the trigger exposes. */
|
|
68831
|
+
vars: record(string(), TraceValueSchema)
|
|
68832
|
+
});
|
|
68833
|
+
var AutomationConditionTraceNodeSchema = object({
|
|
68834
|
+
/** `conditions` for the root, `conditions.<i>` for its i-th child, … */
|
|
68835
|
+
path: string().min(1),
|
|
68836
|
+
kind: _enum([
|
|
68837
|
+
"all",
|
|
68838
|
+
"any",
|
|
68839
|
+
"not",
|
|
68840
|
+
"condition",
|
|
68841
|
+
"expression",
|
|
68842
|
+
"stationary-present"
|
|
68843
|
+
]),
|
|
68844
|
+
outcome: TraceNodeOutcomeSchema,
|
|
68845
|
+
reason: string(),
|
|
68846
|
+
deviceId: number().int().optional(),
|
|
68847
|
+
cap: string().optional(),
|
|
68848
|
+
fieldPath: string().optional(),
|
|
68849
|
+
operator: string().optional(),
|
|
68850
|
+
expected: TraceValueSchema.optional(),
|
|
68851
|
+
/** What the mirror held; `null` when it held nothing (unknown). */
|
|
68852
|
+
observed: TraceValueSchema.optional()
|
|
68853
|
+
});
|
|
68854
|
+
var AutomationRunVerdictSchema = object({
|
|
68855
|
+
mode: AutomationRunModeSchema,
|
|
68856
|
+
decision: _enum([
|
|
68857
|
+
"run",
|
|
68858
|
+
"queued",
|
|
68859
|
+
"restart",
|
|
68860
|
+
"drop",
|
|
68861
|
+
"cooldown",
|
|
68862
|
+
"bypass"
|
|
68863
|
+
]),
|
|
68864
|
+
reason: string()
|
|
68865
|
+
});
|
|
68866
|
+
/** One executed (or not-run) step — shared by automations and Action runs. */
|
|
68867
|
+
var AutomationStepTraceSchema = object({
|
|
68868
|
+
/** `actions.<i>` */
|
|
68869
|
+
path: string().min(1),
|
|
68870
|
+
kind: string().min(1),
|
|
68871
|
+
cap: string().optional(),
|
|
68872
|
+
method: string().optional(),
|
|
68873
|
+
deviceId: number().int().optional(),
|
|
68874
|
+
startedAt: number().optional(),
|
|
68875
|
+
endedAt: number().optional(),
|
|
68876
|
+
outcome: _enum([
|
|
68877
|
+
"ok",
|
|
68878
|
+
"error",
|
|
68879
|
+
"not-run"
|
|
68880
|
+
]),
|
|
68881
|
+
message: string().optional()
|
|
68882
|
+
});
|
|
68883
|
+
var AutomationRunTraceSchema = object({
|
|
68884
|
+
runId: string().min(1),
|
|
68885
|
+
startedAt: number(),
|
|
68886
|
+
endedAt: number(),
|
|
68887
|
+
trigger: AutomationTriggerTraceSchema,
|
|
68888
|
+
conditions: array(AutomationConditionTraceNodeSchema),
|
|
68889
|
+
verdict: AutomationRunVerdictSchema.optional(),
|
|
68890
|
+
steps: array(AutomationStepTraceSchema),
|
|
68891
|
+
outcome: AutomationRunOutcomeSchema,
|
|
68892
|
+
/** How many identical consecutive skips this trace stands for (>= 1). */
|
|
68893
|
+
repeat: number().int().min(1),
|
|
68894
|
+
/** When the latest of those repeats happened. */
|
|
68895
|
+
lastAt: number()
|
|
68896
|
+
});
|
|
68897
|
+
/**
|
|
68898
|
+
* What the hub's `runs` ledger holds for one owner (`automation:<blockId>` or
|
|
68899
|
+
* `action:<actionId>`): the cooldown stamp and the trace ring.
|
|
68900
|
+
*/
|
|
68901
|
+
var AutomationStateDocSchema = object({
|
|
68902
|
+
version: literal(1),
|
|
68903
|
+
lastRunStartedAt: number().nullable(),
|
|
68904
|
+
traces: array(AutomationRunTraceSchema)
|
|
68905
|
+
});
|
|
68906
|
+
/**
|
|
68907
|
+
* Actions — named, runnable things an operator (or an automation) can start,
|
|
68908
|
+
* declared as LIST ENTRIES, never as devices.
|
|
68909
|
+
*
|
|
68910
|
+
* An entry is either **internal** (a short list of cap/wait steps kept in the
|
|
68911
|
+
* hub's `actions` record kind) or declared by an addon through the
|
|
68912
|
+
* `action-source` collection (a Home Assistant script, a scene …). The one
|
|
68913
|
+
* surface the UI and automations talk to is the `actions` cap; it keeps no
|
|
68914
|
+
* catalog of its own (D3) — every `list` asks the providers live.
|
|
68915
|
+
*
|
|
68916
|
+
* This file is the data half: the entry schema, the variables an entry may
|
|
68917
|
+
* declare, and the pure resolution of the values a run was given. It holds no
|
|
68918
|
+
* runtime.
|
|
68919
|
+
*/
|
|
68920
|
+
/** What a variable's value is. `select` takes one of `options`. */
|
|
68921
|
+
var ActionVariableTypeSchema = _enum([
|
|
68922
|
+
"string",
|
|
68923
|
+
"number",
|
|
68924
|
+
"boolean",
|
|
68925
|
+
"select"
|
|
68926
|
+
]);
|
|
68927
|
+
var ACTION_VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
68928
|
+
var ActionVariableValueSchema = union([
|
|
68929
|
+
string(),
|
|
68930
|
+
number(),
|
|
68931
|
+
boolean()
|
|
68932
|
+
]);
|
|
68933
|
+
/**
|
|
68934
|
+
* One declared variable. Steps reference it as `{ $var: 'name' }` (the whole
|
|
68935
|
+
* value) or `{{vars.name}}` (inside a string) in their args.
|
|
68936
|
+
*/
|
|
68937
|
+
var ActionVariableSchema = object({
|
|
68938
|
+
name: string().regex(ACTION_VARIABLE_NAME_RE),
|
|
68939
|
+
label: string().min(1).max(120).optional(),
|
|
68940
|
+
type: ActionVariableTypeSchema,
|
|
68941
|
+
required: boolean().optional(),
|
|
68942
|
+
default: ActionVariableValueSchema.optional(),
|
|
68943
|
+
/** The allowed values of a `select`. */
|
|
68944
|
+
options: array(string().min(1)).min(1).optional()
|
|
68945
|
+
}).superRefine((v, ctx) => {
|
|
68946
|
+
if (v.type === "select" && v.options === void 0) ctx.addIssue({
|
|
68947
|
+
code: "custom",
|
|
68948
|
+
message: "a select variable needs options",
|
|
68949
|
+
path: ["options"]
|
|
68950
|
+
});
|
|
68951
|
+
if (v.default !== void 0 && variableValueError(v, v.default) !== null) ctx.addIssue({
|
|
68952
|
+
code: "custom",
|
|
68953
|
+
message: variableValueError(v, v.default) ?? "invalid default",
|
|
68954
|
+
path: ["default"]
|
|
68955
|
+
});
|
|
68956
|
+
});
|
|
68957
|
+
/** Why `value` is not a legal value for the variable, or null when it is. */
|
|
68958
|
+
function variableValueError(variable, value) {
|
|
68959
|
+
switch (variable.type) {
|
|
68960
|
+
case "string": return typeof value === "string" ? null : `variable "${variable.name}" must be a string`;
|
|
68961
|
+
case "number": return typeof value === "number" && Number.isFinite(value) ? null : `variable "${variable.name}" must be a number`;
|
|
68962
|
+
case "boolean": return typeof value === "boolean" ? null : `variable "${variable.name}" must be true or false`;
|
|
68963
|
+
case "select": return typeof value === "string" && (variable.options ?? []).includes(value) ? null : `variable "${variable.name}" must be one of ${(variable.options ?? []).join(", ")}`;
|
|
68964
|
+
}
|
|
68965
|
+
}
|
|
68966
|
+
var ActionSourceKindSchema = _enum(["internal", "addon"]);
|
|
68967
|
+
function isInternalActionId(actionId) {
|
|
68968
|
+
return actionId.startsWith("internal:") && actionId.length > 9;
|
|
68969
|
+
}
|
|
68970
|
+
/** The addon behind a `<addonId>:<localId>` id, or null for anything else (internal included). */
|
|
68971
|
+
function addonIdOfActionId(actionId) {
|
|
68972
|
+
if (isInternalActionId(actionId)) return null;
|
|
68973
|
+
const at = actionId.indexOf(":");
|
|
68974
|
+
return at > 0 && at < actionId.length - 1 ? actionId.slice(0, at) : null;
|
|
68975
|
+
}
|
|
68976
|
+
var ActionStatusSchema = object({
|
|
68977
|
+
isRunning: boolean(),
|
|
68978
|
+
/** Ms epoch the latest run STARTED; null when it never ran. */
|
|
68979
|
+
lastRunAt: number().nullable(),
|
|
68980
|
+
/** The latest run's failure; null on success or when it never ran. */
|
|
68981
|
+
lastError: string().nullable()
|
|
68982
|
+
});
|
|
68983
|
+
var ActionEntrySchema = object({
|
|
68984
|
+
/** `internal:<uuid>` or `<addonId>:<localId>`. */
|
|
68985
|
+
id: string().min(3),
|
|
68986
|
+
name: string().min(1).max(120),
|
|
68987
|
+
/** The addon that declared it; `core-blocks` for an internal one. */
|
|
68988
|
+
addonId: string().min(1),
|
|
68989
|
+
source: ActionSourceKindSchema,
|
|
68990
|
+
/** An addon-declared entry is edited where it was declared. */
|
|
68991
|
+
readOnly: boolean(),
|
|
68992
|
+
/** Internal only: cap and wait steps. No action-in-action, so no cycles. */
|
|
68993
|
+
steps: array(ActionStepSchema).min(1).max(50).optional(),
|
|
68994
|
+
variables: array(ActionVariableSchema).max(20).optional(),
|
|
68995
|
+
status: ActionStatusSchema
|
|
68996
|
+
}).superRefine((entry, ctx) => {
|
|
68997
|
+
if (entry.source === "internal") {
|
|
68998
|
+
if (!isInternalActionId(entry.id)) ctx.addIssue({
|
|
68999
|
+
code: "custom",
|
|
69000
|
+
message: "an internal action id is internal:<uuid>",
|
|
69001
|
+
path: ["id"]
|
|
69002
|
+
});
|
|
69003
|
+
return;
|
|
69004
|
+
}
|
|
69005
|
+
if (entry.steps !== void 0) ctx.addIssue({
|
|
69006
|
+
code: "custom",
|
|
69007
|
+
message: "only an internal action carries steps",
|
|
69008
|
+
path: ["steps"]
|
|
69009
|
+
});
|
|
69010
|
+
if (!entry.readOnly) ctx.addIssue({
|
|
69011
|
+
code: "custom",
|
|
69012
|
+
message: "an addon action is read-only here",
|
|
69013
|
+
path: ["readOnly"]
|
|
69014
|
+
});
|
|
69015
|
+
if (addonIdOfActionId(entry.id) === null) ctx.addIssue({
|
|
69016
|
+
code: "custom",
|
|
69017
|
+
message: "an addon action id is <addonId>:<localId>",
|
|
69018
|
+
path: ["id"]
|
|
69019
|
+
});
|
|
69020
|
+
});
|
|
69021
|
+
/** What an operator writes for an internal action. */
|
|
69022
|
+
var ActionInputSchema = object({
|
|
69023
|
+
name: string().min(1).max(120),
|
|
69024
|
+
steps: array(ActionStepSchema).min(1).max(50),
|
|
69025
|
+
variables: array(ActionVariableSchema).max(20).optional()
|
|
69026
|
+
});
|
|
69027
|
+
/** Who started a run. Absent = the operator pressed it. */
|
|
69028
|
+
var ActionRunOriginSchema = object({
|
|
69029
|
+
/** The automation block whose step started it. */
|
|
69030
|
+
blockId: string().min(1).optional() });
|
|
69031
|
+
/** `started` = the caller did not ask to wait. */
|
|
69032
|
+
var ActionRunOutcomeSchema = union([AutomationRunOutcomeSchema, literal("started")]);
|
|
69033
|
+
var ActionRunResultSchema = object({
|
|
69034
|
+
runId: string().min(1),
|
|
69035
|
+
outcome: ActionRunOutcomeSchema,
|
|
69036
|
+
/** Why it did not run, or what failed. */
|
|
69037
|
+
message: string().optional()
|
|
69038
|
+
});
|
|
69039
|
+
_enum([
|
|
69040
|
+
"not-found",
|
|
69041
|
+
"read-only",
|
|
69042
|
+
"invalid-variables",
|
|
69043
|
+
"invalid-steps",
|
|
69044
|
+
"not-linked",
|
|
69045
|
+
"busy"
|
|
69046
|
+
]);
|
|
69047
|
+
object({
|
|
69048
|
+
actionId: string(),
|
|
69049
|
+
runId: string(),
|
|
69050
|
+
isRunning: boolean(),
|
|
69051
|
+
/** Set when the run ended. */
|
|
69052
|
+
outcome: AutomationRunOutcomeSchema.optional(),
|
|
69053
|
+
lastRunAt: number().nullable(),
|
|
69054
|
+
lastError: string().nullable()
|
|
68650
69055
|
});
|
|
68651
69056
|
/**
|
|
68652
69057
|
* `addon-pages` — system-scoped singleton aggregator cap. Public-facing
|
|
@@ -71333,7 +71738,7 @@ var CompositionEventPlanSchema = object({
|
|
|
71333
71738
|
stateField: string(),
|
|
71334
71739
|
fieldKey: string()
|
|
71335
71740
|
});
|
|
71336
|
-
/** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.
|
|
71741
|
+
/** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.temperature`. */
|
|
71337
71742
|
var CompositionProblemSchema = object({
|
|
71338
71743
|
code: _enum([
|
|
71339
71744
|
"reserved-variant",
|
|
@@ -71652,7 +72057,7 @@ var GROUP_TEMPLATES = [
|
|
|
71652
72057
|
summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
|
|
71653
72058
|
role: DeviceRole.TemperatureSensor,
|
|
71654
72059
|
outputCap: "temperature-sensor",
|
|
71655
|
-
outputField: "
|
|
72060
|
+
outputField: "temperature",
|
|
71656
72061
|
quantity: "temperature",
|
|
71657
72062
|
defaultAggregate: "mean",
|
|
71658
72063
|
empty: "null",
|
|
@@ -71663,7 +72068,7 @@ var GROUP_TEMPLATES = [
|
|
|
71663
72068
|
summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
|
|
71664
72069
|
role: DeviceRole.HumiditySensor,
|
|
71665
72070
|
outputCap: "humidity-sensor",
|
|
71666
|
-
outputField: "
|
|
72071
|
+
outputField: "humidity",
|
|
71667
72072
|
quantity: "humidity",
|
|
71668
72073
|
defaultAggregate: "mean",
|
|
71669
72074
|
empty: "null",
|
|
@@ -71674,7 +72079,7 @@ var GROUP_TEMPLATES = [
|
|
|
71674
72079
|
summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
|
|
71675
72080
|
role: DeviceRole.PressureSensor,
|
|
71676
72081
|
outputCap: "pressure-sensor",
|
|
71677
|
-
outputField: "
|
|
72082
|
+
outputField: "pressure",
|
|
71678
72083
|
quantity: "pressure",
|
|
71679
72084
|
defaultAggregate: "mean",
|
|
71680
72085
|
empty: "null",
|
|
@@ -71685,7 +72090,7 @@ var GROUP_TEMPLATES = [
|
|
|
71685
72090
|
summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
|
|
71686
72091
|
role: DeviceRole.AmbientLightSensor,
|
|
71687
72092
|
outputCap: "ambient-light-sensor",
|
|
71688
|
-
outputField: "
|
|
72093
|
+
outputField: "illuminance",
|
|
71689
72094
|
quantity: "illuminance",
|
|
71690
72095
|
defaultAggregate: "mean",
|
|
71691
72096
|
empty: "null",
|
|
@@ -71696,7 +72101,7 @@ var GROUP_TEMPLATES = [
|
|
|
71696
72101
|
summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
|
|
71697
72102
|
role: DeviceRole.PowerSensor,
|
|
71698
72103
|
outputCap: "power-meter",
|
|
71699
|
-
outputField: "
|
|
72104
|
+
outputField: "power",
|
|
71700
72105
|
quantity: "power",
|
|
71701
72106
|
defaultAggregate: "sum",
|
|
71702
72107
|
empty: "omit",
|
|
@@ -71707,7 +72112,7 @@ var GROUP_TEMPLATES = [
|
|
|
71707
72112
|
summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
|
|
71708
72113
|
role: DeviceRole.EnergySensor,
|
|
71709
72114
|
outputCap: "power-meter",
|
|
71710
|
-
outputField: "
|
|
72115
|
+
outputField: "energy",
|
|
71711
72116
|
quantity: "energy",
|
|
71712
72117
|
defaultAggregate: "sum",
|
|
71713
72118
|
empty: "omit",
|
|
@@ -71718,7 +72123,7 @@ var GROUP_TEMPLATES = [
|
|
|
71718
72123
|
summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
|
|
71719
72124
|
role: DeviceRole.BatterySensor,
|
|
71720
72125
|
outputCap: "battery",
|
|
71721
|
-
outputField: "
|
|
72126
|
+
outputField: "level",
|
|
71722
72127
|
quantity: "percentage",
|
|
71723
72128
|
defaultAggregate: "mean",
|
|
71724
72129
|
empty: "null",
|
|
@@ -72296,7 +72701,18 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
|
|
|
72296
72701
|
kind: "mutation",
|
|
72297
72702
|
auth: "admin",
|
|
72298
72703
|
access: "view"
|
|
72299
|
-
})
|
|
72704
|
+
}), method(object({
|
|
72705
|
+
blockId: string().min(1),
|
|
72706
|
+
trace: AutomationRunTraceSchema.optional(),
|
|
72707
|
+
runStartedAt: number().optional(),
|
|
72708
|
+
keepTraces: number().int().min(1).max(100).optional()
|
|
72709
|
+
}), _void(), {
|
|
72710
|
+
kind: "mutation",
|
|
72711
|
+
auth: "admin"
|
|
72712
|
+
}), method(object({ blockId: string().min(1) }), AutomationStateDocSchema, { auth: "admin" }), method(object({ blockId: string().min(1) }), object({
|
|
72713
|
+
traces: array(AutomationRunTraceSchema),
|
|
72714
|
+
lastError: string().nullable()
|
|
72715
|
+
}), { auth: "admin" });
|
|
72300
72716
|
/**
|
|
72301
72717
|
* `custom-model-registry` — collection cap exposing operator-registered
|
|
72302
72718
|
* custom detection models. Each provider (today: `addon-model-studio`)
|
|
@@ -97684,51 +98100,6 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
|
|
|
97684
98100
|
kind: "mutation",
|
|
97685
98101
|
auth: "admin"
|
|
97686
98102
|
});
|
|
97687
|
-
new Set([
|
|
97688
|
-
{
|
|
97689
|
-
id: "person",
|
|
97690
|
-
name: "Person"
|
|
97691
|
-
},
|
|
97692
|
-
{
|
|
97693
|
-
id: "vehicle",
|
|
97694
|
-
name: "Vehicle"
|
|
97695
|
-
},
|
|
97696
|
-
{
|
|
97697
|
-
id: "animal",
|
|
97698
|
-
name: "Animal"
|
|
97699
|
-
},
|
|
97700
|
-
{
|
|
97701
|
-
id: "package",
|
|
97702
|
-
name: "Package"
|
|
97703
|
-
}
|
|
97704
|
-
].map((l) => l.id));
|
|
97705
|
-
var COCO_TO_MACRO = {
|
|
97706
|
-
mapping: {
|
|
97707
|
-
person: "person",
|
|
97708
|
-
bicycle: "vehicle",
|
|
97709
|
-
car: "vehicle",
|
|
97710
|
-
motorcycle: "vehicle",
|
|
97711
|
-
airplane: "vehicle",
|
|
97712
|
-
bus: "vehicle",
|
|
97713
|
-
train: "vehicle",
|
|
97714
|
-
truck: "vehicle",
|
|
97715
|
-
boat: "vehicle",
|
|
97716
|
-
bird: "animal",
|
|
97717
|
-
cat: "animal",
|
|
97718
|
-
dog: "animal",
|
|
97719
|
-
horse: "animal",
|
|
97720
|
-
sheep: "animal",
|
|
97721
|
-
cow: "animal",
|
|
97722
|
-
elephant: "animal",
|
|
97723
|
-
bear: "animal",
|
|
97724
|
-
zebra: "animal",
|
|
97725
|
-
giraffe: "animal",
|
|
97726
|
-
suitcase: "package",
|
|
97727
|
-
backpack: "package",
|
|
97728
|
-
handbag: "package"
|
|
97729
|
-
},
|
|
97730
|
-
preserveOriginal: false
|
|
97731
|
-
};
|
|
97732
98103
|
var AUDIO_MACRO_LABELS = [
|
|
97733
98104
|
{
|
|
97734
98105
|
id: "speech",
|
|
@@ -110295,9 +110666,9 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
|
|
|
110295
110666
|
*/
|
|
110296
110667
|
var AirQualitySensorStatusSchema = object({
|
|
110297
110668
|
/** Carbon dioxide concentration in ppm. */
|
|
110298
|
-
|
|
110669
|
+
co2: number().min(0).optional().meta(readingMeta("concentration-volume")),
|
|
110299
110670
|
/** Total volatile organic compounds in ppb. */
|
|
110300
|
-
|
|
110671
|
+
voc: number().min(0).optional().meta(readingMeta("concentration-ppb")),
|
|
110301
110672
|
/** Particulate matter ≤ 2.5 μm in µg/m³. */
|
|
110302
110673
|
pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
|
|
110303
110674
|
/** Particulate matter ≤ 10 μm in µg/m³. */
|
|
@@ -110330,12 +110701,12 @@ var airQualitySensorCapability = {
|
|
|
110330
110701
|
* History (D850): recorded series.
|
|
110331
110702
|
*/
|
|
110332
110703
|
history: { fields: {
|
|
110333
|
-
|
|
110704
|
+
co2: {
|
|
110334
110705
|
kind: "gauge",
|
|
110335
110706
|
unit: "ppm",
|
|
110336
110707
|
deadband: 5
|
|
110337
110708
|
},
|
|
110338
|
-
|
|
110709
|
+
voc: {
|
|
110339
110710
|
kind: "gauge",
|
|
110340
110711
|
unit: "ppb",
|
|
110341
110712
|
deadband: 5
|
|
@@ -110366,7 +110737,7 @@ var airQualitySensorCapability = {
|
|
|
110366
110737
|
*/
|
|
110367
110738
|
var AmbientLightSensorStatusSchema = object({
|
|
110368
110739
|
/** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
|
|
110369
|
-
|
|
110740
|
+
illuminance: number().min(0).nullable().meta(readingMeta("illuminance", {
|
|
110370
110741
|
precision: 0,
|
|
110371
110742
|
label: "Illuminance"
|
|
110372
110743
|
})),
|
|
@@ -110395,7 +110766,7 @@ var ambientLightSensorCapability = {
|
|
|
110395
110766
|
/**
|
|
110396
110767
|
* History (D850): recorded series. Wide range, so a RELATIVE deadband (10 %).
|
|
110397
110768
|
*/
|
|
110398
|
-
history: { fields: {
|
|
110769
|
+
history: { fields: { illuminance: {
|
|
110399
110770
|
kind: "gauge",
|
|
110400
110771
|
unit: "lx",
|
|
110401
110772
|
deadbandPct: 10,
|
|
@@ -110534,6 +110905,84 @@ var audioMetricsCapability = {
|
|
|
110534
110905
|
*/
|
|
110535
110906
|
history: "none"
|
|
110536
110907
|
};
|
|
110908
|
+
method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
|
|
110909
|
+
actionId: string().min(1),
|
|
110910
|
+
variables: record(string(), ActionVariableValueSchema).optional()
|
|
110911
|
+
}), _void(), {
|
|
110912
|
+
kind: "mutation",
|
|
110913
|
+
auth: "admin"
|
|
110914
|
+
}), method(object({ actionId: string().min(1) }), _void(), {
|
|
110915
|
+
kind: "mutation",
|
|
110916
|
+
auth: "admin"
|
|
110917
|
+
});
|
|
110918
|
+
/**
|
|
110919
|
+
* actions — the ONE surface for runnable actions, for the UI and for
|
|
110920
|
+
* automations. Provided by the core-blocks builtin on the hub.
|
|
110921
|
+
*
|
|
110922
|
+
* An action is a LIST ENTRY, not a device (D883): either internal (cap and wait
|
|
110923
|
+
* steps, kept in the hub's `actions` record kind) or declared by an addon over
|
|
110924
|
+
* `action-source`. `list` asks every `action-source` provider LIVE and overlays
|
|
110925
|
+
* the run status from the hub's `runs` ledger; there is no catalog here to
|
|
110926
|
+
* drift (D3).
|
|
110927
|
+
*
|
|
110928
|
+
* **Permissions follow the camera, as linked devices do.** An action is linked
|
|
110929
|
+
* to cameras by the operator (`setLinks`, D356: nothing links itself). A
|
|
110930
|
+
* non-admin may `get` / `run` an action only through a camera they may see that
|
|
110931
|
+
* links it: they name the camera (`deviceId`, a top-level field so the scope
|
|
110932
|
+
* gate sees it) and the provider refuses an action that camera does not link.
|
|
110933
|
+
* An admin, and an in-process caller (an automation block), may run any action.
|
|
110934
|
+
* Every refusal is typed (`[not-linked] …`) and logged.
|
|
110935
|
+
*
|
|
110936
|
+
* A second `run` of an action that is already running is REFUSED — traced and
|
|
110937
|
+
* logged — never queued.
|
|
110938
|
+
*/
|
|
110939
|
+
var ActionIdInput = object({ actionId: string().min(1) });
|
|
110940
|
+
var ActionLinksSchema = object({
|
|
110941
|
+
deviceId: number().int().nonnegative(),
|
|
110942
|
+
actionIds: array(string().min(1)).max(100)
|
|
110943
|
+
});
|
|
110944
|
+
method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
|
|
110945
|
+
actionId: string().min(1),
|
|
110946
|
+
deviceId: number().int().nonnegative().optional()
|
|
110947
|
+
}), object({ action: ActionEntrySchema.nullable() }), {
|
|
110948
|
+
role: "viewer",
|
|
110949
|
+
auth: "protected",
|
|
110950
|
+
kind: "query",
|
|
110951
|
+
caller: "required"
|
|
110952
|
+
}), method(object({
|
|
110953
|
+
actionId: string().min(1),
|
|
110954
|
+
variables: record(string(), ActionVariableValueSchema).optional(),
|
|
110955
|
+
wait: boolean().optional(),
|
|
110956
|
+
origin: ActionRunOriginSchema.optional(),
|
|
110957
|
+
deviceId: number().int().nonnegative().optional()
|
|
110958
|
+
}), ActionRunResultSchema, {
|
|
110959
|
+
role: "viewer",
|
|
110960
|
+
auth: "protected",
|
|
110961
|
+
kind: "mutation",
|
|
110962
|
+
caller: "required"
|
|
110963
|
+
}), method(ActionIdInput, _void(), {
|
|
110964
|
+
kind: "mutation",
|
|
110965
|
+
auth: "admin"
|
|
110966
|
+
}), method(ActionIdInput, object({ traces: array(AutomationRunTraceSchema) }), { auth: "admin" }), method(object({ action: ActionInputSchema }), object({ action: ActionEntrySchema }), {
|
|
110967
|
+
kind: "mutation",
|
|
110968
|
+
auth: "admin"
|
|
110969
|
+
}), method(object({
|
|
110970
|
+
actionId: string().min(1),
|
|
110971
|
+
action: ActionInputSchema.partial()
|
|
110972
|
+
}), object({ action: ActionEntrySchema }), {
|
|
110973
|
+
kind: "mutation",
|
|
110974
|
+
auth: "admin"
|
|
110975
|
+
}), method(ActionIdInput, _void(), {
|
|
110976
|
+
kind: "mutation",
|
|
110977
|
+
auth: "admin"
|
|
110978
|
+
}), method(object({ deviceId: number().int().nonnegative() }), array(ActionEntrySchema), {
|
|
110979
|
+
role: "viewer",
|
|
110980
|
+
auth: "protected",
|
|
110981
|
+
kind: "query"
|
|
110982
|
+
}), method(object({ deviceId: number().int().nonnegative() }), ActionLinksSchema, { auth: "admin" }), method(ActionLinksSchema, ActionLinksSchema, {
|
|
110983
|
+
kind: "mutation",
|
|
110984
|
+
auth: "admin"
|
|
110985
|
+
});
|
|
110537
110986
|
/**
|
|
110538
110987
|
* Automation-control cap. Models HA `automation.*` entities on
|
|
110539
110988
|
* `DeviceType.Automation`. An automation is a trigger+condition+
|
|
@@ -110630,7 +111079,7 @@ var BatteryStatusSchema = object({
|
|
|
110630
111079
|
* Consumers must SKIP a null rather than coerce it — `battery-band` already
|
|
110631
111080
|
* declines to band a non-finite reading, which is the correct shape.
|
|
110632
111081
|
*/
|
|
110633
|
-
|
|
111082
|
+
level: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
|
|
110634
111083
|
/**
|
|
110635
111084
|
* Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
|
|
110636
111085
|
* Reolink-specific for the Solar Panel 2 accessory (will become
|
|
@@ -110640,7 +111089,7 @@ var BatteryStatusSchema = object({
|
|
|
110640
111089
|
* `'unknown'` is the absence of an answer: the provider has not read the
|
|
110641
111090
|
* power fields yet, or the firmware reported neither. Before it existed,
|
|
110642
111091
|
* two unreadable fields produced `'none'`, and a camera nobody had reached
|
|
110643
|
-
* was drawn "on battery alone" — the same D315 defect `
|
|
111092
|
+
* was drawn "on battery alone" — the same D315 defect `level` closed
|
|
110644
111093
|
* by going nullable. Consumers SKIP it (no charger state published, no
|
|
110645
111094
|
* glyph, no HomeKit ChargingState) rather than coerce it either way.
|
|
110646
111095
|
*/
|
|
@@ -110664,7 +111113,7 @@ var BatteryStatusSchema = object({
|
|
|
110664
111113
|
* that only report charge. A second reading on this cap, not a second
|
|
110665
111114
|
* `numeric-sensor` on the same device.
|
|
110666
111115
|
*/
|
|
110667
|
-
|
|
111116
|
+
voltage: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
|
|
110668
111117
|
/**
|
|
110669
111118
|
* Ms epoch of the last time the device PROVED it was reachable — a
|
|
110670
111119
|
* completed firmware round-trip, an observed wake, or an inbound push
|
|
@@ -110698,7 +111147,7 @@ var BatteryStatusSchema = object({
|
|
|
110698
111147
|
/**
|
|
110699
111148
|
* True when the source is a BINARY low-battery indicator (HA
|
|
110700
111149
|
* `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
|
|
110701
|
-
* charge level — `
|
|
111150
|
+
* charge level — `level` is then a coarse stand-in (100 = normal,
|
|
110702
111151
|
* sub-threshold = low). UI MUST render "Normal"/"Low" instead of a
|
|
110703
111152
|
* misleading exact percentage. Absent/false → genuine 0–100 % reading.
|
|
110704
111153
|
*/
|
|
@@ -110718,15 +111167,15 @@ var BATTERY_DEVICE_TYPES = [
|
|
|
110718
111167
|
var batteryCustomize = {
|
|
110719
111168
|
version: 1,
|
|
110720
111169
|
targets: [{
|
|
110721
|
-
id: "
|
|
111170
|
+
id: "level",
|
|
110722
111171
|
cardinality: "scalar",
|
|
110723
111172
|
vocabulary: {
|
|
110724
111173
|
cap: "battery",
|
|
110725
|
-
stateField: "
|
|
111174
|
+
stateField: "level"
|
|
110726
111175
|
},
|
|
110727
111176
|
deviceTypes: BATTERY_DEVICE_TYPES,
|
|
110728
111177
|
apply: "add",
|
|
110729
|
-
path: "
|
|
111178
|
+
path: "level",
|
|
110730
111179
|
sources: [{
|
|
110731
111180
|
kind: "field",
|
|
110732
111181
|
valueKinds: ["number", "string"],
|
|
@@ -110820,7 +111269,7 @@ onStatusChanged: { data: object({
|
|
|
110820
111269
|
schema: BatteryStatusSchema,
|
|
110821
111270
|
kind: "push",
|
|
110822
111271
|
empty: {
|
|
110823
|
-
|
|
111272
|
+
level: null,
|
|
110824
111273
|
charging: "unknown",
|
|
110825
111274
|
sleeping: false,
|
|
110826
111275
|
lastUpdated: 0
|
|
@@ -110847,11 +111296,11 @@ onStatusChanged: { data: object({
|
|
|
110847
111296
|
* History (D850): recorded series. The generic capture replaces the `battery` series of `pipeline-analytics:stats-5m`.
|
|
110848
111297
|
*/
|
|
110849
111298
|
history: { fields: {
|
|
110850
|
-
|
|
111299
|
+
level: {
|
|
110851
111300
|
kind: "gauge",
|
|
110852
111301
|
unit: "%"
|
|
110853
111302
|
},
|
|
110854
|
-
|
|
111303
|
+
voltage: {
|
|
110855
111304
|
kind: "gauge",
|
|
110856
111305
|
unit: "V"
|
|
110857
111306
|
}
|
|
@@ -110911,14 +111360,14 @@ var binaryCapability = {
|
|
|
110911
111360
|
* controls to this single-method surface.
|
|
110912
111361
|
*
|
|
110913
111362
|
* The cap is intentionally minimal: a single `setBrightness({deviceId,
|
|
110914
|
-
*
|
|
111363
|
+
* level})` mutation plus the auto-injected `getStatus`. Drivers
|
|
110915
111364
|
* that expose richer controls (color temperature, scenes, schedules)
|
|
110916
111365
|
* should surface those via the device's `getSettingsUISchema()`
|
|
110917
111366
|
* instead of bloating this cap.
|
|
110918
111367
|
*/
|
|
110919
111368
|
var BrightnessStatusSchema = object({
|
|
110920
111369
|
/** Current level as 0..100 inclusive. Firmware-reported. */
|
|
110921
|
-
|
|
111370
|
+
level: number().min(0).max(100).meta(readingMeta("percentage")),
|
|
110922
111371
|
/** Ms epoch of the last operator-driven change. Useful for UI freshness. */
|
|
110923
111372
|
lastChangedAt: number()
|
|
110924
111373
|
});
|
|
@@ -110930,7 +111379,7 @@ var brightnessCapability = {
|
|
|
110930
111379
|
deviceTypes: [DeviceType.Light],
|
|
110931
111380
|
methods: { setBrightness: method(object({
|
|
110932
111381
|
deviceId: number().int().nonnegative(),
|
|
110933
|
-
|
|
111382
|
+
level: number().min(0).max(100)
|
|
110934
111383
|
}), _void(), {
|
|
110935
111384
|
kind: "mutation",
|
|
110936
111385
|
auth: "protected"
|
|
@@ -110943,7 +111392,7 @@ var brightnessCapability = {
|
|
|
110943
111392
|
*/
|
|
110944
111393
|
onBrightnessChanged: { data: object({
|
|
110945
111394
|
deviceId: number(),
|
|
110946
|
-
|
|
111395
|
+
level: number().min(0).max(100),
|
|
110947
111396
|
lastChangedAt: number()
|
|
110948
111397
|
}) } },
|
|
110949
111398
|
status: {
|
|
@@ -110966,7 +111415,7 @@ onBrightnessChanged: { data: object({
|
|
|
110966
111415
|
/**
|
|
110967
111416
|
* History (D850): recorded series. Actuator tier: level keeps every change.
|
|
110968
111417
|
*/
|
|
110969
|
-
history: { fields: {
|
|
111418
|
+
history: { fields: { level: {
|
|
110970
111419
|
kind: "gauge",
|
|
110971
111420
|
unit: "%",
|
|
110972
111421
|
minIntervalMs: 0
|
|
@@ -111580,11 +112029,11 @@ var ColorInputSchema = discriminatedUnion("mode", [
|
|
|
111580
112029
|
}),
|
|
111581
112030
|
object({
|
|
111582
112031
|
mode: literal("mired"),
|
|
111583
|
-
|
|
112032
|
+
colorTemperature: number().int().min(50).max(1e3)
|
|
111584
112033
|
})
|
|
111585
112034
|
]);
|
|
111586
112035
|
var ColorStatusSchema = object({
|
|
111587
|
-
/** Active color mode — which of `rgb` / `hsv` / `
|
|
112036
|
+
/** Active color mode — which of `rgb` / `hsv` / `colorTemperature` reflects
|
|
111588
112037
|
* the bulb's current state. */
|
|
111589
112038
|
mode: _enum([
|
|
111590
112039
|
"rgb",
|
|
@@ -111596,7 +112045,7 @@ var ColorStatusSchema = object({
|
|
|
111596
112045
|
/** Populated when `mode === 'hsv'`. */
|
|
111597
112046
|
hsv: HsvTripletSchema.optional(),
|
|
111598
112047
|
/** Populated when `mode === 'mired'`. */
|
|
111599
|
-
|
|
112048
|
+
colorTemperature: number().int().optional().meta(readingMeta("colour-temperature")),
|
|
111600
112049
|
/** Ms epoch of the last operator-driven change. */
|
|
111601
112050
|
lastChangedAt: number()
|
|
111602
112051
|
});
|
|
@@ -111628,7 +112077,7 @@ onColorChanged: { data: object({
|
|
|
111628
112077
|
]),
|
|
111629
112078
|
rgb: RgbTripletSchema.optional(),
|
|
111630
112079
|
hsv: HsvTripletSchema.optional(),
|
|
111631
|
-
|
|
112080
|
+
colorTemperature: number().int().optional(),
|
|
111632
112081
|
lastChangedAt: number()
|
|
111633
112082
|
}) } },
|
|
111634
112083
|
status: {
|
|
@@ -112517,11 +112966,11 @@ var DishwasherStatusSchema = object({
|
|
|
112517
112966
|
/** Feature keys of the programs the appliance currently offers — drives the UI selector. */
|
|
112518
112967
|
availablePrograms: array(string()),
|
|
112519
112968
|
/** Seconds remaining on the active cycle, or null when not running / not known. */
|
|
112520
|
-
|
|
112969
|
+
remaining: number().int().nonnegative().nullable().meta(readingMeta("duration")),
|
|
112521
112970
|
/** Seconds the delayed start is set to (time until the cycle starts), or null. */
|
|
112522
|
-
|
|
112971
|
+
delayedStart: number().int().nonnegative().nullable().meta(readingMeta("duration")),
|
|
112523
112972
|
/** Cycle completion 0..100, or null when not applicable. */
|
|
112524
|
-
|
|
112973
|
+
progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
112525
112974
|
/** True when the door is reported open; null when the door state is not known. */
|
|
112526
112975
|
doorOpen: boolean().nullable(),
|
|
112527
112976
|
/** True when the door is latched (locked) during a cycle; null when not reported. */
|
|
@@ -112740,14 +113189,14 @@ var dishwasherCapability = {
|
|
|
112740
113189
|
runtimeState: DishwasherStatusSchema,
|
|
112741
113190
|
durability: "session",
|
|
112742
113191
|
history: { fields: {
|
|
112743
|
-
|
|
113192
|
+
progress: {
|
|
112744
113193
|
kind: "gauge",
|
|
112745
113194
|
unit: "%",
|
|
112746
113195
|
deadband: 2
|
|
112747
113196
|
},
|
|
112748
113197
|
state: { kind: "enum" },
|
|
112749
113198
|
operationState: { kind: "enum" },
|
|
112750
|
-
|
|
113199
|
+
remaining: {
|
|
112751
113200
|
kind: "gauge",
|
|
112752
113201
|
unit: "s",
|
|
112753
113202
|
minIntervalMs: 6e4
|
|
@@ -113338,7 +113787,7 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
|
|
|
113338
113787
|
var FanControlStatusSchema = object({
|
|
113339
113788
|
/** Active speed as 0..100 inclusive. Null when the device has no
|
|
113340
113789
|
* speed surface (single-speed fan). */
|
|
113341
|
-
|
|
113790
|
+
speed: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
113342
113791
|
/** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
|
|
113343
113792
|
* a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
|
|
113344
113793
|
percentageStep: number().positive().optional().meta(readingMeta("percentage")),
|
|
@@ -113365,7 +113814,7 @@ var fanControlCapability = {
|
|
|
113365
113814
|
methods: {
|
|
113366
113815
|
setPercentage: method(object({
|
|
113367
113816
|
deviceId: number().int().nonnegative(),
|
|
113368
|
-
|
|
113817
|
+
speed: number().min(0).max(100)
|
|
113369
113818
|
}), _void(), {
|
|
113370
113819
|
kind: "mutation",
|
|
113371
113820
|
auth: "protected"
|
|
@@ -113398,7 +113847,7 @@ var fanControlCapability = {
|
|
|
113398
113847
|
},
|
|
113399
113848
|
/**
|
|
113400
113849
|
* Runtime-state slice — mirrored by the kernel. UI fan speed
|
|
113401
|
-
* sliders read `
|
|
113850
|
+
* sliders read `speed` for live updates.
|
|
113402
113851
|
*/
|
|
113403
113852
|
runtimeState: FanControlStatusSchema,
|
|
113404
113853
|
/**
|
|
@@ -113412,7 +113861,7 @@ var fanControlCapability = {
|
|
|
113412
113861
|
* History (D850): recorded series. Actuator tier: speed keeps every change.
|
|
113413
113862
|
*/
|
|
113414
113863
|
history: { fields: {
|
|
113415
|
-
|
|
113864
|
+
speed: {
|
|
113416
113865
|
kind: "gauge",
|
|
113417
113866
|
unit: "%",
|
|
113418
113867
|
minIntervalMs: 0
|
|
@@ -113857,7 +114306,7 @@ var humidifierCapability = {
|
|
|
113857
114306
|
*/
|
|
113858
114307
|
var HumiditySensorStatusSchema = object({
|
|
113859
114308
|
/** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
|
|
113860
|
-
|
|
114309
|
+
humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
|
|
113861
114310
|
precision: 0,
|
|
113862
114311
|
label: "Humidity"
|
|
113863
114312
|
})),
|
|
@@ -113886,7 +114335,7 @@ var humiditySensorCapability = {
|
|
|
113886
114335
|
/**
|
|
113887
114336
|
* History (D850): recorded series.
|
|
113888
114337
|
*/
|
|
113889
|
-
history: { fields: {
|
|
114338
|
+
history: { fields: { humidity: {
|
|
113890
114339
|
kind: "gauge",
|
|
113891
114340
|
unit: "%",
|
|
113892
114341
|
deadband: .5
|
|
@@ -114526,7 +114975,7 @@ var LawnMowerControlStatusSchema = object({
|
|
|
114526
114975
|
batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
114527
114976
|
/** 0..100 mowing-completion percentage of the current task, or null when no
|
|
114528
114977
|
* task is active / progress is unavailable. */
|
|
114529
|
-
|
|
114978
|
+
progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
114530
114979
|
/** Current device/event code (dynamic — mostly status, sometimes an error),
|
|
114531
114980
|
* or null when unknown. */
|
|
114532
114981
|
currentCode: number().nullable().meta(readingMeta("none")),
|
|
@@ -115026,7 +115475,7 @@ var lockControlCapability = {
|
|
|
115026
115475
|
volatileStateFields: ["lastChangedAt"]
|
|
115027
115476
|
};
|
|
115028
115477
|
/**
|
|
115029
|
-
* A monotonic total between resets (D944). Energy uses `power-meter.
|
|
115478
|
+
* A monotonic total between resets (D944). Energy uses `power-meter.energy`
|
|
115030
115479
|
* so dashboards keep one series; every other instantaneous reading lands here.
|
|
115031
115480
|
* `value` is the time integral in the source quantity's accumulated base
|
|
115032
115481
|
* (base-unit seconds, or kWh on the power meter). `null` is a gap, never zero.
|
|
@@ -115120,10 +115569,10 @@ var MediaPlayerStatusSchema = object({
|
|
|
115120
115569
|
currentMedia: MediaInfoSchema.nullable(),
|
|
115121
115570
|
/** Current playback position in ms. Null when not seekable or
|
|
115122
115571
|
* nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
|
|
115123
|
-
|
|
115572
|
+
position: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
|
|
115124
115573
|
/** Total duration of the current media in ms. Null when unknown
|
|
115125
115574
|
* (live stream) or nothing is playing. */
|
|
115126
|
-
|
|
115575
|
+
duration: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
|
|
115127
115576
|
/** Shuffle toggle. Null when no shuffle surface.
|
|
115128
115577
|
* Pair with `DeviceFeature.MediaPlayerShuffle`. */
|
|
115129
115578
|
shuffle: boolean().nullable(),
|
|
@@ -115162,7 +115611,7 @@ var mediaPlayerCapability = {
|
|
|
115162
115611
|
}),
|
|
115163
115612
|
seek: method(object({
|
|
115164
115613
|
deviceId: number().int().nonnegative(),
|
|
115165
|
-
|
|
115614
|
+
position: number().int().nonnegative()
|
|
115166
115615
|
}), _void(), {
|
|
115167
115616
|
kind: "mutation",
|
|
115168
115617
|
auth: "protected"
|
|
@@ -115234,7 +115683,7 @@ var mediaPlayerCapability = {
|
|
|
115234
115683
|
/**
|
|
115235
115684
|
* History (D850): volume (a level an operator or an automation sets; the
|
|
115236
115685
|
* default 10 s floor folds a slider drag into its end value) and mute.
|
|
115237
|
-
* `
|
|
115686
|
+
* `position` is not recorded: it moves continuously while playing.
|
|
115238
115687
|
*/
|
|
115239
115688
|
history: { fields: {
|
|
115240
115689
|
volumeLevel: {
|
|
@@ -116333,9 +116782,9 @@ var NetworkLinkStatusSchema = object({
|
|
|
116333
116782
|
* one whose reading has not landed must not be drawn at 0 %. Consumers
|
|
116334
116783
|
* SKIP a null rather than coerce it.
|
|
116335
116784
|
*/
|
|
116336
|
-
|
|
116785
|
+
signal: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
116337
116786
|
/** Raw received signal strength in dBm, when the firmware reports one. */
|
|
116338
|
-
|
|
116787
|
+
rssi: number().optional().meta(readingMeta("signal-level")),
|
|
116339
116788
|
/** Network name of a wireless link, when the firmware reports it. */
|
|
116340
116789
|
ssid: string().optional(),
|
|
116341
116790
|
/** Ms epoch of the last observation. Lets consumers reason about freshness. */
|
|
@@ -116344,7 +116793,7 @@ var NetworkLinkStatusSchema = object({
|
|
|
116344
116793
|
/** The slice a provider seeds before its first read: nothing is known yet. */
|
|
116345
116794
|
var NETWORK_LINK_UNKNOWN = {
|
|
116346
116795
|
type: "unknown",
|
|
116347
|
-
|
|
116796
|
+
signal: null,
|
|
116348
116797
|
lastUpdated: 0
|
|
116349
116798
|
};
|
|
116350
116799
|
/**
|
|
@@ -116405,13 +116854,13 @@ onStatusChanged: { data: object({
|
|
|
116405
116854
|
* History (D850): recorded series.
|
|
116406
116855
|
*/
|
|
116407
116856
|
history: { fields: {
|
|
116408
|
-
|
|
116857
|
+
signal: {
|
|
116409
116858
|
kind: "gauge",
|
|
116410
116859
|
unit: "%",
|
|
116411
116860
|
deadband: 3,
|
|
116412
116861
|
minIntervalMs: 6e4
|
|
116413
116862
|
},
|
|
116414
|
-
|
|
116863
|
+
rssi: {
|
|
116415
116864
|
kind: "gauge",
|
|
116416
116865
|
unit: "dBm",
|
|
116417
116866
|
deadband: 2,
|
|
@@ -117731,16 +118180,16 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
|
|
|
117731
118180
|
* in real-world devices.
|
|
117732
118181
|
*/
|
|
117733
118182
|
var PowerMeterStatusSchema = object({
|
|
117734
|
-
/** Instantaneous power draw
|
|
117735
|
-
|
|
118183
|
+
/** Instantaneous power draw (watts). */
|
|
118184
|
+
power: number().optional().meta(readingMeta("power", { label: "Power" })),
|
|
117736
118185
|
/** Cumulative energy in kilowatt-hours since the meter was reset. */
|
|
117737
|
-
|
|
117738
|
-
/** Voltage
|
|
117739
|
-
|
|
118186
|
+
energy: number().optional().meta(readingMeta("energy", { label: "Energy" })),
|
|
118187
|
+
/** Voltage (volts). */
|
|
118188
|
+
voltage: number().optional().meta(readingMeta("voltage", { label: "Voltage" })),
|
|
117740
118189
|
/** Current in amperes. */
|
|
117741
|
-
|
|
118190
|
+
current: number().optional().meta(readingMeta("current", { label: "Current" })),
|
|
117742
118191
|
/** Apparent power in volt-amperes. */
|
|
117743
|
-
|
|
118192
|
+
apparentPower: number().optional().meta(readingMeta("apparent-power")),
|
|
117744
118193
|
/** Power factor (0..1) if reported. */
|
|
117745
118194
|
powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
|
|
117746
118195
|
/** Ms epoch when the slice was last updated. */
|
|
@@ -117798,14 +118247,14 @@ function powerTarget(id, unit) {
|
|
|
117798
118247
|
advancedExpression: true
|
|
117799
118248
|
};
|
|
117800
118249
|
}
|
|
117801
|
-
/** "Power and energy" extension:
|
|
118250
|
+
/** "Power and energy" extension: power, energy, voltage and current from other devices (D951). */
|
|
117802
118251
|
var powerMeterCustomize = {
|
|
117803
118252
|
version: 1,
|
|
117804
118253
|
targets: [
|
|
117805
|
-
powerTarget("
|
|
117806
|
-
powerTarget("
|
|
117807
|
-
powerTarget("
|
|
117808
|
-
powerTarget("
|
|
118254
|
+
powerTarget("power", "W"),
|
|
118255
|
+
powerTarget("energy", "kWh"),
|
|
118256
|
+
powerTarget("voltage", "V"),
|
|
118257
|
+
powerTarget("current", "A")
|
|
117809
118258
|
]
|
|
117810
118259
|
};
|
|
117811
118260
|
var powerMeterCapability = {
|
|
@@ -117821,41 +118270,41 @@ var powerMeterCapability = {
|
|
|
117821
118270
|
},
|
|
117822
118271
|
runtimeState: PowerMeterStatusSchema,
|
|
117823
118272
|
/**
|
|
117824
|
-
* Runtime-state durability: **restored** — as `numeric-sensor`; `
|
|
118273
|
+
* Runtime-state durability: **restored** — as `numeric-sensor`; `energy` is an accumulator whose restored value is the baseline.
|
|
117825
118274
|
*
|
|
117826
118275
|
* See `RuntimeStateDurability`. Enforced by
|
|
117827
118276
|
* `scripts/check-runtime-state-durability.ts`.
|
|
117828
118277
|
*/
|
|
117829
118278
|
durability: "restored",
|
|
117830
118279
|
/**
|
|
117831
|
-
* History (D850): recorded series. Gauges are floored at 30 s; `
|
|
118280
|
+
* History (D850): recorded series. Gauges are floored at 30 s; `energy` is the one monotonic meter (counter).
|
|
117832
118281
|
*/
|
|
117833
118282
|
history: { fields: {
|
|
117834
|
-
|
|
118283
|
+
power: {
|
|
117835
118284
|
kind: "gauge",
|
|
117836
118285
|
unit: "W",
|
|
117837
118286
|
deadband: 1,
|
|
117838
118287
|
minIntervalMs: 3e4
|
|
117839
118288
|
},
|
|
117840
|
-
|
|
118289
|
+
energy: {
|
|
117841
118290
|
kind: "counter",
|
|
117842
118291
|
unit: "kWh",
|
|
117843
118292
|
deadband: .01,
|
|
117844
118293
|
minIntervalMs: 6e4
|
|
117845
118294
|
},
|
|
117846
|
-
|
|
118295
|
+
voltage: {
|
|
117847
118296
|
kind: "gauge",
|
|
117848
118297
|
unit: "V",
|
|
117849
118298
|
deadband: 1,
|
|
117850
118299
|
minIntervalMs: 3e4
|
|
117851
118300
|
},
|
|
117852
|
-
|
|
118301
|
+
current: {
|
|
117853
118302
|
kind: "gauge",
|
|
117854
118303
|
unit: "A",
|
|
117855
118304
|
deadband: .05,
|
|
117856
118305
|
minIntervalMs: 3e4
|
|
117857
118306
|
},
|
|
117858
|
-
|
|
118307
|
+
apparentPower: {
|
|
117859
118308
|
kind: "gauge",
|
|
117860
118309
|
unit: "VA",
|
|
117861
118310
|
deadband: 1,
|
|
@@ -117905,9 +118354,6 @@ var PresenceStatusSchema = object({
|
|
|
117905
118354
|
location: string().nullable(),
|
|
117906
118355
|
/** GPS coordinates when available. Null otherwise. */
|
|
117907
118356
|
gps: GpsLocationSchema.nullable(),
|
|
117908
|
-
/** Optional battery level of the tracking device (0..100). Null
|
|
117909
|
-
* when not reported. */
|
|
117910
|
-
batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
117911
118357
|
/** Ms epoch when the slice was last updated. */
|
|
117912
118358
|
lastChangedAt: number()
|
|
117913
118359
|
});
|
|
@@ -117936,14 +118382,7 @@ var presenceCapability = {
|
|
|
117936
118382
|
* `scripts/check-runtime-state-durability.ts`.
|
|
117937
118383
|
*/
|
|
117938
118384
|
durability: "restored",
|
|
117939
|
-
|
|
117940
|
-
* History (D850): recorded series.
|
|
117941
|
-
*/
|
|
117942
|
-
history: { fields: { batteryPercent: {
|
|
117943
|
-
kind: "gauge",
|
|
117944
|
-
unit: "%",
|
|
117945
|
-
deadband: 1
|
|
117946
|
-
} } },
|
|
118385
|
+
history: "none",
|
|
117947
118386
|
/** Clock fields: written, but excluded from the compare that decides
|
|
117948
118387
|
* whether persisting is worth a SQLite commit. */
|
|
117949
118388
|
volatileStateFields: ["lastChangedAt"]
|
|
@@ -117958,7 +118397,7 @@ var presenceCapability = {
|
|
|
117958
118397
|
*/
|
|
117959
118398
|
var PressureSensorStatusSchema = object({
|
|
117960
118399
|
/** Current pressure in hPa. `null` when the sample was refused (D393). */
|
|
117961
|
-
|
|
118400
|
+
pressure: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
|
|
117962
118401
|
/** Ms epoch when the slice was last updated. */
|
|
117963
118402
|
lastFetchedAt: number()
|
|
117964
118403
|
});
|
|
@@ -117984,7 +118423,7 @@ var pressureSensorCapability = {
|
|
|
117984
118423
|
/**
|
|
117985
118424
|
* History (D850): recorded series.
|
|
117986
118425
|
*/
|
|
117987
|
-
history: { fields: {
|
|
118426
|
+
history: { fields: { pressure: {
|
|
117988
118427
|
kind: "gauge",
|
|
117989
118428
|
unit: "hPa",
|
|
117990
118429
|
deadband: .1
|
|
@@ -121691,7 +122130,7 @@ var tamperCapability = {
|
|
|
121691
122130
|
*/
|
|
121692
122131
|
var TemperatureSensorStatusSchema = object({
|
|
121693
122132
|
/** Current temperature in Celsius. `null` when the sample was refused (D393). */
|
|
121694
|
-
|
|
122133
|
+
temperature: number().nullable().meta(readingMeta("temperature", {
|
|
121695
122134
|
precision: 1,
|
|
121696
122135
|
label: "Temperature"
|
|
121697
122136
|
})),
|
|
@@ -121720,7 +122159,7 @@ var temperatureSensorCapability = {
|
|
|
121720
122159
|
/**
|
|
121721
122160
|
* History (D850): recorded series.
|
|
121722
122161
|
*/
|
|
121723
|
-
history: { fields: {
|
|
122162
|
+
history: { fields: { temperature: {
|
|
121724
122163
|
kind: "gauge",
|
|
121725
122164
|
unit: "°C",
|
|
121726
122165
|
deadband: .1
|
|
@@ -122017,7 +122456,7 @@ var VacuumControlStatusSchema = object({
|
|
|
122017
122456
|
/** Speed tokens the hardware accepts — drives the UI selector. */
|
|
122018
122457
|
availableFanSpeeds: array(string()),
|
|
122019
122458
|
/** 0..100 cleaning-completion percentage of the current task, or null. */
|
|
122020
|
-
|
|
122459
|
+
progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
122021
122460
|
/** Current error code (0 / null = no error). */
|
|
122022
122461
|
errorCode: number().nullable().meta(readingMeta("none")),
|
|
122023
122462
|
/** Human label for {@link errorCode}, or null when none / undecodable. */
|
|
@@ -124099,7 +124538,7 @@ var BaseDevice = class {
|
|
|
124099
124538
|
* `device-local-state.ts`. Drivers access via:
|
|
124100
124539
|
*
|
|
124101
124540
|
* `this.state.battery.sleeping = true` // patches the battery slice
|
|
124102
|
-
* `const pct = this.state.battery.
|
|
124541
|
+
* `const pct = this.state.battery.level` // reads the battery slice
|
|
124103
124542
|
* `this.state.deviceStatus.online = true` // mirrors `markOnline(true)`
|
|
124104
124543
|
*
|
|
124105
124544
|
* Adding a new cap with `runtimeState:` automatically extends this
|
|
@@ -124139,7 +124578,7 @@ var BaseDevice = class {
|
|
|
124139
124578
|
* shape; drivers don't redeclare it, they just write through.
|
|
124140
124579
|
*
|
|
124141
124580
|
* Read: `this.runtimeState.getCapState('battery')` →
|
|
124142
|
-
* `{
|
|
124581
|
+
* `{level, charging, sleeping, lastUpdated}` for any
|
|
124143
124582
|
* provider that registers `batteryCapability`.
|
|
124144
124583
|
* Write: `this.runtimeState.setCapState('battery', { … })`.
|
|
124145
124584
|
*
|
|
@@ -125329,6 +125768,104 @@ Object.freeze({
|
|
|
125329
125768
|
access: "create",
|
|
125330
125769
|
role: "admin"
|
|
125331
125770
|
},
|
|
125771
|
+
"actions.create": {
|
|
125772
|
+
capName: "actions",
|
|
125773
|
+
capScope: "system",
|
|
125774
|
+
addonId: null,
|
|
125775
|
+
access: "create",
|
|
125776
|
+
role: "admin"
|
|
125777
|
+
},
|
|
125778
|
+
"actions.delete": {
|
|
125779
|
+
capName: "actions",
|
|
125780
|
+
capScope: "system",
|
|
125781
|
+
addonId: null,
|
|
125782
|
+
access: "delete",
|
|
125783
|
+
role: "admin"
|
|
125784
|
+
},
|
|
125785
|
+
"actions.get": {
|
|
125786
|
+
capName: "actions",
|
|
125787
|
+
capScope: "system",
|
|
125788
|
+
addonId: null,
|
|
125789
|
+
access: "view",
|
|
125790
|
+
role: "viewer"
|
|
125791
|
+
},
|
|
125792
|
+
"actions.getLinks": {
|
|
125793
|
+
capName: "actions",
|
|
125794
|
+
capScope: "system",
|
|
125795
|
+
addonId: null,
|
|
125796
|
+
access: "view",
|
|
125797
|
+
role: "admin"
|
|
125798
|
+
},
|
|
125799
|
+
"actions.getTraces": {
|
|
125800
|
+
capName: "actions",
|
|
125801
|
+
capScope: "system",
|
|
125802
|
+
addonId: null,
|
|
125803
|
+
access: "view",
|
|
125804
|
+
role: "admin"
|
|
125805
|
+
},
|
|
125806
|
+
"actions.list": {
|
|
125807
|
+
capName: "actions",
|
|
125808
|
+
capScope: "system",
|
|
125809
|
+
addonId: null,
|
|
125810
|
+
access: "view",
|
|
125811
|
+
role: "admin"
|
|
125812
|
+
},
|
|
125813
|
+
"actions.listForCamera": {
|
|
125814
|
+
capName: "actions",
|
|
125815
|
+
capScope: "system",
|
|
125816
|
+
addonId: null,
|
|
125817
|
+
access: "view",
|
|
125818
|
+
role: "viewer"
|
|
125819
|
+
},
|
|
125820
|
+
"actions.run": {
|
|
125821
|
+
capName: "actions",
|
|
125822
|
+
capScope: "system",
|
|
125823
|
+
addonId: null,
|
|
125824
|
+
access: "create",
|
|
125825
|
+
role: "viewer"
|
|
125826
|
+
},
|
|
125827
|
+
"actions.setLinks": {
|
|
125828
|
+
capName: "actions",
|
|
125829
|
+
capScope: "system",
|
|
125830
|
+
addonId: null,
|
|
125831
|
+
access: "create",
|
|
125832
|
+
role: "admin"
|
|
125833
|
+
},
|
|
125834
|
+
"actions.stop": {
|
|
125835
|
+
capName: "actions",
|
|
125836
|
+
capScope: "system",
|
|
125837
|
+
addonId: null,
|
|
125838
|
+
access: "create",
|
|
125839
|
+
role: "admin"
|
|
125840
|
+
},
|
|
125841
|
+
"actions.update": {
|
|
125842
|
+
capName: "actions",
|
|
125843
|
+
capScope: "system",
|
|
125844
|
+
addonId: null,
|
|
125845
|
+
access: "create",
|
|
125846
|
+
role: "admin"
|
|
125847
|
+
},
|
|
125848
|
+
"actionSource.listActions": {
|
|
125849
|
+
capName: "action-source",
|
|
125850
|
+
capScope: "system",
|
|
125851
|
+
addonId: null,
|
|
125852
|
+
access: "view",
|
|
125853
|
+
role: "admin"
|
|
125854
|
+
},
|
|
125855
|
+
"actionSource.runAction": {
|
|
125856
|
+
capName: "action-source",
|
|
125857
|
+
capScope: "system",
|
|
125858
|
+
addonId: null,
|
|
125859
|
+
access: "create",
|
|
125860
|
+
role: "admin"
|
|
125861
|
+
},
|
|
125862
|
+
"actionSource.stopAction": {
|
|
125863
|
+
capName: "action-source",
|
|
125864
|
+
capScope: "system",
|
|
125865
|
+
addonId: null,
|
|
125866
|
+
access: "create",
|
|
125867
|
+
role: "admin"
|
|
125868
|
+
},
|
|
125332
125869
|
"addonPages.listPages": {
|
|
125333
125870
|
capName: "addon-pages",
|
|
125334
125871
|
capScope: "system",
|
|
@@ -126610,6 +127147,13 @@ Object.freeze({
|
|
|
126610
127147
|
access: "view",
|
|
126611
127148
|
role: "admin"
|
|
126612
127149
|
},
|
|
127150
|
+
"coreBlocks.getAutomationRunState": {
|
|
127151
|
+
capName: "core-blocks",
|
|
127152
|
+
capScope: "system",
|
|
127153
|
+
addonId: null,
|
|
127154
|
+
access: "view",
|
|
127155
|
+
role: "admin"
|
|
127156
|
+
},
|
|
126613
127157
|
"coreBlocks.getBlockSettings": {
|
|
126614
127158
|
capName: "core-blocks",
|
|
126615
127159
|
capScope: "system",
|
|
@@ -126631,6 +127175,13 @@ Object.freeze({
|
|
|
126631
127175
|
access: "view",
|
|
126632
127176
|
role: "admin"
|
|
126633
127177
|
},
|
|
127178
|
+
"coreBlocks.getTraces": {
|
|
127179
|
+
capName: "core-blocks",
|
|
127180
|
+
capScope: "system",
|
|
127181
|
+
addonId: null,
|
|
127182
|
+
access: "view",
|
|
127183
|
+
role: "admin"
|
|
127184
|
+
},
|
|
126634
127185
|
"coreBlocks.getTypeDefs": {
|
|
126635
127186
|
capName: "core-blocks",
|
|
126636
127187
|
capScope: "system",
|
|
@@ -126666,6 +127217,13 @@ Object.freeze({
|
|
|
126666
127217
|
access: "create",
|
|
126667
127218
|
role: "admin"
|
|
126668
127219
|
},
|
|
127220
|
+
"coreBlocks.reportAutomationRun": {
|
|
127221
|
+
capName: "core-blocks",
|
|
127222
|
+
capScope: "system",
|
|
127223
|
+
addonId: null,
|
|
127224
|
+
access: "create",
|
|
127225
|
+
role: "admin"
|
|
127226
|
+
},
|
|
126669
127227
|
"coreBlocks.restart": {
|
|
126670
127228
|
capName: "core-blocks",
|
|
126671
127229
|
capScope: "system",
|
|
@@ -135239,7 +135797,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
135239
135797
|
label: t("Full brightness", "Luminosità massima"),
|
|
135240
135798
|
phrase: t("Set {device} to full brightness", "Porta {device} alla massima luminosità"),
|
|
135241
135799
|
icon: "sun",
|
|
135242
|
-
args: {
|
|
135800
|
+
args: { level: 100 }
|
|
135243
135801
|
},
|
|
135244
135802
|
{
|
|
135245
135803
|
id: "brightness.half",
|
|
@@ -135247,7 +135805,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
135247
135805
|
label: t("Half brightness", "Luminosità a metà"),
|
|
135248
135806
|
phrase: t("Dim {device} to half", "Abbassa {device} a metà luminosità"),
|
|
135249
135807
|
icon: "sun-dim",
|
|
135250
|
-
args: {
|
|
135808
|
+
args: { level: 50 }
|
|
135251
135809
|
},
|
|
135252
135810
|
{
|
|
135253
135811
|
id: "brightness.off",
|
|
@@ -135255,16 +135813,16 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
135255
135813
|
label: t("Brightness off", "Luminosità a zero"),
|
|
135256
135814
|
phrase: t("Dim {device} to zero", "Abbassa {device} a zero"),
|
|
135257
135815
|
icon: "lightbulb-off",
|
|
135258
|
-
args: {
|
|
135816
|
+
args: { level: 0 }
|
|
135259
135817
|
},
|
|
135260
135818
|
{
|
|
135261
135819
|
id: "brightness.set",
|
|
135262
135820
|
method: "setBrightness",
|
|
135263
135821
|
label: t("Set brightness", "Imposta la luminosità"),
|
|
135264
|
-
phrase: t("Set the brightness of {device} to {
|
|
135822
|
+
phrase: t("Set the brightness of {device} to {level}", "Imposta la luminosità di {device} a {level}"),
|
|
135265
135823
|
icon: "sun-medium",
|
|
135266
135824
|
openArgs: [{
|
|
135267
|
-
key: "
|
|
135825
|
+
key: "level",
|
|
135268
135826
|
label: PERCENT,
|
|
135269
135827
|
offer: {
|
|
135270
135828
|
kind: "number",
|
|
@@ -135278,13 +135836,13 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
135278
135836
|
],
|
|
135279
135837
|
state: [{
|
|
135280
135838
|
kind: "numeric",
|
|
135281
|
-
field: "
|
|
135839
|
+
field: "level",
|
|
135282
135840
|
quantity: t("the brightness", "la luminosità"),
|
|
135283
135841
|
label: t("Brightness", "Luminosità"),
|
|
135284
135842
|
unit: "%",
|
|
135285
135843
|
icon: "sun"
|
|
135286
135844
|
}],
|
|
135287
|
-
events: { onBrightnessChanged: { notOperatorFacing: "the `
|
|
135845
|
+
events: { onBrightnessChanged: { notOperatorFacing: "the `level` state trigger reads the same change" } }
|
|
135288
135846
|
},
|
|
135289
135847
|
color: {
|
|
135290
135848
|
scope: "device",
|
|
@@ -135298,7 +135856,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
135298
135856
|
icon: "sun",
|
|
135299
135857
|
args: { color: {
|
|
135300
135858
|
mode: "mired",
|
|
135301
|
-
|
|
135859
|
+
colorTemperature: 370
|
|
135302
135860
|
} }
|
|
135303
135861
|
},
|
|
135304
135862
|
{
|
|
@@ -135309,7 +135867,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
135309
135867
|
icon: "snowflake",
|
|
135310
135868
|
args: { color: {
|
|
135311
135869
|
mode: "mired",
|
|
135312
|
-
|
|
135870
|
+
colorTemperature: 200
|
|
135313
135871
|
} }
|
|
135314
135872
|
},
|
|
135315
135873
|
{
|
|
@@ -135923,7 +136481,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
135923
136481
|
label: t("Fan off", "Ventilatore spento"),
|
|
135924
136482
|
phrase: t("Stop {device}", "Ferma {device}"),
|
|
135925
136483
|
icon: "power-off",
|
|
135926
|
-
args: {
|
|
136484
|
+
args: { speed: 0 }
|
|
135927
136485
|
},
|
|
135928
136486
|
{
|
|
135929
136487
|
id: "fan.full",
|
|
@@ -135931,16 +136489,16 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
135931
136489
|
label: t("Fan full", "Ventilatore al massimo"),
|
|
135932
136490
|
phrase: t("Run {device} at full speed", "Porta {device} alla massima velocità"),
|
|
135933
136491
|
icon: "fan",
|
|
135934
|
-
args: {
|
|
136492
|
+
args: { speed: 100 }
|
|
135935
136493
|
},
|
|
135936
136494
|
{
|
|
135937
136495
|
id: "fan.speed",
|
|
135938
136496
|
method: "setPercentage",
|
|
135939
136497
|
label: t("Set speed", "Imposta la velocità"),
|
|
135940
|
-
phrase: t("Set the speed of {device} to {
|
|
136498
|
+
phrase: t("Set the speed of {device} to {speed}", "Imposta la velocità di {device} al {speed}"),
|
|
135941
136499
|
icon: "fan",
|
|
135942
136500
|
openArgs: [{
|
|
135943
|
-
key: "
|
|
136501
|
+
key: "speed",
|
|
135944
136502
|
label: t("Speed", "Velocità"),
|
|
135945
136503
|
offer: {
|
|
135946
136504
|
kind: "number",
|
|
@@ -136002,7 +136560,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
136002
136560
|
],
|
|
136003
136561
|
state: [{
|
|
136004
136562
|
kind: "numeric",
|
|
136005
|
-
field: "
|
|
136563
|
+
field: "speed",
|
|
136006
136564
|
quantity: t("the speed", "la velocità"),
|
|
136007
136565
|
label: t("Speed", "Velocità"),
|
|
136008
136566
|
unit: "%",
|
|
@@ -136800,7 +137358,7 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
136800
137358
|
},
|
|
136801
137359
|
{
|
|
136802
137360
|
kind: "numeric",
|
|
136803
|
-
field: "
|
|
137361
|
+
field: "progress",
|
|
136804
137362
|
quantity: t("the cycle progress", "l’avanzamento del lavaggio"),
|
|
136805
137363
|
label: t("Progress", "Avanzamento"),
|
|
136806
137364
|
unit: "%",
|
|
@@ -137158,10 +137716,10 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
137158
137716
|
id: "media.seek",
|
|
137159
137717
|
method: "seek",
|
|
137160
137718
|
label: t("Seek", "Vai al punto"),
|
|
137161
|
-
phrase: t("Move {device} to {
|
|
137719
|
+
phrase: t("Move {device} to {position}", "Porta {device} al punto {position}"),
|
|
137162
137720
|
icon: "fast-forward",
|
|
137163
137721
|
openArgs: [{
|
|
137164
|
-
key: "
|
|
137722
|
+
key: "position",
|
|
137165
137723
|
label: t("Position", "Posizione"),
|
|
137166
137724
|
offer: {
|
|
137167
137725
|
kind: "number",
|
|
@@ -138210,7 +138768,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138210
138768
|
state: [
|
|
138211
138769
|
{
|
|
138212
138770
|
kind: "numeric",
|
|
138213
|
-
field: "
|
|
138771
|
+
field: "co2",
|
|
138214
138772
|
quantity: t("the CO₂", "la CO₂"),
|
|
138215
138773
|
label: t("CO₂", "CO₂"),
|
|
138216
138774
|
unit: "ppm",
|
|
@@ -138218,7 +138776,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138218
138776
|
},
|
|
138219
138777
|
{
|
|
138220
138778
|
kind: "numeric",
|
|
138221
|
-
field: "
|
|
138779
|
+
field: "voc",
|
|
138222
138780
|
quantity: t("the VOC level", "il livello di COV"),
|
|
138223
138781
|
label: t("VOC", "COV"),
|
|
138224
138782
|
unit: "ppb",
|
|
@@ -138254,7 +138812,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138254
138812
|
noun: t("Light level", "Luminosità ambiente"),
|
|
138255
138813
|
state: [{
|
|
138256
138814
|
kind: "numeric",
|
|
138257
|
-
field: "
|
|
138815
|
+
field: "illuminance",
|
|
138258
138816
|
quantity: t("the light level", "la luminosità"),
|
|
138259
138817
|
label: t("Illuminance", "Illuminamento"),
|
|
138260
138818
|
unit: "lx",
|
|
@@ -138457,7 +139015,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138457
139015
|
noun: t("Humidity", "Umidità"),
|
|
138458
139016
|
state: [{
|
|
138459
139017
|
kind: "numeric",
|
|
138460
|
-
field: "
|
|
139018
|
+
field: "humidity",
|
|
138461
139019
|
quantity: t("the humidity", "l’umidità"),
|
|
138462
139020
|
label: t("Humidity", "Umidità"),
|
|
138463
139021
|
unit: "%",
|
|
@@ -138505,7 +139063,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138505
139063
|
state: [
|
|
138506
139064
|
{
|
|
138507
139065
|
kind: "numeric",
|
|
138508
|
-
field: "
|
|
139066
|
+
field: "power",
|
|
138509
139067
|
quantity: t("the power draw", "il consumo"),
|
|
138510
139068
|
label: t("Power", "Potenza"),
|
|
138511
139069
|
unit: "W",
|
|
@@ -138513,7 +139071,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138513
139071
|
},
|
|
138514
139072
|
{
|
|
138515
139073
|
kind: "numeric",
|
|
138516
|
-
field: "
|
|
139074
|
+
field: "energy",
|
|
138517
139075
|
quantity: t("the energy used", "l’energia consumata"),
|
|
138518
139076
|
label: t("Energy", "Energia"),
|
|
138519
139077
|
unit: "kWh",
|
|
@@ -138521,7 +139079,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138521
139079
|
},
|
|
138522
139080
|
{
|
|
138523
139081
|
kind: "numeric",
|
|
138524
|
-
field: "
|
|
139082
|
+
field: "voltage",
|
|
138525
139083
|
quantity: t("the voltage", "la tensione"),
|
|
138526
139084
|
label: t("Voltage", "Tensione"),
|
|
138527
139085
|
unit: "V",
|
|
@@ -138529,7 +139087,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138529
139087
|
},
|
|
138530
139088
|
{
|
|
138531
139089
|
kind: "numeric",
|
|
138532
|
-
field: "
|
|
139090
|
+
field: "current",
|
|
138533
139091
|
quantity: t("the current", "la corrente"),
|
|
138534
139092
|
label: t("Current", "Corrente"),
|
|
138535
139093
|
unit: "A",
|
|
@@ -138546,13 +139104,6 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138546
139104
|
label: t("Where", "Posizione"),
|
|
138547
139105
|
phrase: t("When {device} is at {value}", "Quando {device} è in {value}"),
|
|
138548
139106
|
icon: "map-pin"
|
|
138549
|
-
}, {
|
|
138550
|
-
kind: "numeric",
|
|
138551
|
-
field: "batteryPercent",
|
|
138552
|
-
quantity: t("the phone battery", "la batteria del telefono"),
|
|
138553
|
-
label: t("Battery", "Batteria"),
|
|
138554
|
-
unit: "%",
|
|
138555
|
-
icon: "battery"
|
|
138556
139107
|
}]
|
|
138557
139108
|
},
|
|
138558
139109
|
"pressure-sensor": {
|
|
@@ -138560,7 +139111,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138560
139111
|
noun: t("Pressure", "Pressione"),
|
|
138561
139112
|
state: [{
|
|
138562
139113
|
kind: "numeric",
|
|
138563
|
-
field: "
|
|
139114
|
+
field: "pressure",
|
|
138564
139115
|
quantity: t("the pressure", "la pressione"),
|
|
138565
139116
|
label: t("Pressure", "Pressione"),
|
|
138566
139117
|
unit: "hPa",
|
|
@@ -138596,7 +139147,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138596
139147
|
noun: t("Temperature", "Temperatura"),
|
|
138597
139148
|
state: [{
|
|
138598
139149
|
kind: "numeric",
|
|
138599
|
-
field: "
|
|
139150
|
+
field: "temperature",
|
|
138600
139151
|
quantity: t("the temperature", "la temperatura"),
|
|
138601
139152
|
label: t("Temperature", "Temperatura"),
|
|
138602
139153
|
unit: "°C",
|
|
@@ -138667,7 +139218,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138667
139218
|
state: [
|
|
138668
139219
|
{
|
|
138669
139220
|
kind: "numeric",
|
|
138670
|
-
field: "
|
|
139221
|
+
field: "level",
|
|
138671
139222
|
quantity: t("the battery level", "il livello della batteria"),
|
|
138672
139223
|
label: t("Battery", "Batteria"),
|
|
138673
139224
|
unit: "%",
|
|
@@ -138709,7 +139260,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138709
139260
|
icon: "battery-charging"
|
|
138710
139261
|
}
|
|
138711
139262
|
],
|
|
138712
|
-
events: { onStatusChanged: { notOperatorFacing: "the
|
|
139263
|
+
events: { onStatusChanged: { notOperatorFacing: "the level / sleeping / charging state triggers each read one field of this change" } },
|
|
138713
139264
|
extraEvents: [{
|
|
138714
139265
|
category: EventCategory.DeviceAwake,
|
|
138715
139266
|
label: t("Woke up", "Risvegliato"),
|
|
@@ -138727,7 +139278,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138727
139278
|
noun: t("Network link", "Collegamento di rete"),
|
|
138728
139279
|
state: [{
|
|
138729
139280
|
kind: "numeric",
|
|
138730
|
-
field: "
|
|
139281
|
+
field: "signal",
|
|
138731
139282
|
quantity: t("the signal strength", "l’intensità del segnale"),
|
|
138732
139283
|
label: t("Signal", "Segnale"),
|
|
138733
139284
|
unit: "%",
|
|
@@ -138820,6 +139371,8 @@ var ML = "model and training machinery — no device action";
|
|
|
138820
139371
|
var INTERNAL = "internal plumbing between addons — never operator-facing";
|
|
138821
139372
|
/** Every system-scoped cap, with why it is not a device's vocabulary. */
|
|
138822
139373
|
var SYSTEM_CAP_VOCABULARY = {
|
|
139374
|
+
"action-source": system(INTERNAL),
|
|
139375
|
+
actions: system("declared actions — run from the Actions page and from the cameras that link them"),
|
|
138823
139376
|
"addon-pages": system(UI_PLUMBING),
|
|
138824
139377
|
"addon-pages-source": system(UI_PLUMBING),
|
|
138825
139378
|
"addon-routes": system(UI_PLUMBING),
|
|
@@ -139016,6 +139569,31 @@ Object.freeze({
|
|
|
139016
139569
|
form: "single",
|
|
139017
139570
|
optional: false
|
|
139018
139571
|
}],
|
|
139572
|
+
"actions.get": [{
|
|
139573
|
+
name: "deviceId",
|
|
139574
|
+
form: "single",
|
|
139575
|
+
optional: true
|
|
139576
|
+
}],
|
|
139577
|
+
"actions.getLinks": [{
|
|
139578
|
+
name: "deviceId",
|
|
139579
|
+
form: "single",
|
|
139580
|
+
optional: false
|
|
139581
|
+
}],
|
|
139582
|
+
"actions.listForCamera": [{
|
|
139583
|
+
name: "deviceId",
|
|
139584
|
+
form: "single",
|
|
139585
|
+
optional: false
|
|
139586
|
+
}],
|
|
139587
|
+
"actions.run": [{
|
|
139588
|
+
name: "deviceId",
|
|
139589
|
+
form: "single",
|
|
139590
|
+
optional: true
|
|
139591
|
+
}],
|
|
139592
|
+
"actions.setLinks": [{
|
|
139593
|
+
name: "deviceId",
|
|
139594
|
+
form: "single",
|
|
139595
|
+
optional: false
|
|
139596
|
+
}],
|
|
139019
139597
|
"addonSettings.getDeviceSettings": [{
|
|
139020
139598
|
name: "deviceId",
|
|
139021
139599
|
form: "single",
|
|
@@ -143187,6 +143765,7 @@ function ingestReading(input) {
|
|
|
143187
143765
|
value: converted
|
|
143188
143766
|
};
|
|
143189
143767
|
}
|
|
143768
|
+
String.raw`(?<=^|["'(\s>,\[{])`;
|
|
143190
143769
|
var MB = 1024 * 1024;
|
|
143191
143770
|
1024 * MB, 3072 * MB;
|
|
143192
143771
|
//#endregion
|
|
@@ -143313,6 +143892,334 @@ function buildConnectionFormSchema() {
|
|
|
143313
143892
|
] };
|
|
143314
143893
|
}
|
|
143315
143894
|
//#endregion
|
|
143895
|
+
//#region src/dreame-vacuum-decode.ts
|
|
143896
|
+
/**
|
|
143897
|
+
* Pure decode helpers for the Dreame vacuum. Translate the library's MIoT enum
|
|
143898
|
+
* values into the camstack `vacuum-control` slice shape. Unit-tested in
|
|
143899
|
+
* isolation (no facade, no network) — mirrors `homematic-decode.ts`.
|
|
143900
|
+
*/
|
|
143901
|
+
/**
|
|
143902
|
+
* Map a Dreame {@link MiotState} (siid 2 piid 1) onto the camstack canonical
|
|
143903
|
+
* `VacuumState` lifecycle (`idle` / `cleaning` / `paused` / `returning` /
|
|
143904
|
+
* `docked` / `error`). A `null` state (not yet observed) settles on `idle`.
|
|
143905
|
+
*
|
|
143906
|
+
* The MIoT state space is large (~50 values); we collapse it onto the six
|
|
143907
|
+
* canonical states by family. Anything unrecognised falls through to `idle`
|
|
143908
|
+
* (the safe neutral) so a firmware-added code never throws.
|
|
143909
|
+
*/
|
|
143910
|
+
function mapMiotStateToVacuumState(state) {
|
|
143911
|
+
if (state === null) return "idle";
|
|
143912
|
+
switch (state) {
|
|
143913
|
+
case MiotState.Charging:
|
|
143914
|
+
case MiotState.ChargingComplete:
|
|
143915
|
+
case MiotState.IntelligentCharging:
|
|
143916
|
+
case MiotState.CleanWashboardBase:
|
|
143917
|
+
case MiotState.AutoRepairBase:
|
|
143918
|
+
case MiotState.WaterSupplyDrainTest:
|
|
143919
|
+
case MiotState.CleanMopRefillWater:
|
|
143920
|
+
case MiotState.AutoEmptying:
|
|
143921
|
+
case MiotState.Emptying:
|
|
143922
|
+
case MiotState.Draining:
|
|
143923
|
+
case MiotState.AutoWaterDraining:
|
|
143924
|
+
case MiotState.Sanitizing: return "docked";
|
|
143925
|
+
case MiotState.MopDrying:
|
|
143926
|
+
case MiotState.DustBagDrying:
|
|
143927
|
+
case MiotState.SanitizingWithDry: return "drying";
|
|
143928
|
+
case MiotState.ReturningToCharge:
|
|
143929
|
+
case MiotState.ReturningToWash:
|
|
143930
|
+
case MiotState.ReturnInstallMop:
|
|
143931
|
+
case MiotState.ReturnRemoveMop:
|
|
143932
|
+
case MiotState.ReturnToEmpty:
|
|
143933
|
+
case MiotState.ReturningToDrain: return "returning";
|
|
143934
|
+
case MiotState.Paused:
|
|
143935
|
+
case MiotState.PausedAlt:
|
|
143936
|
+
case MiotState.PausedCleaning:
|
|
143937
|
+
case MiotState.DustBagDryingPaused:
|
|
143938
|
+
case MiotState.CameraMonitoringPaused:
|
|
143939
|
+
case MiotState.FindingPetPaused:
|
|
143940
|
+
case MiotState.InitialDeepCleanPaused:
|
|
143941
|
+
case MiotState.ChangingMopPaused:
|
|
143942
|
+
case MiotState.FloorMaintainingPaused: return "paused";
|
|
143943
|
+
case MiotState.Standby:
|
|
143944
|
+
case MiotState.WaitingForTask: return "idle";
|
|
143945
|
+
default: return "cleaning";
|
|
143946
|
+
}
|
|
143947
|
+
}
|
|
143948
|
+
/**
|
|
143949
|
+
* The camstack `fanSpeed` token for a Dreame {@link SuctionLevel} — the enum
|
|
143950
|
+
* member NAME, lower-cased (`Quiet` → `'quiet'`). Returns `null` for an
|
|
143951
|
+
* unobserved / unknown level.
|
|
143952
|
+
*/
|
|
143953
|
+
function suctionLevelToken(level) {
|
|
143954
|
+
if (level === null) return null;
|
|
143955
|
+
const name = SuctionLevel[level];
|
|
143956
|
+
return typeof name === "string" ? name.toLowerCase() : null;
|
|
143957
|
+
}
|
|
143958
|
+
/**
|
|
143959
|
+
* Resolve a camstack `fanSpeed` token back to the Dreame {@link SuctionLevel}
|
|
143960
|
+
* enum value, validated against the model's `supported` levels. Returns null
|
|
143961
|
+
* when the token doesn't match a supported level (the caller rejects the set).
|
|
143962
|
+
*/
|
|
143963
|
+
function tokenToSuctionLevel(token, supported) {
|
|
143964
|
+
const wanted = token.trim().toLowerCase();
|
|
143965
|
+
for (const level of supported) if (suctionLevelToken(level) === wanted) return level;
|
|
143966
|
+
return null;
|
|
143967
|
+
}
|
|
143968
|
+
/** The list of accepted `fanSpeed` tokens for a model's supported suction set. */
|
|
143969
|
+
function supportedFanSpeedTokens(supported) {
|
|
143970
|
+
const tokens = [];
|
|
143971
|
+
for (const level of supported) {
|
|
143972
|
+
const token = suctionLevelToken(level);
|
|
143973
|
+
if (token !== null) tokens.push(token);
|
|
143974
|
+
}
|
|
143975
|
+
return tokens;
|
|
143976
|
+
}
|
|
143977
|
+
/**
|
|
143978
|
+
* Clamp a raw battery reading (already a 0..100 percentage from the library)
|
|
143979
|
+
* to the `vacuum-control` slice's `batteryLevel` field. Returns null for a
|
|
143980
|
+
* null/NaN/out-of-band reading so the slice stays valid (the schema rejects
|
|
143981
|
+
* a number outside 0..100).
|
|
143982
|
+
*/
|
|
143983
|
+
function clampBatteryLevel$1(value) {
|
|
143984
|
+
if (value === null || !Number.isFinite(value)) return null;
|
|
143985
|
+
return Math.max(0, Math.min(100, value));
|
|
143986
|
+
}
|
|
143987
|
+
/**
|
|
143988
|
+
* MiotError codes that are BENIGN completion / reminder states, not active
|
|
143989
|
+
* faults. A fresh cloud-shadow read of an idle docked robot legitimately
|
|
143990
|
+
* returns `TaskComplete` (68); it is not an error to show. The Error sensor
|
|
143991
|
+
* (`dreame-entity-catalog.ts`) and the vacuum card both read this set.
|
|
143992
|
+
* Extend it if other non-fault codes surface as spurious "errors".
|
|
143993
|
+
*/
|
|
143994
|
+
var BENIGN_ERROR_CODES = new Set([MiotError.TaskComplete]);
|
|
143995
|
+
/** The card's error label: null for no error and for a benign completion code. */
|
|
143996
|
+
function vacuumErrorLabel(code) {
|
|
143997
|
+
if (!code || BENIGN_ERROR_CODES.has(code)) return null;
|
|
143998
|
+
return `Error ${code}`;
|
|
143999
|
+
}
|
|
144000
|
+
/** Clamp a 0..100 percentage (progress, coverage, …), null-safe. */
|
|
144001
|
+
function clampPercent$1(value) {
|
|
144002
|
+
if (value === null || !Number.isFinite(value)) return null;
|
|
144003
|
+
return Math.max(0, Math.min(100, value));
|
|
144004
|
+
}
|
|
144005
|
+
/**
|
|
144006
|
+
* Where each {@link VacuumTankProps} field lives (siid/piid). Property ids and
|
|
144007
|
+
* enum values are the HA Dreame integration's (Tasshack `dreame-vacuum`
|
|
144008
|
+
* `types.py`, `DreameVacuumProperty` → siid/piid map; same on `dev` and
|
|
144009
|
+
* v2.0.0b25, checked 2026-09-26). 27/1 and 27/4 were already read here; 27/2 and
|
|
144010
|
+
* 27/3 are their neighbours on the same station service. The robot pushes them
|
|
144011
|
+
* on change; the consumables refresh also re-reads them from the shadow.
|
|
144012
|
+
*/
|
|
144013
|
+
var VACUUM_TANK_PROPS = {
|
|
144014
|
+
cleanWaterTankStatus: {
|
|
144015
|
+
siid: 27,
|
|
144016
|
+
piid: 1
|
|
144017
|
+
},
|
|
144018
|
+
waterTank: {
|
|
144019
|
+
siid: 4,
|
|
144020
|
+
piid: 6
|
|
144021
|
+
},
|
|
144022
|
+
detergentStatus: {
|
|
144023
|
+
siid: 27,
|
|
144024
|
+
piid: 4
|
|
144025
|
+
},
|
|
144026
|
+
dustCollection: {
|
|
144027
|
+
siid: 15,
|
|
144028
|
+
piid: 3
|
|
144029
|
+
},
|
|
144030
|
+
dirtyWaterTankStatus: {
|
|
144031
|
+
siid: 27,
|
|
144032
|
+
piid: 2
|
|
144033
|
+
},
|
|
144034
|
+
dustBagStatus: {
|
|
144035
|
+
siid: 27,
|
|
144036
|
+
piid: 3
|
|
144037
|
+
}
|
|
144038
|
+
};
|
|
144039
|
+
/**
|
|
144040
|
+
* Clean-water tank `status`: prefer the richer clean-water-tank-status (27/1),
|
|
144041
|
+
* fall back to the coarser water-tank presence (4/6). `null` when neither maps
|
|
144042
|
+
* to a known good/low reading (NOT_INSTALLED / CHECKING / unknown). Pure.
|
|
144043
|
+
*/
|
|
144044
|
+
function cleanWaterTankStatus(props) {
|
|
144045
|
+
if (props.cleanWaterTankStatus !== null) {
|
|
144046
|
+
if (props.cleanWaterTankStatus === 0) return "ok";
|
|
144047
|
+
if (props.cleanWaterTankStatus === 2) return "low";
|
|
144048
|
+
return null;
|
|
144049
|
+
}
|
|
144050
|
+
if (props.waterTank === 1 || props.waterTank === 10) return "ok";
|
|
144051
|
+
return null;
|
|
144052
|
+
}
|
|
144053
|
+
/** Detergent tank `status` from detergent-status (27/4). Pure. */
|
|
144054
|
+
function detergentTankStatus(value) {
|
|
144055
|
+
if (value === 0) return "ok";
|
|
144056
|
+
if (value === 2) return "low";
|
|
144057
|
+
return null;
|
|
144058
|
+
}
|
|
144059
|
+
/**
|
|
144060
|
+
* Dust-bin `status` from dust-collection (15/3); OVER_USE reads as `full`. A dust
|
|
144061
|
+
* bag that is missing or asks to be checked (27/3 NOT_INSTALLED / CHECK) is never
|
|
144062
|
+
* `ok` — the enum does not say "full" either, so it is unknown. Pure.
|
|
144063
|
+
*/
|
|
144064
|
+
function dustBinTankStatus(dustCollection, dustBag) {
|
|
144065
|
+
if (dustCollection === 2) return "full";
|
|
144066
|
+
if (dustBag !== null && dustBag !== 0) return null;
|
|
144067
|
+
if (dustCollection === 1) return "ok";
|
|
144068
|
+
return null;
|
|
144069
|
+
}
|
|
144070
|
+
/**
|
|
144071
|
+
* Dirty-water tank `status` from dirty-water-tank-status (27/2). The robot has
|
|
144072
|
+
* ONE value for "not installed or full" and cannot tell them apart; lifting the
|
|
144073
|
+
* tank out to EMPTY it reads exactly like a full one, so that value is unknown
|
|
144074
|
+
* (`null`) — never `full`, which would raise a false "tank full" at the moment
|
|
144075
|
+
* someone is fixing it (controller ruling 2026-09-26). Absent or any other
|
|
144076
|
+
* value is unknown too, never `ok`.
|
|
144077
|
+
*
|
|
144078
|
+
* The unambiguous "full" is the robot's own error `MiotError.DirtyWaterTankFull`
|
|
144079
|
+
* (86, the library's `errorCode`, siid 2 piid 2): while it is raised the tank is
|
|
144080
|
+
* `full` whatever 27/2 says; once it clears, 27/2 decides again. Pure.
|
|
144081
|
+
*/
|
|
144082
|
+
function dirtyWaterTankStatus(value, errorCode) {
|
|
144083
|
+
if (errorCode === MiotError.DirtyWaterTankFull) return "full";
|
|
144084
|
+
if (value === 0) return "ok";
|
|
144085
|
+
return null;
|
|
144086
|
+
}
|
|
144087
|
+
/**
|
|
144088
|
+
* The four tanks (clean-water / dirty-water / detergent / dust-bin) decoded from the model's {@link VacuumCapabilities}, with the
|
|
144089
|
+
* discrete `status` populated from the live MIoT enum props when supplied. Pure.
|
|
144090
|
+
*
|
|
144091
|
+
* The nodedreame library exposes NO tank FILL-LEVEL telemetry — the cloud shadow
|
|
144092
|
+
* (verified against a real `dreame.vacuum.r2538z`) carries only the water-VOLUME
|
|
144093
|
+
* *setting* (a mop-flow level, NOT a tank fill). We therefore never fabricate a
|
|
144094
|
+
* `level`; `level` is always `null`. The cap contract is honoured on PRESENCE,
|
|
144095
|
+
* with `status` derived from the tank-status enums:
|
|
144096
|
+
*
|
|
144097
|
+
* - a field is `null` when the hardware HAS NO such tank (capability flags), and
|
|
144098
|
+
* - `{ level: null, status }` when the tank EXISTS — `status` from the enum
|
|
144099
|
+
* prop ('ok' / 'low' / 'full'), or `null` when the device reports none.
|
|
144100
|
+
*
|
|
144101
|
+
* Presence is derived from the capability record:
|
|
144102
|
+
* - clean-water ← `canMop` (a mopping robot carries a clean-water reservoir)
|
|
144103
|
+
* - dirty-water ← `canMopWash` (a self-wash dock has a recovery/dirty tank)
|
|
144104
|
+
* - detergent ← `hasDetergentReservoir`
|
|
144105
|
+
* - dust-bin ← `canAutoEmpty` (an auto-empty robot has an onboard dust bin)
|
|
144106
|
+
*/
|
|
144107
|
+
function decodeVacuumTanks(caps, props, errorCode = null) {
|
|
144108
|
+
const tank = (present, status) => present ? {
|
|
144109
|
+
level: null,
|
|
144110
|
+
status
|
|
144111
|
+
} : null;
|
|
144112
|
+
return {
|
|
144113
|
+
cleanWater: tank(Boolean(caps?.canMop), props ? cleanWaterTankStatus(props) : null),
|
|
144114
|
+
dirtyWater: tank(Boolean(caps?.canMopWash), dirtyWaterTankStatus(props?.dirtyWaterTankStatus ?? null, errorCode)),
|
|
144115
|
+
detergent: tank(Boolean(caps?.hasDetergentReservoir), props ? detergentTankStatus(props.detergentStatus) : null),
|
|
144116
|
+
dustBin: tank(Boolean(caps?.canAutoEmpty), props ? dustBinTankStatus(props.dustCollection, props.dustBagStatus) : null)
|
|
144117
|
+
};
|
|
144118
|
+
}
|
|
144119
|
+
/** How each tank is named and what it is, in the order the panel lists them. */
|
|
144120
|
+
var TANK_ITEMS = [
|
|
144121
|
+
{
|
|
144122
|
+
key: "cleanWater",
|
|
144123
|
+
label: "Clean water tank",
|
|
144124
|
+
kind: "supply",
|
|
144125
|
+
form: "tank"
|
|
144126
|
+
},
|
|
144127
|
+
{
|
|
144128
|
+
key: "dirtyWater",
|
|
144129
|
+
label: "Dirty water tank",
|
|
144130
|
+
kind: "waste",
|
|
144131
|
+
form: "tank"
|
|
144132
|
+
},
|
|
144133
|
+
{
|
|
144134
|
+
key: "detergent",
|
|
144135
|
+
label: "Detergent",
|
|
144136
|
+
kind: "supply",
|
|
144137
|
+
form: "product"
|
|
144138
|
+
},
|
|
144139
|
+
{
|
|
144140
|
+
key: "dustBin",
|
|
144141
|
+
label: "Dust bin",
|
|
144142
|
+
kind: "waste",
|
|
144143
|
+
form: "bin"
|
|
144144
|
+
}
|
|
144145
|
+
];
|
|
144146
|
+
/**
|
|
144147
|
+
* The tanks as `consumables` items (D915): every PRESENT tank becomes a
|
|
144148
|
+
* `supply` (clean water, detergent) or `waste` (dirty water, dust bin) item
|
|
144149
|
+
* keyed by its old field name, drawn as a `tank` (the two water tanks), a
|
|
144150
|
+
* `product` (detergent) or a `bin` (the dust bin) — the key the notification centre's tank
|
|
144151
|
+
* episodes were built on, so an open episode carries on. A tank is never
|
|
144152
|
+
* resettable: emptying or refilling it is the reset. Pure.
|
|
144153
|
+
*/
|
|
144154
|
+
function vacuumTankItems(tanks) {
|
|
144155
|
+
return TANK_ITEMS.flatMap(({ key, label, kind, form }) => {
|
|
144156
|
+
const tank = tanks[key];
|
|
144157
|
+
if (tank === null) return [];
|
|
144158
|
+
return [{
|
|
144159
|
+
key,
|
|
144160
|
+
label,
|
|
144161
|
+
level: tank.level,
|
|
144162
|
+
status: tank.status,
|
|
144163
|
+
kind,
|
|
144164
|
+
form,
|
|
144165
|
+
lastResetAt: null,
|
|
144166
|
+
resettable: false
|
|
144167
|
+
}];
|
|
144168
|
+
});
|
|
144169
|
+
}
|
|
144170
|
+
/**
|
|
144171
|
+
* Clamp a raw consumable percentage to `[0, 100]`, or return `null` when the
|
|
144172
|
+
* value is absent / non-finite. The library reports remaining life as a 0..100
|
|
144173
|
+
* integer; we clamp defensively so an out-of-range firmware value never violates
|
|
144174
|
+
* the cap schema.
|
|
144175
|
+
*/
|
|
144176
|
+
function clampConsumablePct(value) {
|
|
144177
|
+
if (value === null || !Number.isFinite(value)) return null;
|
|
144178
|
+
return Math.max(0, Math.min(100, value));
|
|
144179
|
+
}
|
|
144180
|
+
/**
|
|
144181
|
+
* The `consumables` cap items for the consumables a vacuum REPORTS.
|
|
144182
|
+
*
|
|
144183
|
+
* PRESENCE-driven (mirrors HA): the library's `supportedConsumables` enumerates
|
|
144184
|
+
* ONLY the consumables this model reports (a present MIoT life property), each
|
|
144185
|
+
* with its remaining-life % and whether a reset action exists. No hardcoded
|
|
144186
|
+
* per-model list: a consumable the device does not expose never appears.
|
|
144187
|
+
*
|
|
144188
|
+
* `status` is `null` — the provider reports the level, the notification rule
|
|
144189
|
+
* decides what is low. The slice clocks are `nextConsumablesSlice`'s job. Pure.
|
|
144190
|
+
*/
|
|
144191
|
+
function buildVacuumConsumableItems(readings) {
|
|
144192
|
+
return readings.map((c) => ({
|
|
144193
|
+
key: c.key,
|
|
144194
|
+
label: c.label,
|
|
144195
|
+
level: clampConsumablePct(c.leftPct),
|
|
144196
|
+
status: null,
|
|
144197
|
+
lastResetAt: null,
|
|
144198
|
+
resettable: c.resettable,
|
|
144199
|
+
form: "part"
|
|
144200
|
+
}));
|
|
144201
|
+
}
|
|
144202
|
+
/** Structural equality of two JSON-shaped values, key order ignored. */
|
|
144203
|
+
function jsonEqual(a, b) {
|
|
144204
|
+
if (a === b) return true;
|
|
144205
|
+
if (Array.isArray(a) || Array.isArray(b)) return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((v, i) => jsonEqual(v, b[i]));
|
|
144206
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
|
|
144207
|
+
const ka = Object.keys(a);
|
|
144208
|
+
const kb = Object.keys(b);
|
|
144209
|
+
return ka.length === kb.length && ka.every((k) => jsonEqual(Reflect.get(a, k), Reflect.get(b, k)));
|
|
144210
|
+
}
|
|
144211
|
+
/**
|
|
144212
|
+
* The `vacuum-control` slice to write, or `null` when only the clock moved.
|
|
144213
|
+
* `recomputeSlice` runs on EVERY push; without this each one was a write (and a
|
|
144214
|
+
* `device.state-changed`) even when nothing the robot reported had changed. Pure.
|
|
144215
|
+
*/
|
|
144216
|
+
function nextVacuumControlSlice(prev, next) {
|
|
144217
|
+
if (prev === void 0) return next;
|
|
144218
|
+
const { lastChangedAt: _prevAt, ...prevContent } = prev;
|
|
144219
|
+
const { lastChangedAt: _nextAt, ...nextContent } = next;
|
|
144220
|
+
return jsonEqual(prevContent, nextContent) ? null : next;
|
|
144221
|
+
}
|
|
144222
|
+
//#endregion
|
|
143316
144223
|
//#region src/dreame-entity-catalog.ts
|
|
143317
144224
|
var STATS = "Statistics";
|
|
143318
144225
|
var MAINT = "Maintenance";
|
|
@@ -143959,14 +144866,6 @@ function enumOptions(e) {
|
|
|
143959
144866
|
label: humanizeEnumName(name)
|
|
143960
144867
|
}));
|
|
143961
144868
|
}
|
|
143962
|
-
/**
|
|
143963
|
-
* MiotError codes that are BENIGN completion / reminder states, not active
|
|
143964
|
-
* faults. HA does not surface these as errors — a fresh cloud-shadow read of an
|
|
143965
|
-
* idle docked robot legitimately returns `TaskComplete` (68). We relabel them to
|
|
143966
|
-
* the `Clear` label so the Error sensor only shows real faults (HA parity).
|
|
143967
|
-
* Extend this set if other non-fault codes surface as spurious "errors".
|
|
143968
|
-
*/
|
|
143969
|
-
var BENIGN_ERROR_CODES = new Set([MiotError.TaskComplete]);
|
|
143970
144869
|
/** `MiotError` options with the benign completion codes remapped to the `Clear`
|
|
143971
144870
|
* label, so the Error sensor reads as no-error for them (matches HA). */
|
|
143972
144871
|
function errorEnumOptions() {
|
|
@@ -145324,321 +146223,6 @@ var DreameButtonDevice = class extends DreameChildDevice {
|
|
|
145324
146223
|
recomputeSlice() {}
|
|
145325
146224
|
};
|
|
145326
146225
|
//#endregion
|
|
145327
|
-
//#region src/dreame-vacuum-decode.ts
|
|
145328
|
-
/**
|
|
145329
|
-
* Pure decode helpers for the Dreame vacuum. Translate the library's MIoT enum
|
|
145330
|
-
* values into the camstack `vacuum-control` slice shape. Unit-tested in
|
|
145331
|
-
* isolation (no facade, no network) — mirrors `homematic-decode.ts`.
|
|
145332
|
-
*/
|
|
145333
|
-
/**
|
|
145334
|
-
* Map a Dreame {@link MiotState} (siid 2 piid 1) onto the camstack canonical
|
|
145335
|
-
* `VacuumState` lifecycle (`idle` / `cleaning` / `paused` / `returning` /
|
|
145336
|
-
* `docked` / `error`). A `null` state (not yet observed) settles on `idle`.
|
|
145337
|
-
*
|
|
145338
|
-
* The MIoT state space is large (~50 values); we collapse it onto the six
|
|
145339
|
-
* canonical states by family. Anything unrecognised falls through to `idle`
|
|
145340
|
-
* (the safe neutral) so a firmware-added code never throws.
|
|
145341
|
-
*/
|
|
145342
|
-
function mapMiotStateToVacuumState(state) {
|
|
145343
|
-
if (state === null) return "idle";
|
|
145344
|
-
switch (state) {
|
|
145345
|
-
case MiotState.Charging:
|
|
145346
|
-
case MiotState.ChargingComplete:
|
|
145347
|
-
case MiotState.IntelligentCharging:
|
|
145348
|
-
case MiotState.CleanWashboardBase:
|
|
145349
|
-
case MiotState.AutoRepairBase:
|
|
145350
|
-
case MiotState.WaterSupplyDrainTest:
|
|
145351
|
-
case MiotState.CleanMopRefillWater:
|
|
145352
|
-
case MiotState.AutoEmptying:
|
|
145353
|
-
case MiotState.Emptying:
|
|
145354
|
-
case MiotState.Draining:
|
|
145355
|
-
case MiotState.AutoWaterDraining:
|
|
145356
|
-
case MiotState.Sanitizing: return "docked";
|
|
145357
|
-
case MiotState.MopDrying:
|
|
145358
|
-
case MiotState.DustBagDrying:
|
|
145359
|
-
case MiotState.SanitizingWithDry: return "drying";
|
|
145360
|
-
case MiotState.ReturningToCharge:
|
|
145361
|
-
case MiotState.ReturningToWash:
|
|
145362
|
-
case MiotState.ReturnInstallMop:
|
|
145363
|
-
case MiotState.ReturnRemoveMop:
|
|
145364
|
-
case MiotState.ReturnToEmpty:
|
|
145365
|
-
case MiotState.ReturningToDrain: return "returning";
|
|
145366
|
-
case MiotState.Paused:
|
|
145367
|
-
case MiotState.PausedAlt:
|
|
145368
|
-
case MiotState.PausedCleaning:
|
|
145369
|
-
case MiotState.DustBagDryingPaused:
|
|
145370
|
-
case MiotState.CameraMonitoringPaused:
|
|
145371
|
-
case MiotState.FindingPetPaused:
|
|
145372
|
-
case MiotState.InitialDeepCleanPaused:
|
|
145373
|
-
case MiotState.ChangingMopPaused:
|
|
145374
|
-
case MiotState.FloorMaintainingPaused: return "paused";
|
|
145375
|
-
case MiotState.Standby:
|
|
145376
|
-
case MiotState.WaitingForTask: return "idle";
|
|
145377
|
-
default: return "cleaning";
|
|
145378
|
-
}
|
|
145379
|
-
}
|
|
145380
|
-
/**
|
|
145381
|
-
* The camstack `fanSpeed` token for a Dreame {@link SuctionLevel} — the enum
|
|
145382
|
-
* member NAME, lower-cased (`Quiet` → `'quiet'`). Returns `null` for an
|
|
145383
|
-
* unobserved / unknown level.
|
|
145384
|
-
*/
|
|
145385
|
-
function suctionLevelToken(level) {
|
|
145386
|
-
if (level === null) return null;
|
|
145387
|
-
const name = SuctionLevel[level];
|
|
145388
|
-
return typeof name === "string" ? name.toLowerCase() : null;
|
|
145389
|
-
}
|
|
145390
|
-
/**
|
|
145391
|
-
* Resolve a camstack `fanSpeed` token back to the Dreame {@link SuctionLevel}
|
|
145392
|
-
* enum value, validated against the model's `supported` levels. Returns null
|
|
145393
|
-
* when the token doesn't match a supported level (the caller rejects the set).
|
|
145394
|
-
*/
|
|
145395
|
-
function tokenToSuctionLevel(token, supported) {
|
|
145396
|
-
const wanted = token.trim().toLowerCase();
|
|
145397
|
-
for (const level of supported) if (suctionLevelToken(level) === wanted) return level;
|
|
145398
|
-
return null;
|
|
145399
|
-
}
|
|
145400
|
-
/** The list of accepted `fanSpeed` tokens for a model's supported suction set. */
|
|
145401
|
-
function supportedFanSpeedTokens(supported) {
|
|
145402
|
-
const tokens = [];
|
|
145403
|
-
for (const level of supported) {
|
|
145404
|
-
const token = suctionLevelToken(level);
|
|
145405
|
-
if (token !== null) tokens.push(token);
|
|
145406
|
-
}
|
|
145407
|
-
return tokens;
|
|
145408
|
-
}
|
|
145409
|
-
/**
|
|
145410
|
-
* Clamp a raw battery reading (already a 0..100 percentage from the library)
|
|
145411
|
-
* to the `vacuum-control` slice's `batteryLevel` field. Returns null for a
|
|
145412
|
-
* null/NaN/out-of-band reading so the slice stays valid (the schema rejects
|
|
145413
|
-
* a number outside 0..100).
|
|
145414
|
-
*/
|
|
145415
|
-
function clampBatteryLevel$1(value) {
|
|
145416
|
-
if (value === null || !Number.isFinite(value)) return null;
|
|
145417
|
-
return Math.max(0, Math.min(100, value));
|
|
145418
|
-
}
|
|
145419
|
-
/** Clamp a 0..100 percentage (progress, coverage, …), null-safe. */
|
|
145420
|
-
function clampPercent$1(value) {
|
|
145421
|
-
if (value === null || !Number.isFinite(value)) return null;
|
|
145422
|
-
return Math.max(0, Math.min(100, value));
|
|
145423
|
-
}
|
|
145424
|
-
/**
|
|
145425
|
-
* Where each {@link VacuumTankProps} field lives (siid/piid). Property ids and
|
|
145426
|
-
* enum values are the HA Dreame integration's (Tasshack `dreame-vacuum`
|
|
145427
|
-
* `types.py`, `DreameVacuumProperty` → siid/piid map; same on `dev` and
|
|
145428
|
-
* v2.0.0b25, checked 2026-09-26). 27/1 and 27/4 were already read here; 27/2 and
|
|
145429
|
-
* 27/3 are their neighbours on the same station service. The robot pushes them
|
|
145430
|
-
* on change; the consumables refresh also re-reads them from the shadow.
|
|
145431
|
-
*/
|
|
145432
|
-
var VACUUM_TANK_PROPS = {
|
|
145433
|
-
cleanWaterTankStatus: {
|
|
145434
|
-
siid: 27,
|
|
145435
|
-
piid: 1
|
|
145436
|
-
},
|
|
145437
|
-
waterTank: {
|
|
145438
|
-
siid: 4,
|
|
145439
|
-
piid: 6
|
|
145440
|
-
},
|
|
145441
|
-
detergentStatus: {
|
|
145442
|
-
siid: 27,
|
|
145443
|
-
piid: 4
|
|
145444
|
-
},
|
|
145445
|
-
dustCollection: {
|
|
145446
|
-
siid: 15,
|
|
145447
|
-
piid: 3
|
|
145448
|
-
},
|
|
145449
|
-
dirtyWaterTankStatus: {
|
|
145450
|
-
siid: 27,
|
|
145451
|
-
piid: 2
|
|
145452
|
-
},
|
|
145453
|
-
dustBagStatus: {
|
|
145454
|
-
siid: 27,
|
|
145455
|
-
piid: 3
|
|
145456
|
-
}
|
|
145457
|
-
};
|
|
145458
|
-
/**
|
|
145459
|
-
* Clean-water tank `status`: prefer the richer clean-water-tank-status (27/1),
|
|
145460
|
-
* fall back to the coarser water-tank presence (4/6). `null` when neither maps
|
|
145461
|
-
* to a known good/low reading (NOT_INSTALLED / CHECKING / unknown). Pure.
|
|
145462
|
-
*/
|
|
145463
|
-
function cleanWaterTankStatus(props) {
|
|
145464
|
-
if (props.cleanWaterTankStatus !== null) {
|
|
145465
|
-
if (props.cleanWaterTankStatus === 0) return "ok";
|
|
145466
|
-
if (props.cleanWaterTankStatus === 2) return "low";
|
|
145467
|
-
return null;
|
|
145468
|
-
}
|
|
145469
|
-
if (props.waterTank === 1 || props.waterTank === 10) return "ok";
|
|
145470
|
-
return null;
|
|
145471
|
-
}
|
|
145472
|
-
/** Detergent tank `status` from detergent-status (27/4). Pure. */
|
|
145473
|
-
function detergentTankStatus(value) {
|
|
145474
|
-
if (value === 0) return "ok";
|
|
145475
|
-
if (value === 2) return "low";
|
|
145476
|
-
return null;
|
|
145477
|
-
}
|
|
145478
|
-
/**
|
|
145479
|
-
* Dust-bin `status` from dust-collection (15/3); OVER_USE reads as `full`. A dust
|
|
145480
|
-
* bag that is missing or asks to be checked (27/3 NOT_INSTALLED / CHECK) is never
|
|
145481
|
-
* `ok` — the enum does not say "full" either, so it is unknown. Pure.
|
|
145482
|
-
*/
|
|
145483
|
-
function dustBinTankStatus(dustCollection, dustBag) {
|
|
145484
|
-
if (dustCollection === 2) return "full";
|
|
145485
|
-
if (dustBag !== null && dustBag !== 0) return null;
|
|
145486
|
-
if (dustCollection === 1) return "ok";
|
|
145487
|
-
return null;
|
|
145488
|
-
}
|
|
145489
|
-
/**
|
|
145490
|
-
* Dirty-water tank `status` from dirty-water-tank-status (27/2). The robot has
|
|
145491
|
-
* ONE value for "not installed or full" and cannot tell them apart; lifting the
|
|
145492
|
-
* tank out to EMPTY it reads exactly like a full one, so that value is unknown
|
|
145493
|
-
* (`null`) — never `full`, which would raise a false "tank full" at the moment
|
|
145494
|
-
* someone is fixing it (controller ruling 2026-09-26). Absent or any other
|
|
145495
|
-
* value is unknown too, never `ok`.
|
|
145496
|
-
*
|
|
145497
|
-
* The unambiguous "full" is the robot's own error `MiotError.DirtyWaterTankFull`
|
|
145498
|
-
* (86, the library's `errorCode`, siid 2 piid 2): while it is raised the tank is
|
|
145499
|
-
* `full` whatever 27/2 says; once it clears, 27/2 decides again. Pure.
|
|
145500
|
-
*/
|
|
145501
|
-
function dirtyWaterTankStatus(value, errorCode) {
|
|
145502
|
-
if (errorCode === MiotError.DirtyWaterTankFull) return "full";
|
|
145503
|
-
if (value === 0) return "ok";
|
|
145504
|
-
return null;
|
|
145505
|
-
}
|
|
145506
|
-
/**
|
|
145507
|
-
* The four tanks (clean-water / dirty-water / detergent / dust-bin) decoded from the model's {@link VacuumCapabilities}, with the
|
|
145508
|
-
* discrete `status` populated from the live MIoT enum props when supplied. Pure.
|
|
145509
|
-
*
|
|
145510
|
-
* The nodedreame library exposes NO tank FILL-LEVEL telemetry — the cloud shadow
|
|
145511
|
-
* (verified against a real `dreame.vacuum.r2538z`) carries only the water-VOLUME
|
|
145512
|
-
* *setting* (a mop-flow level, NOT a tank fill). We therefore never fabricate a
|
|
145513
|
-
* `level`; `level` is always `null`. The cap contract is honoured on PRESENCE,
|
|
145514
|
-
* with `status` derived from the tank-status enums:
|
|
145515
|
-
*
|
|
145516
|
-
* - a field is `null` when the hardware HAS NO such tank (capability flags), and
|
|
145517
|
-
* - `{ level: null, status }` when the tank EXISTS — `status` from the enum
|
|
145518
|
-
* prop ('ok' / 'low' / 'full'), or `null` when the device reports none.
|
|
145519
|
-
*
|
|
145520
|
-
* Presence is derived from the capability record:
|
|
145521
|
-
* - clean-water ← `canMop` (a mopping robot carries a clean-water reservoir)
|
|
145522
|
-
* - dirty-water ← `canMopWash` (a self-wash dock has a recovery/dirty tank)
|
|
145523
|
-
* - detergent ← `hasDetergentReservoir`
|
|
145524
|
-
* - dust-bin ← `canAutoEmpty` (an auto-empty robot has an onboard dust bin)
|
|
145525
|
-
*/
|
|
145526
|
-
function decodeVacuumTanks(caps, props, errorCode = null) {
|
|
145527
|
-
const tank = (present, status) => present ? {
|
|
145528
|
-
level: null,
|
|
145529
|
-
status
|
|
145530
|
-
} : null;
|
|
145531
|
-
return {
|
|
145532
|
-
cleanWater: tank(Boolean(caps?.canMop), props ? cleanWaterTankStatus(props) : null),
|
|
145533
|
-
dirtyWater: tank(Boolean(caps?.canMopWash), dirtyWaterTankStatus(props?.dirtyWaterTankStatus ?? null, errorCode)),
|
|
145534
|
-
detergent: tank(Boolean(caps?.hasDetergentReservoir), props ? detergentTankStatus(props.detergentStatus) : null),
|
|
145535
|
-
dustBin: tank(Boolean(caps?.canAutoEmpty), props ? dustBinTankStatus(props.dustCollection, props.dustBagStatus) : null)
|
|
145536
|
-
};
|
|
145537
|
-
}
|
|
145538
|
-
/** How each tank is named and what it is, in the order the panel lists them. */
|
|
145539
|
-
var TANK_ITEMS = [
|
|
145540
|
-
{
|
|
145541
|
-
key: "cleanWater",
|
|
145542
|
-
label: "Clean water tank",
|
|
145543
|
-
kind: "supply",
|
|
145544
|
-
form: "tank"
|
|
145545
|
-
},
|
|
145546
|
-
{
|
|
145547
|
-
key: "dirtyWater",
|
|
145548
|
-
label: "Dirty water tank",
|
|
145549
|
-
kind: "waste",
|
|
145550
|
-
form: "tank"
|
|
145551
|
-
},
|
|
145552
|
-
{
|
|
145553
|
-
key: "detergent",
|
|
145554
|
-
label: "Detergent",
|
|
145555
|
-
kind: "supply",
|
|
145556
|
-
form: "product"
|
|
145557
|
-
},
|
|
145558
|
-
{
|
|
145559
|
-
key: "dustBin",
|
|
145560
|
-
label: "Dust bin",
|
|
145561
|
-
kind: "waste",
|
|
145562
|
-
form: "bin"
|
|
145563
|
-
}
|
|
145564
|
-
];
|
|
145565
|
-
/**
|
|
145566
|
-
* The tanks as `consumables` items (D915): every PRESENT tank becomes a
|
|
145567
|
-
* `supply` (clean water, detergent) or `waste` (dirty water, dust bin) item
|
|
145568
|
-
* keyed by its old field name, drawn as a `tank` (the two water tanks), a
|
|
145569
|
-
* `product` (detergent) or a `bin` (the dust bin) — the key the notification centre's tank
|
|
145570
|
-
* episodes were built on, so an open episode carries on. A tank is never
|
|
145571
|
-
* resettable: emptying or refilling it is the reset. Pure.
|
|
145572
|
-
*/
|
|
145573
|
-
function vacuumTankItems(tanks) {
|
|
145574
|
-
return TANK_ITEMS.flatMap(({ key, label, kind, form }) => {
|
|
145575
|
-
const tank = tanks[key];
|
|
145576
|
-
if (tank === null) return [];
|
|
145577
|
-
return [{
|
|
145578
|
-
key,
|
|
145579
|
-
label,
|
|
145580
|
-
level: tank.level,
|
|
145581
|
-
status: tank.status,
|
|
145582
|
-
kind,
|
|
145583
|
-
form,
|
|
145584
|
-
lastResetAt: null,
|
|
145585
|
-
resettable: false
|
|
145586
|
-
}];
|
|
145587
|
-
});
|
|
145588
|
-
}
|
|
145589
|
-
/**
|
|
145590
|
-
* Clamp a raw consumable percentage to `[0, 100]`, or return `null` when the
|
|
145591
|
-
* value is absent / non-finite. The library reports remaining life as a 0..100
|
|
145592
|
-
* integer; we clamp defensively so an out-of-range firmware value never violates
|
|
145593
|
-
* the cap schema.
|
|
145594
|
-
*/
|
|
145595
|
-
function clampConsumablePct(value) {
|
|
145596
|
-
if (value === null || !Number.isFinite(value)) return null;
|
|
145597
|
-
return Math.max(0, Math.min(100, value));
|
|
145598
|
-
}
|
|
145599
|
-
/**
|
|
145600
|
-
* The `consumables` cap items for the consumables a vacuum REPORTS.
|
|
145601
|
-
*
|
|
145602
|
-
* PRESENCE-driven (mirrors HA): the library's `supportedConsumables` enumerates
|
|
145603
|
-
* ONLY the consumables this model reports (a present MIoT life property), each
|
|
145604
|
-
* with its remaining-life % and whether a reset action exists. No hardcoded
|
|
145605
|
-
* per-model list: a consumable the device does not expose never appears.
|
|
145606
|
-
*
|
|
145607
|
-
* `status` is `null` — the provider reports the level, the notification rule
|
|
145608
|
-
* decides what is low. The slice clocks are `nextConsumablesSlice`'s job. Pure.
|
|
145609
|
-
*/
|
|
145610
|
-
function buildVacuumConsumableItems(readings) {
|
|
145611
|
-
return readings.map((c) => ({
|
|
145612
|
-
key: c.key,
|
|
145613
|
-
label: c.label,
|
|
145614
|
-
level: clampConsumablePct(c.leftPct),
|
|
145615
|
-
status: null,
|
|
145616
|
-
lastResetAt: null,
|
|
145617
|
-
resettable: c.resettable,
|
|
145618
|
-
form: "part"
|
|
145619
|
-
}));
|
|
145620
|
-
}
|
|
145621
|
-
/** Structural equality of two JSON-shaped values, key order ignored. */
|
|
145622
|
-
function jsonEqual(a, b) {
|
|
145623
|
-
if (a === b) return true;
|
|
145624
|
-
if (Array.isArray(a) || Array.isArray(b)) return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((v, i) => jsonEqual(v, b[i]));
|
|
145625
|
-
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
|
|
145626
|
-
const ka = Object.keys(a);
|
|
145627
|
-
const kb = Object.keys(b);
|
|
145628
|
-
return ka.length === kb.length && ka.every((k) => jsonEqual(Reflect.get(a, k), Reflect.get(b, k)));
|
|
145629
|
-
}
|
|
145630
|
-
/**
|
|
145631
|
-
* The `vacuum-control` slice to write, or `null` when only the clock moved.
|
|
145632
|
-
* `recomputeSlice` runs on EVERY push; without this each one was a write (and a
|
|
145633
|
-
* `device.state-changed`) even when nothing the robot reported had changed. Pure.
|
|
145634
|
-
*/
|
|
145635
|
-
function nextVacuumControlSlice(prev, next) {
|
|
145636
|
-
if (prev === void 0) return next;
|
|
145637
|
-
const { lastChangedAt: _prevAt, ...prevContent } = prev;
|
|
145638
|
-
const { lastChangedAt: _nextAt, ...nextContent } = next;
|
|
145639
|
-
return jsonEqual(prevContent, nextContent) ? null : next;
|
|
145640
|
-
}
|
|
145641
|
-
//#endregion
|
|
145642
146226
|
//#region src/devices/dreame-camera-stream.ts
|
|
145643
146227
|
/**
|
|
145644
146228
|
* The robot camera as a stream the broker can pull — the pure facts.
|
|
@@ -145673,7 +146257,7 @@ function clampLevel(level) {
|
|
|
145673
146257
|
function cameraBatterySlice(facts) {
|
|
145674
146258
|
const charging = facts.charging === null ? "unknown" : facts.charging === CHARGING_STATUS_CHARGING ? "dc" : "none";
|
|
145675
146259
|
return {
|
|
145676
|
-
|
|
146260
|
+
level: clampLevel(facts.level),
|
|
145677
146261
|
charging,
|
|
145678
146262
|
sleeping: !facts.streamOpen,
|
|
145679
146263
|
lastUpdated: facts.now,
|
|
@@ -146082,7 +146666,7 @@ function networkLinkFromBars(bars, now) {
|
|
|
146082
146666
|
if (bars === null || !Number.isFinite(bars)) return null;
|
|
146083
146667
|
return {
|
|
146084
146668
|
type: "wifi",
|
|
146085
|
-
|
|
146669
|
+
signal: bars === 0 ? null : signalPercentFromBars(bars, 4),
|
|
146086
146670
|
lastUpdated: now
|
|
146087
146671
|
};
|
|
146088
146672
|
}
|
|
@@ -193285,7 +193869,7 @@ var SHADOW_POLL_MS = 12e4;
|
|
|
193285
193869
|
var COLD_START = {
|
|
193286
193870
|
activity: "idle",
|
|
193287
193871
|
batteryLevel: null,
|
|
193288
|
-
|
|
193872
|
+
progress: null,
|
|
193289
193873
|
currentCode: null,
|
|
193290
193874
|
currentCodeLabel: null,
|
|
193291
193875
|
severity: "info",
|
|
@@ -193399,11 +193983,11 @@ var DreameMowerDevice = class extends DreameChildDevice {
|
|
|
193399
193983
|
const status = mower.status;
|
|
193400
193984
|
const code = mower.faultRaw;
|
|
193401
193985
|
const livePct = clampPercent(mower.mowingProgressPct);
|
|
193402
|
-
const prevPct = this.runtimeState.getCapState(CAP_NAME$9)?.
|
|
193986
|
+
const prevPct = this.runtimeState.getCapState(CAP_NAME$9)?.progress;
|
|
193403
193987
|
const slice = {
|
|
193404
193988
|
activity: mapMowerStatusToActivity(status),
|
|
193405
193989
|
batteryLevel: clampBatteryLevel(mower.battery),
|
|
193406
|
-
|
|
193990
|
+
progress: livePct ?? prevPct ?? null,
|
|
193407
193991
|
currentCode: code,
|
|
193408
193992
|
currentCodeLabel: code !== null ? MowerFault[code] ?? null : null,
|
|
193409
193993
|
severity: mower.faultSeverity,
|
|
@@ -194343,7 +194927,7 @@ var CONTROL_COLD_START = {
|
|
|
194343
194927
|
batteryLevel: null,
|
|
194344
194928
|
fanSpeed: null,
|
|
194345
194929
|
availableFanSpeeds: [],
|
|
194346
|
-
|
|
194930
|
+
progress: null,
|
|
194347
194931
|
errorCode: null,
|
|
194348
194932
|
errorLabel: null,
|
|
194349
194933
|
lastChangedAt: 0
|
|
@@ -194497,9 +195081,9 @@ var DreameVacuumDevice = class extends DreameChildDevice {
|
|
|
194497
195081
|
batteryLevel: clampBatteryLevel$1(vacuum.battery),
|
|
194498
195082
|
fanSpeed: suctionLevelToken(vacuum.suction),
|
|
194499
195083
|
availableFanSpeeds: supportedFanSpeedTokens(vacuum.supportedSuctionLevels),
|
|
194500
|
-
|
|
195084
|
+
progress: clampPercent$1(vacuum.taskProgressPct),
|
|
194501
195085
|
errorCode: vacuum.errorCode,
|
|
194502
|
-
errorLabel: vacuum.errorCode
|
|
195086
|
+
errorLabel: vacuumErrorLabel(vacuum.errorCode),
|
|
194503
195087
|
lastChangedAt: Date.now()
|
|
194504
195088
|
};
|
|
194505
195089
|
const toWrite = nextVacuumControlSlice(this.runtimeState.getCapState(CAP_NAME$1), controlSlice);
|