@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.mjs
CHANGED
|
@@ -52899,7 +52899,7 @@ var DreameCameraStream = class extends TypedEmitter {
|
|
|
52899
52899
|
}
|
|
52900
52900
|
};
|
|
52901
52901
|
//#endregion
|
|
52902
|
-
//#region ../types/dist/event-category-
|
|
52902
|
+
//#region ../types/dist/event-category-DmZDPDf_.mjs
|
|
52903
52903
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
52904
52904
|
EventCategory["SystemBoot"] = "system.boot";
|
|
52905
52905
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -53526,7 +53526,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
53526
53526
|
* (a track was promoted to a stationary-registry entry) or departs (the
|
|
53527
53527
|
* object moved / was removed). Telemetry (D8): lossy, drives a UI refresh of
|
|
53528
53528
|
* the dedicated "Stationary" section — never the live event feed. Payload:
|
|
53529
|
-
* `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp }`.
|
|
53529
|
+
* `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp, observedAt, zones }`.
|
|
53530
53530
|
*/
|
|
53531
53531
|
EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
|
|
53532
53532
|
/**
|
|
@@ -53664,6 +53664,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
53664
53664
|
*/
|
|
53665
53665
|
EventCategory["AccessoriesChanged"] = "accessories.onAccessoriesChanged";
|
|
53666
53666
|
/**
|
|
53667
|
+
* An action run started or ended. Emitted by the `actions` surface.
|
|
53668
|
+
* Payload: `ActionRunChanged` (`{ actionId, runId, isRunning, outcome?, lastRunAt, lastError }`).
|
|
53669
|
+
*/
|
|
53670
|
+
EventCategory["ActionsRunChanged"] = "actions.onRunChanged";
|
|
53671
|
+
/**
|
|
53667
53672
|
* Progress update from a running model conversion job.
|
|
53668
53673
|
* Payload: `{ kind: 'model-convert', phase, sessionId?, pct?, detail? }`.
|
|
53669
53674
|
* Emitted by `addon-model-studio` on the converting node.
|
|
@@ -58342,7 +58347,7 @@ var ZodIssueCode = {
|
|
|
58342
58347
|
var ZodFirstPartyTypeKind;
|
|
58343
58348
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
58344
58349
|
//#endregion
|
|
58345
|
-
//#region ../types/dist/sleep-
|
|
58350
|
+
//#region ../types/dist/sleep-Crlmu_tO.mjs
|
|
58346
58351
|
/**
|
|
58347
58352
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
58348
58353
|
* window to float samples (D455).
|
|
@@ -61462,6 +61467,8 @@ Object.freeze({ "doorbell": "lastPressedAt" });
|
|
|
61462
61467
|
/** Every capability name, so addon code can check a name without the schema graph. */
|
|
61463
61468
|
var CAP_NAMES = Object.freeze([
|
|
61464
61469
|
"accessories",
|
|
61470
|
+
"action-source",
|
|
61471
|
+
"actions",
|
|
61465
61472
|
"addon-pages",
|
|
61466
61473
|
"addon-pages-source",
|
|
61467
61474
|
"addon-routes",
|
|
@@ -61650,11 +61657,11 @@ var CAP_NAMES = Object.freeze([
|
|
|
61650
61657
|
Object.freeze({
|
|
61651
61658
|
"air-quality-sensor": [
|
|
61652
61659
|
{
|
|
61653
|
-
"path": "
|
|
61660
|
+
"path": "co2",
|
|
61654
61661
|
"valueType": "number"
|
|
61655
61662
|
},
|
|
61656
61663
|
{
|
|
61657
|
-
"path": "
|
|
61664
|
+
"path": "voc",
|
|
61658
61665
|
"valueType": "number"
|
|
61659
61666
|
},
|
|
61660
61667
|
{
|
|
@@ -61704,7 +61711,7 @@ Object.freeze({
|
|
|
61704
61711
|
}
|
|
61705
61712
|
],
|
|
61706
61713
|
"ambient-light-sensor": [{
|
|
61707
|
-
"path": "
|
|
61714
|
+
"path": "illuminance",
|
|
61708
61715
|
"valueType": "number"
|
|
61709
61716
|
}],
|
|
61710
61717
|
"audio-metrics": [
|
|
@@ -61765,7 +61772,7 @@ Object.freeze({
|
|
|
61765
61772
|
],
|
|
61766
61773
|
"battery": [
|
|
61767
61774
|
{
|
|
61768
|
-
"path": "
|
|
61775
|
+
"path": "level",
|
|
61769
61776
|
"valueType": "number"
|
|
61770
61777
|
},
|
|
61771
61778
|
{
|
|
@@ -61787,7 +61794,7 @@ Object.freeze({
|
|
|
61787
61794
|
"valueType": "number"
|
|
61788
61795
|
},
|
|
61789
61796
|
{
|
|
61790
|
-
"path": "
|
|
61797
|
+
"path": "voltage",
|
|
61791
61798
|
"valueType": "number"
|
|
61792
61799
|
},
|
|
61793
61800
|
{
|
|
@@ -61808,7 +61815,7 @@ Object.freeze({
|
|
|
61808
61815
|
"valueType": "boolean"
|
|
61809
61816
|
}],
|
|
61810
61817
|
"brightness": [{
|
|
61811
|
-
"path": "
|
|
61818
|
+
"path": "level",
|
|
61812
61819
|
"valueType": "number"
|
|
61813
61820
|
}],
|
|
61814
61821
|
"broker": [{
|
|
@@ -62009,7 +62016,7 @@ Object.freeze({
|
|
|
62009
62016
|
"valueType": "number"
|
|
62010
62017
|
},
|
|
62011
62018
|
{
|
|
62012
|
-
"path": "
|
|
62019
|
+
"path": "colorTemperature",
|
|
62013
62020
|
"valueType": "number"
|
|
62014
62021
|
}
|
|
62015
62022
|
],
|
|
@@ -62188,15 +62195,15 @@ Object.freeze({
|
|
|
62188
62195
|
"valueType": "string"
|
|
62189
62196
|
},
|
|
62190
62197
|
{
|
|
62191
|
-
"path": "
|
|
62198
|
+
"path": "remaining",
|
|
62192
62199
|
"valueType": "number"
|
|
62193
62200
|
},
|
|
62194
62201
|
{
|
|
62195
|
-
"path": "
|
|
62202
|
+
"path": "delayedStart",
|
|
62196
62203
|
"valueType": "number"
|
|
62197
62204
|
},
|
|
62198
62205
|
{
|
|
62199
|
-
"path": "
|
|
62206
|
+
"path": "progress",
|
|
62200
62207
|
"valueType": "number"
|
|
62201
62208
|
},
|
|
62202
62209
|
{
|
|
@@ -62310,7 +62317,7 @@ Object.freeze({
|
|
|
62310
62317
|
],
|
|
62311
62318
|
"fan-control": [
|
|
62312
62319
|
{
|
|
62313
|
-
"path": "
|
|
62320
|
+
"path": "speed",
|
|
62314
62321
|
"valueType": "number"
|
|
62315
62322
|
},
|
|
62316
62323
|
{
|
|
@@ -62410,7 +62417,7 @@ Object.freeze({
|
|
|
62410
62417
|
}
|
|
62411
62418
|
],
|
|
62412
62419
|
"humidity-sensor": [{
|
|
62413
|
-
"path": "
|
|
62420
|
+
"path": "humidity",
|
|
62414
62421
|
"valueType": "number"
|
|
62415
62422
|
}],
|
|
62416
62423
|
"image": [{
|
|
@@ -62520,7 +62527,7 @@ Object.freeze({
|
|
|
62520
62527
|
"valueType": "number"
|
|
62521
62528
|
},
|
|
62522
62529
|
{
|
|
62523
|
-
"path": "
|
|
62530
|
+
"path": "progress",
|
|
62524
62531
|
"valueType": "number"
|
|
62525
62532
|
},
|
|
62526
62533
|
{
|
|
@@ -62599,11 +62606,11 @@ Object.freeze({
|
|
|
62599
62606
|
"valueType": "string"
|
|
62600
62607
|
},
|
|
62601
62608
|
{
|
|
62602
|
-
"path": "
|
|
62609
|
+
"path": "position",
|
|
62603
62610
|
"valueType": "number"
|
|
62604
62611
|
},
|
|
62605
62612
|
{
|
|
62606
|
-
"path": "
|
|
62613
|
+
"path": "duration",
|
|
62607
62614
|
"valueType": "number"
|
|
62608
62615
|
},
|
|
62609
62616
|
{
|
|
@@ -62705,11 +62712,11 @@ Object.freeze({
|
|
|
62705
62712
|
]
|
|
62706
62713
|
},
|
|
62707
62714
|
{
|
|
62708
|
-
"path": "
|
|
62715
|
+
"path": "signal",
|
|
62709
62716
|
"valueType": "number"
|
|
62710
62717
|
},
|
|
62711
62718
|
{
|
|
62712
|
-
"path": "
|
|
62719
|
+
"path": "rssi",
|
|
62713
62720
|
"valueType": "number"
|
|
62714
62721
|
},
|
|
62715
62722
|
{
|
|
@@ -62840,23 +62847,23 @@ Object.freeze({
|
|
|
62840
62847
|
],
|
|
62841
62848
|
"power-meter": [
|
|
62842
62849
|
{
|
|
62843
|
-
"path": "
|
|
62850
|
+
"path": "power",
|
|
62844
62851
|
"valueType": "number"
|
|
62845
62852
|
},
|
|
62846
62853
|
{
|
|
62847
|
-
"path": "
|
|
62854
|
+
"path": "energy",
|
|
62848
62855
|
"valueType": "number"
|
|
62849
62856
|
},
|
|
62850
62857
|
{
|
|
62851
|
-
"path": "
|
|
62858
|
+
"path": "voltage",
|
|
62852
62859
|
"valueType": "number"
|
|
62853
62860
|
},
|
|
62854
62861
|
{
|
|
62855
|
-
"path": "
|
|
62862
|
+
"path": "current",
|
|
62856
62863
|
"valueType": "number"
|
|
62857
62864
|
},
|
|
62858
62865
|
{
|
|
62859
|
-
"path": "
|
|
62866
|
+
"path": "apparentPower",
|
|
62860
62867
|
"valueType": "number"
|
|
62861
62868
|
},
|
|
62862
62869
|
{
|
|
@@ -62884,14 +62891,10 @@ Object.freeze({
|
|
|
62884
62891
|
{
|
|
62885
62892
|
"path": "gps.accuracyMeters",
|
|
62886
62893
|
"valueType": "number"
|
|
62887
|
-
},
|
|
62888
|
-
{
|
|
62889
|
-
"path": "batteryPercent",
|
|
62890
|
-
"valueType": "number"
|
|
62891
62894
|
}
|
|
62892
62895
|
],
|
|
62893
62896
|
"pressure-sensor": [{
|
|
62894
|
-
"path": "
|
|
62897
|
+
"path": "pressure",
|
|
62895
62898
|
"valueType": "number"
|
|
62896
62899
|
}],
|
|
62897
62900
|
"privacy-mask": [{
|
|
@@ -63205,7 +63208,7 @@ Object.freeze({
|
|
|
63205
63208
|
"valueType": "boolean"
|
|
63206
63209
|
}],
|
|
63207
63210
|
"temperature-sensor": [{
|
|
63208
|
-
"path": "
|
|
63211
|
+
"path": "temperature",
|
|
63209
63212
|
"valueType": "number"
|
|
63210
63213
|
}],
|
|
63211
63214
|
"update": [
|
|
@@ -63253,7 +63256,7 @@ Object.freeze({
|
|
|
63253
63256
|
"valueType": "string"
|
|
63254
63257
|
},
|
|
63255
63258
|
{
|
|
63256
|
-
"path": "
|
|
63259
|
+
"path": "progress",
|
|
63257
63260
|
"valueType": "number"
|
|
63258
63261
|
},
|
|
63259
63262
|
{
|
|
@@ -63752,7 +63755,7 @@ function errMsg(err) {
|
|
|
63752
63755
|
return String(err);
|
|
63753
63756
|
}
|
|
63754
63757
|
//#endregion
|
|
63755
|
-
//#region ../types/dist/composition-
|
|
63758
|
+
//#region ../types/dist/composition-CZImLpOI.mjs
|
|
63756
63759
|
/**
|
|
63757
63760
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
63758
63761
|
*
|
|
@@ -68516,6 +68519,14 @@ var AutomationConditionLeafSchema = object({
|
|
|
68516
68519
|
array(union([string(), number()]))
|
|
68517
68520
|
])
|
|
68518
68521
|
});
|
|
68522
|
+
var AutomationConditionStationaryPresentSchema = object({
|
|
68523
|
+
kind: literal("stationary-present"),
|
|
68524
|
+
deviceId: number().int().nonnegative(),
|
|
68525
|
+
/** Class names; a macro class matches every raw class that maps to it. */
|
|
68526
|
+
classes: array(string().min(1)).min(1).optional(),
|
|
68527
|
+
/** The parked object must be in at least one of these zones. */
|
|
68528
|
+
zones: array(string().min(1)).min(1).optional()
|
|
68529
|
+
});
|
|
68519
68530
|
/**
|
|
68520
68531
|
* The expression leaf, declared as a plain object rather than an intersection
|
|
68521
68532
|
* with {@link ExpressionSourceSchema}: a discriminated union has to be able to
|
|
@@ -68549,7 +68560,8 @@ var AutomationConditionSchema = lazy(() => discriminatedUnion("kind", [
|
|
|
68549
68560
|
child: AutomationConditionSchema
|
|
68550
68561
|
}),
|
|
68551
68562
|
AutomationConditionLeafSchema,
|
|
68552
|
-
AutomationConditionExpressionSchema
|
|
68563
|
+
AutomationConditionExpressionSchema,
|
|
68564
|
+
AutomationConditionStationaryPresentSchema
|
|
68553
68565
|
]));
|
|
68554
68566
|
/**
|
|
68555
68567
|
* What starts a run.
|
|
@@ -68590,7 +68602,34 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
|
|
|
68590
68602
|
kind: literal("schedule"),
|
|
68591
68603
|
cron: string().min(1).max(120)
|
|
68592
68604
|
}),
|
|
68593
|
-
object({ kind: literal("manual") })
|
|
68605
|
+
object({ kind: literal("manual") }),
|
|
68606
|
+
object({
|
|
68607
|
+
kind: literal("detection"),
|
|
68608
|
+
/** Class names. A macro class (`animal`, `vehicle`, `person`, `package`)
|
|
68609
|
+
* matches every raw class that maps to it; any other name matches
|
|
68610
|
+
* exactly. */
|
|
68611
|
+
classes: array(string().min(1)).min(1),
|
|
68612
|
+
deviceIds: array(number().int().nonnegative()).min(1),
|
|
68613
|
+
/** The track must have visited at least one of these zones. */
|
|
68614
|
+
zones: array(string().min(1)).min(1).optional(),
|
|
68615
|
+
/** Best-so-far confidence the track must reach. */
|
|
68616
|
+
minScore: number().min(0).max(1).optional()
|
|
68617
|
+
}),
|
|
68618
|
+
object({
|
|
68619
|
+
kind: literal("stationary"),
|
|
68620
|
+
deviceIds: array(number().int().nonnegative()).min(1),
|
|
68621
|
+
/** Class names; a macro class matches like the detection trigger. */
|
|
68622
|
+
classes: array(string().min(1)).min(1).optional(),
|
|
68623
|
+
/** The parked object must be in at least one of these zones. */
|
|
68624
|
+
zones: array(string().min(1)).min(1).optional(),
|
|
68625
|
+
/** `appeared`: it became parked. `departed`: the registry retired it.
|
|
68626
|
+
* The vocabulary of `PipelineAnalyticsStationaryChanged`. */
|
|
68627
|
+
phase: _enum(["appeared", "departed"])
|
|
68628
|
+
}),
|
|
68629
|
+
object({
|
|
68630
|
+
kind: literal("interval"),
|
|
68631
|
+
minutes: number().int().min(1).max(1440)
|
|
68632
|
+
})
|
|
68594
68633
|
]);
|
|
68595
68634
|
/**
|
|
68596
68635
|
* One action step.
|
|
@@ -68610,28 +68649,47 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
|
|
|
68610
68649
|
* is called. The runner still receives a number and is untouched; the
|
|
68611
68650
|
* resolution is the recipe's job, not the runner's.
|
|
68612
68651
|
*/
|
|
68652
|
+
var AutomationWaitStepSchema = object({
|
|
68653
|
+
kind: literal("wait"),
|
|
68654
|
+
seconds: number().min(0).max(300)
|
|
68655
|
+
});
|
|
68656
|
+
var AutomationCapStepSchema = object({
|
|
68657
|
+
kind: literal("cap"),
|
|
68658
|
+
/**
|
|
68659
|
+
* One device, several same-family devices (locks / climate family / …),
|
|
68660
|
+
* or `{ $var }` for the device that triggered the run. An array is
|
|
68661
|
+
* fan-out at codegen — each id gets the same cap.method call.
|
|
68662
|
+
*/
|
|
68663
|
+
deviceId: union([
|
|
68664
|
+
number().int(),
|
|
68665
|
+
array(number().int()).min(1),
|
|
68666
|
+
object({ $var: string().min(1) })
|
|
68667
|
+
]),
|
|
68668
|
+
cap: string().min(1),
|
|
68669
|
+
method: string().min(1),
|
|
68670
|
+
/** Values may carry `{{vars.x}}` slots, or be exactly `{ $var: 'name' }`, which
|
|
68671
|
+
* SUBSTITUTE and do not evaluate (§3.2.3). Anything beyond substitution is
|
|
68672
|
+
* the expression leaf. */
|
|
68673
|
+
args: record(string(), unknown()).optional()
|
|
68674
|
+
});
|
|
68675
|
+
/**
|
|
68676
|
+
* The steps an internal Action may contain: cap and wait, and nothing that can
|
|
68677
|
+
* call back into an Action — so an Action can never reach itself (no cycles).
|
|
68678
|
+
*/
|
|
68679
|
+
var ActionStepSchema = discriminatedUnion("kind", [AutomationWaitStepSchema, AutomationCapStepSchema]);
|
|
68613
68680
|
var AutomationActionSchema = discriminatedUnion("kind", [
|
|
68681
|
+
AutomationWaitStepSchema,
|
|
68682
|
+
AutomationCapStepSchema,
|
|
68614
68683
|
object({
|
|
68615
|
-
kind: literal("
|
|
68616
|
-
|
|
68617
|
-
|
|
68618
|
-
|
|
68619
|
-
|
|
68620
|
-
|
|
68621
|
-
|
|
68622
|
-
|
|
68623
|
-
|
|
68624
|
-
*/
|
|
68625
|
-
deviceId: union([
|
|
68626
|
-
number().int(),
|
|
68627
|
-
array(number().int()).min(1),
|
|
68628
|
-
object({ $var: string().min(1) })
|
|
68629
|
-
]),
|
|
68630
|
-
cap: string().min(1),
|
|
68631
|
-
method: string().min(1),
|
|
68632
|
-
/** Values may carry `{{vars.x}}` slots, which SUBSTITUTE and do not
|
|
68633
|
-
* evaluate (§3.2.3). Anything beyond substitution is the expression leaf. */
|
|
68634
|
-
args: record(string(), unknown()).optional()
|
|
68684
|
+
kind: literal("action"),
|
|
68685
|
+
/** An entry of the `actions` surface (`internal:<uuid>` or `<addonId>:<localId>`). */
|
|
68686
|
+
actionId: string().min(1),
|
|
68687
|
+
/** Values for the action's declared variables; the others take their default. */
|
|
68688
|
+
variables: record(string(), union([
|
|
68689
|
+
string(),
|
|
68690
|
+
number(),
|
|
68691
|
+
boolean()
|
|
68692
|
+
])).optional()
|
|
68635
68693
|
}),
|
|
68636
68694
|
object({
|
|
68637
68695
|
kind: literal("code"),
|
|
@@ -68640,10 +68698,357 @@ var AutomationActionSchema = discriminatedUnion("kind", [
|
|
|
68640
68698
|
code: string().min(1).max(2e4)
|
|
68641
68699
|
})
|
|
68642
68700
|
]);
|
|
68701
|
+
/**
|
|
68702
|
+
* How a trigger that arrives while the automation is busy (or cooling down) is
|
|
68703
|
+
* handled. One of the automation's execution modes, never a property of its
|
|
68704
|
+
* actions. Absent means `single`.
|
|
68705
|
+
*
|
|
68706
|
+
* - `single` — drop the trigger while a run is in progress (traced + logged).
|
|
68707
|
+
* - `restart` — abort the run in progress (at its next step boundary), start fresh.
|
|
68708
|
+
* - `queued` — queue up to `max` triggers, drop the rest (traced).
|
|
68709
|
+
* - `parallel` — up to `max` runs at once, drop the rest (traced).
|
|
68710
|
+
* - `cooldown` — `single`, and no new run until `minutes` after the last run
|
|
68711
|
+
* STARTED; persisted across a restart. A manual trigger bypasses only this
|
|
68712
|
+
* wait, never the single/queued limits.
|
|
68713
|
+
*/
|
|
68714
|
+
var AutomationRunModeSchema = discriminatedUnion("mode", [
|
|
68715
|
+
object({ mode: literal("single") }),
|
|
68716
|
+
object({ mode: literal("restart") }),
|
|
68717
|
+
object({
|
|
68718
|
+
mode: literal("queued"),
|
|
68719
|
+
max: number().int().min(1).max(10)
|
|
68720
|
+
}),
|
|
68721
|
+
object({
|
|
68722
|
+
mode: literal("parallel"),
|
|
68723
|
+
max: number().int().min(1).max(10)
|
|
68724
|
+
}),
|
|
68725
|
+
object({
|
|
68726
|
+
mode: literal("cooldown"),
|
|
68727
|
+
minutes: number().int().min(1).max(1440)
|
|
68728
|
+
})
|
|
68729
|
+
]);
|
|
68643
68730
|
var AutomationRecipeSchema = object({
|
|
68644
68731
|
triggers: array(AutomationTriggerSchema),
|
|
68645
68732
|
conditions: AutomationConditionSchema.optional(),
|
|
68646
|
-
actions: array(AutomationActionSchema)
|
|
68733
|
+
actions: array(AutomationActionSchema),
|
|
68734
|
+
runMode: AutomationRunModeSchema.optional(),
|
|
68735
|
+
/** How many run traces the automation keeps (default {@link DEFAULT_TRACE_KEEP}). */
|
|
68736
|
+
keepTraces: number().int().min(1).max(100).optional()
|
|
68737
|
+
});
|
|
68738
|
+
new Set([
|
|
68739
|
+
{
|
|
68740
|
+
id: "person",
|
|
68741
|
+
name: "Person"
|
|
68742
|
+
},
|
|
68743
|
+
{
|
|
68744
|
+
id: "vehicle",
|
|
68745
|
+
name: "Vehicle"
|
|
68746
|
+
},
|
|
68747
|
+
{
|
|
68748
|
+
id: "animal",
|
|
68749
|
+
name: "Animal"
|
|
68750
|
+
},
|
|
68751
|
+
{
|
|
68752
|
+
id: "package",
|
|
68753
|
+
name: "Package"
|
|
68754
|
+
}
|
|
68755
|
+
].map((l) => l.id));
|
|
68756
|
+
var COCO_TO_MACRO = {
|
|
68757
|
+
mapping: {
|
|
68758
|
+
person: "person",
|
|
68759
|
+
bicycle: "vehicle",
|
|
68760
|
+
car: "vehicle",
|
|
68761
|
+
motorcycle: "vehicle",
|
|
68762
|
+
airplane: "vehicle",
|
|
68763
|
+
bus: "vehicle",
|
|
68764
|
+
train: "vehicle",
|
|
68765
|
+
truck: "vehicle",
|
|
68766
|
+
boat: "vehicle",
|
|
68767
|
+
bird: "animal",
|
|
68768
|
+
cat: "animal",
|
|
68769
|
+
dog: "animal",
|
|
68770
|
+
horse: "animal",
|
|
68771
|
+
sheep: "animal",
|
|
68772
|
+
cow: "animal",
|
|
68773
|
+
elephant: "animal",
|
|
68774
|
+
bear: "animal",
|
|
68775
|
+
zebra: "animal",
|
|
68776
|
+
giraffe: "animal",
|
|
68777
|
+
suitcase: "package",
|
|
68778
|
+
backpack: "package",
|
|
68779
|
+
handbag: "package"
|
|
68780
|
+
},
|
|
68781
|
+
preserveOriginal: false
|
|
68782
|
+
};
|
|
68783
|
+
new Set(Object.values(COCO_TO_MACRO.mapping));
|
|
68784
|
+
/**
|
|
68785
|
+
* Run traces — what an automation (or, later, an Action run) DID, node by node,
|
|
68786
|
+
* so the editor can paint every block green or red with its reason.
|
|
68787
|
+
*
|
|
68788
|
+
* A trace is data, not a log: it covers the WHOLE tree — the trigger stage,
|
|
68789
|
+
* every condition node (containers and leaves, including the ones a
|
|
68790
|
+
* short-circuit never reached), the run-mode verdict and every action step —
|
|
68791
|
+
* each addressed by a stable path (`conditions.0.1`, `actions.2`, or `steps.N`
|
|
68792
|
+
* for an Action run). Skipped
|
|
68793
|
+
* evaluations are traced too, and identical consecutive skips collapse into a
|
|
68794
|
+
* repeat count so a trigger that fails every 5 minutes cannot flush real runs.
|
|
68795
|
+
*
|
|
68796
|
+
* {@link AutomationStepTraceSchema} is deliberately free of automation
|
|
68797
|
+
* vocabulary so an Action run records the same step shape.
|
|
68798
|
+
*/
|
|
68799
|
+
/** A value a trace may show: JSON primitives and short lists, nothing deeper. */
|
|
68800
|
+
var TraceValueSchema = union([
|
|
68801
|
+
string(),
|
|
68802
|
+
number(),
|
|
68803
|
+
boolean(),
|
|
68804
|
+
_null(),
|
|
68805
|
+
array(union([string(), number()]))
|
|
68806
|
+
]);
|
|
68807
|
+
var AutomationRunOutcomeSchema = _enum([
|
|
68808
|
+
"ran",
|
|
68809
|
+
"skipped-trigger",
|
|
68810
|
+
"skipped-conditions",
|
|
68811
|
+
"skipped-cooldown",
|
|
68812
|
+
"skipped-overlap",
|
|
68813
|
+
"failed",
|
|
68814
|
+
"aborted"
|
|
68815
|
+
]);
|
|
68816
|
+
/** `not-evaluated` = a short-circuit never reached the node. */
|
|
68817
|
+
var TraceNodeOutcomeSchema = _enum([
|
|
68818
|
+
"pass",
|
|
68819
|
+
"fail",
|
|
68820
|
+
"not-evaluated"
|
|
68821
|
+
]);
|
|
68822
|
+
var AutomationTriggerTraceSchema = object({
|
|
68823
|
+
kind: string().min(1),
|
|
68824
|
+
outcome: _enum(["pass", "fail"]),
|
|
68825
|
+
/** Why an event was rejected ("class person not in [dog]") or what fired. */
|
|
68826
|
+
reason: string(),
|
|
68827
|
+
/** deviceId, className, trackId, score … — only what the trigger exposes. */
|
|
68828
|
+
vars: record(string(), TraceValueSchema)
|
|
68829
|
+
});
|
|
68830
|
+
var AutomationConditionTraceNodeSchema = object({
|
|
68831
|
+
/** `conditions` for the root, `conditions.<i>` for its i-th child, … */
|
|
68832
|
+
path: string().min(1),
|
|
68833
|
+
kind: _enum([
|
|
68834
|
+
"all",
|
|
68835
|
+
"any",
|
|
68836
|
+
"not",
|
|
68837
|
+
"condition",
|
|
68838
|
+
"expression",
|
|
68839
|
+
"stationary-present"
|
|
68840
|
+
]),
|
|
68841
|
+
outcome: TraceNodeOutcomeSchema,
|
|
68842
|
+
reason: string(),
|
|
68843
|
+
deviceId: number().int().optional(),
|
|
68844
|
+
cap: string().optional(),
|
|
68845
|
+
fieldPath: string().optional(),
|
|
68846
|
+
operator: string().optional(),
|
|
68847
|
+
expected: TraceValueSchema.optional(),
|
|
68848
|
+
/** What the mirror held; `null` when it held nothing (unknown). */
|
|
68849
|
+
observed: TraceValueSchema.optional()
|
|
68850
|
+
});
|
|
68851
|
+
var AutomationRunVerdictSchema = object({
|
|
68852
|
+
mode: AutomationRunModeSchema,
|
|
68853
|
+
decision: _enum([
|
|
68854
|
+
"run",
|
|
68855
|
+
"queued",
|
|
68856
|
+
"restart",
|
|
68857
|
+
"drop",
|
|
68858
|
+
"cooldown",
|
|
68859
|
+
"bypass"
|
|
68860
|
+
]),
|
|
68861
|
+
reason: string()
|
|
68862
|
+
});
|
|
68863
|
+
/** One executed (or not-run) step — shared by automations and Action runs. */
|
|
68864
|
+
var AutomationStepTraceSchema = object({
|
|
68865
|
+
/** `actions.<i>` */
|
|
68866
|
+
path: string().min(1),
|
|
68867
|
+
kind: string().min(1),
|
|
68868
|
+
cap: string().optional(),
|
|
68869
|
+
method: string().optional(),
|
|
68870
|
+
deviceId: number().int().optional(),
|
|
68871
|
+
startedAt: number().optional(),
|
|
68872
|
+
endedAt: number().optional(),
|
|
68873
|
+
outcome: _enum([
|
|
68874
|
+
"ok",
|
|
68875
|
+
"error",
|
|
68876
|
+
"not-run"
|
|
68877
|
+
]),
|
|
68878
|
+
message: string().optional()
|
|
68879
|
+
});
|
|
68880
|
+
var AutomationRunTraceSchema = object({
|
|
68881
|
+
runId: string().min(1),
|
|
68882
|
+
startedAt: number(),
|
|
68883
|
+
endedAt: number(),
|
|
68884
|
+
trigger: AutomationTriggerTraceSchema,
|
|
68885
|
+
conditions: array(AutomationConditionTraceNodeSchema),
|
|
68886
|
+
verdict: AutomationRunVerdictSchema.optional(),
|
|
68887
|
+
steps: array(AutomationStepTraceSchema),
|
|
68888
|
+
outcome: AutomationRunOutcomeSchema,
|
|
68889
|
+
/** How many identical consecutive skips this trace stands for (>= 1). */
|
|
68890
|
+
repeat: number().int().min(1),
|
|
68891
|
+
/** When the latest of those repeats happened. */
|
|
68892
|
+
lastAt: number()
|
|
68893
|
+
});
|
|
68894
|
+
/**
|
|
68895
|
+
* What the hub's `runs` ledger holds for one owner (`automation:<blockId>` or
|
|
68896
|
+
* `action:<actionId>`): the cooldown stamp and the trace ring.
|
|
68897
|
+
*/
|
|
68898
|
+
var AutomationStateDocSchema = object({
|
|
68899
|
+
version: literal(1),
|
|
68900
|
+
lastRunStartedAt: number().nullable(),
|
|
68901
|
+
traces: array(AutomationRunTraceSchema)
|
|
68902
|
+
});
|
|
68903
|
+
/**
|
|
68904
|
+
* Actions — named, runnable things an operator (or an automation) can start,
|
|
68905
|
+
* declared as LIST ENTRIES, never as devices.
|
|
68906
|
+
*
|
|
68907
|
+
* An entry is either **internal** (a short list of cap/wait steps kept in the
|
|
68908
|
+
* hub's `actions` record kind) or declared by an addon through the
|
|
68909
|
+
* `action-source` collection (a Home Assistant script, a scene …). The one
|
|
68910
|
+
* surface the UI and automations talk to is the `actions` cap; it keeps no
|
|
68911
|
+
* catalog of its own (D3) — every `list` asks the providers live.
|
|
68912
|
+
*
|
|
68913
|
+
* This file is the data half: the entry schema, the variables an entry may
|
|
68914
|
+
* declare, and the pure resolution of the values a run was given. It holds no
|
|
68915
|
+
* runtime.
|
|
68916
|
+
*/
|
|
68917
|
+
/** What a variable's value is. `select` takes one of `options`. */
|
|
68918
|
+
var ActionVariableTypeSchema = _enum([
|
|
68919
|
+
"string",
|
|
68920
|
+
"number",
|
|
68921
|
+
"boolean",
|
|
68922
|
+
"select"
|
|
68923
|
+
]);
|
|
68924
|
+
var ACTION_VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
68925
|
+
var ActionVariableValueSchema = union([
|
|
68926
|
+
string(),
|
|
68927
|
+
number(),
|
|
68928
|
+
boolean()
|
|
68929
|
+
]);
|
|
68930
|
+
/**
|
|
68931
|
+
* One declared variable. Steps reference it as `{ $var: 'name' }` (the whole
|
|
68932
|
+
* value) or `{{vars.name}}` (inside a string) in their args.
|
|
68933
|
+
*/
|
|
68934
|
+
var ActionVariableSchema = object({
|
|
68935
|
+
name: string().regex(ACTION_VARIABLE_NAME_RE),
|
|
68936
|
+
label: string().min(1).max(120).optional(),
|
|
68937
|
+
type: ActionVariableTypeSchema,
|
|
68938
|
+
required: boolean().optional(),
|
|
68939
|
+
default: ActionVariableValueSchema.optional(),
|
|
68940
|
+
/** The allowed values of a `select`. */
|
|
68941
|
+
options: array(string().min(1)).min(1).optional()
|
|
68942
|
+
}).superRefine((v, ctx) => {
|
|
68943
|
+
if (v.type === "select" && v.options === void 0) ctx.addIssue({
|
|
68944
|
+
code: "custom",
|
|
68945
|
+
message: "a select variable needs options",
|
|
68946
|
+
path: ["options"]
|
|
68947
|
+
});
|
|
68948
|
+
if (v.default !== void 0 && variableValueError(v, v.default) !== null) ctx.addIssue({
|
|
68949
|
+
code: "custom",
|
|
68950
|
+
message: variableValueError(v, v.default) ?? "invalid default",
|
|
68951
|
+
path: ["default"]
|
|
68952
|
+
});
|
|
68953
|
+
});
|
|
68954
|
+
/** Why `value` is not a legal value for the variable, or null when it is. */
|
|
68955
|
+
function variableValueError(variable, value) {
|
|
68956
|
+
switch (variable.type) {
|
|
68957
|
+
case "string": return typeof value === "string" ? null : `variable "${variable.name}" must be a string`;
|
|
68958
|
+
case "number": return typeof value === "number" && Number.isFinite(value) ? null : `variable "${variable.name}" must be a number`;
|
|
68959
|
+
case "boolean": return typeof value === "boolean" ? null : `variable "${variable.name}" must be true or false`;
|
|
68960
|
+
case "select": return typeof value === "string" && (variable.options ?? []).includes(value) ? null : `variable "${variable.name}" must be one of ${(variable.options ?? []).join(", ")}`;
|
|
68961
|
+
}
|
|
68962
|
+
}
|
|
68963
|
+
var ActionSourceKindSchema = _enum(["internal", "addon"]);
|
|
68964
|
+
function isInternalActionId(actionId) {
|
|
68965
|
+
return actionId.startsWith("internal:") && actionId.length > 9;
|
|
68966
|
+
}
|
|
68967
|
+
/** The addon behind a `<addonId>:<localId>` id, or null for anything else (internal included). */
|
|
68968
|
+
function addonIdOfActionId(actionId) {
|
|
68969
|
+
if (isInternalActionId(actionId)) return null;
|
|
68970
|
+
const at = actionId.indexOf(":");
|
|
68971
|
+
return at > 0 && at < actionId.length - 1 ? actionId.slice(0, at) : null;
|
|
68972
|
+
}
|
|
68973
|
+
var ActionStatusSchema = object({
|
|
68974
|
+
isRunning: boolean(),
|
|
68975
|
+
/** Ms epoch the latest run STARTED; null when it never ran. */
|
|
68976
|
+
lastRunAt: number().nullable(),
|
|
68977
|
+
/** The latest run's failure; null on success or when it never ran. */
|
|
68978
|
+
lastError: string().nullable()
|
|
68979
|
+
});
|
|
68980
|
+
var ActionEntrySchema = object({
|
|
68981
|
+
/** `internal:<uuid>` or `<addonId>:<localId>`. */
|
|
68982
|
+
id: string().min(3),
|
|
68983
|
+
name: string().min(1).max(120),
|
|
68984
|
+
/** The addon that declared it; `core-blocks` for an internal one. */
|
|
68985
|
+
addonId: string().min(1),
|
|
68986
|
+
source: ActionSourceKindSchema,
|
|
68987
|
+
/** An addon-declared entry is edited where it was declared. */
|
|
68988
|
+
readOnly: boolean(),
|
|
68989
|
+
/** Internal only: cap and wait steps. No action-in-action, so no cycles. */
|
|
68990
|
+
steps: array(ActionStepSchema).min(1).max(50).optional(),
|
|
68991
|
+
variables: array(ActionVariableSchema).max(20).optional(),
|
|
68992
|
+
status: ActionStatusSchema
|
|
68993
|
+
}).superRefine((entry, ctx) => {
|
|
68994
|
+
if (entry.source === "internal") {
|
|
68995
|
+
if (!isInternalActionId(entry.id)) ctx.addIssue({
|
|
68996
|
+
code: "custom",
|
|
68997
|
+
message: "an internal action id is internal:<uuid>",
|
|
68998
|
+
path: ["id"]
|
|
68999
|
+
});
|
|
69000
|
+
return;
|
|
69001
|
+
}
|
|
69002
|
+
if (entry.steps !== void 0) ctx.addIssue({
|
|
69003
|
+
code: "custom",
|
|
69004
|
+
message: "only an internal action carries steps",
|
|
69005
|
+
path: ["steps"]
|
|
69006
|
+
});
|
|
69007
|
+
if (!entry.readOnly) ctx.addIssue({
|
|
69008
|
+
code: "custom",
|
|
69009
|
+
message: "an addon action is read-only here",
|
|
69010
|
+
path: ["readOnly"]
|
|
69011
|
+
});
|
|
69012
|
+
if (addonIdOfActionId(entry.id) === null) ctx.addIssue({
|
|
69013
|
+
code: "custom",
|
|
69014
|
+
message: "an addon action id is <addonId>:<localId>",
|
|
69015
|
+
path: ["id"]
|
|
69016
|
+
});
|
|
69017
|
+
});
|
|
69018
|
+
/** What an operator writes for an internal action. */
|
|
69019
|
+
var ActionInputSchema = object({
|
|
69020
|
+
name: string().min(1).max(120),
|
|
69021
|
+
steps: array(ActionStepSchema).min(1).max(50),
|
|
69022
|
+
variables: array(ActionVariableSchema).max(20).optional()
|
|
69023
|
+
});
|
|
69024
|
+
/** Who started a run. Absent = the operator pressed it. */
|
|
69025
|
+
var ActionRunOriginSchema = object({
|
|
69026
|
+
/** The automation block whose step started it. */
|
|
69027
|
+
blockId: string().min(1).optional() });
|
|
69028
|
+
/** `started` = the caller did not ask to wait. */
|
|
69029
|
+
var ActionRunOutcomeSchema = union([AutomationRunOutcomeSchema, literal("started")]);
|
|
69030
|
+
var ActionRunResultSchema = object({
|
|
69031
|
+
runId: string().min(1),
|
|
69032
|
+
outcome: ActionRunOutcomeSchema,
|
|
69033
|
+
/** Why it did not run, or what failed. */
|
|
69034
|
+
message: string().optional()
|
|
69035
|
+
});
|
|
69036
|
+
_enum([
|
|
69037
|
+
"not-found",
|
|
69038
|
+
"read-only",
|
|
69039
|
+
"invalid-variables",
|
|
69040
|
+
"invalid-steps",
|
|
69041
|
+
"not-linked",
|
|
69042
|
+
"busy"
|
|
69043
|
+
]);
|
|
69044
|
+
object({
|
|
69045
|
+
actionId: string(),
|
|
69046
|
+
runId: string(),
|
|
69047
|
+
isRunning: boolean(),
|
|
69048
|
+
/** Set when the run ended. */
|
|
69049
|
+
outcome: AutomationRunOutcomeSchema.optional(),
|
|
69050
|
+
lastRunAt: number().nullable(),
|
|
69051
|
+
lastError: string().nullable()
|
|
68647
69052
|
});
|
|
68648
69053
|
/**
|
|
68649
69054
|
* `addon-pages` — system-scoped singleton aggregator cap. Public-facing
|
|
@@ -71330,7 +71735,7 @@ var CompositionEventPlanSchema = object({
|
|
|
71330
71735
|
stateField: string(),
|
|
71331
71736
|
fieldKey: string()
|
|
71332
71737
|
});
|
|
71333
|
-
/** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.
|
|
71738
|
+
/** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.temperature`. */
|
|
71334
71739
|
var CompositionProblemSchema = object({
|
|
71335
71740
|
code: _enum([
|
|
71336
71741
|
"reserved-variant",
|
|
@@ -71649,7 +72054,7 @@ var GROUP_TEMPLATES = [
|
|
|
71649
72054
|
summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
|
|
71650
72055
|
role: DeviceRole.TemperatureSensor,
|
|
71651
72056
|
outputCap: "temperature-sensor",
|
|
71652
|
-
outputField: "
|
|
72057
|
+
outputField: "temperature",
|
|
71653
72058
|
quantity: "temperature",
|
|
71654
72059
|
defaultAggregate: "mean",
|
|
71655
72060
|
empty: "null",
|
|
@@ -71660,7 +72065,7 @@ var GROUP_TEMPLATES = [
|
|
|
71660
72065
|
summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
|
|
71661
72066
|
role: DeviceRole.HumiditySensor,
|
|
71662
72067
|
outputCap: "humidity-sensor",
|
|
71663
|
-
outputField: "
|
|
72068
|
+
outputField: "humidity",
|
|
71664
72069
|
quantity: "humidity",
|
|
71665
72070
|
defaultAggregate: "mean",
|
|
71666
72071
|
empty: "null",
|
|
@@ -71671,7 +72076,7 @@ var GROUP_TEMPLATES = [
|
|
|
71671
72076
|
summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
|
|
71672
72077
|
role: DeviceRole.PressureSensor,
|
|
71673
72078
|
outputCap: "pressure-sensor",
|
|
71674
|
-
outputField: "
|
|
72079
|
+
outputField: "pressure",
|
|
71675
72080
|
quantity: "pressure",
|
|
71676
72081
|
defaultAggregate: "mean",
|
|
71677
72082
|
empty: "null",
|
|
@@ -71682,7 +72087,7 @@ var GROUP_TEMPLATES = [
|
|
|
71682
72087
|
summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
|
|
71683
72088
|
role: DeviceRole.AmbientLightSensor,
|
|
71684
72089
|
outputCap: "ambient-light-sensor",
|
|
71685
|
-
outputField: "
|
|
72090
|
+
outputField: "illuminance",
|
|
71686
72091
|
quantity: "illuminance",
|
|
71687
72092
|
defaultAggregate: "mean",
|
|
71688
72093
|
empty: "null",
|
|
@@ -71693,7 +72098,7 @@ var GROUP_TEMPLATES = [
|
|
|
71693
72098
|
summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
|
|
71694
72099
|
role: DeviceRole.PowerSensor,
|
|
71695
72100
|
outputCap: "power-meter",
|
|
71696
|
-
outputField: "
|
|
72101
|
+
outputField: "power",
|
|
71697
72102
|
quantity: "power",
|
|
71698
72103
|
defaultAggregate: "sum",
|
|
71699
72104
|
empty: "omit",
|
|
@@ -71704,7 +72109,7 @@ var GROUP_TEMPLATES = [
|
|
|
71704
72109
|
summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
|
|
71705
72110
|
role: DeviceRole.EnergySensor,
|
|
71706
72111
|
outputCap: "power-meter",
|
|
71707
|
-
outputField: "
|
|
72112
|
+
outputField: "energy",
|
|
71708
72113
|
quantity: "energy",
|
|
71709
72114
|
defaultAggregate: "sum",
|
|
71710
72115
|
empty: "omit",
|
|
@@ -71715,7 +72120,7 @@ var GROUP_TEMPLATES = [
|
|
|
71715
72120
|
summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
|
|
71716
72121
|
role: DeviceRole.BatterySensor,
|
|
71717
72122
|
outputCap: "battery",
|
|
71718
|
-
outputField: "
|
|
72123
|
+
outputField: "level",
|
|
71719
72124
|
quantity: "percentage",
|
|
71720
72125
|
defaultAggregate: "mean",
|
|
71721
72126
|
empty: "null",
|
|
@@ -72293,7 +72698,18 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
|
|
|
72293
72698
|
kind: "mutation",
|
|
72294
72699
|
auth: "admin",
|
|
72295
72700
|
access: "view"
|
|
72296
|
-
})
|
|
72701
|
+
}), method(object({
|
|
72702
|
+
blockId: string().min(1),
|
|
72703
|
+
trace: AutomationRunTraceSchema.optional(),
|
|
72704
|
+
runStartedAt: number().optional(),
|
|
72705
|
+
keepTraces: number().int().min(1).max(100).optional()
|
|
72706
|
+
}), _void(), {
|
|
72707
|
+
kind: "mutation",
|
|
72708
|
+
auth: "admin"
|
|
72709
|
+
}), method(object({ blockId: string().min(1) }), AutomationStateDocSchema, { auth: "admin" }), method(object({ blockId: string().min(1) }), object({
|
|
72710
|
+
traces: array(AutomationRunTraceSchema),
|
|
72711
|
+
lastError: string().nullable()
|
|
72712
|
+
}), { auth: "admin" });
|
|
72297
72713
|
/**
|
|
72298
72714
|
* `custom-model-registry` — collection cap exposing operator-registered
|
|
72299
72715
|
* custom detection models. Each provider (today: `addon-model-studio`)
|
|
@@ -97681,51 +98097,6 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
|
|
|
97681
98097
|
kind: "mutation",
|
|
97682
98098
|
auth: "admin"
|
|
97683
98099
|
});
|
|
97684
|
-
new Set([
|
|
97685
|
-
{
|
|
97686
|
-
id: "person",
|
|
97687
|
-
name: "Person"
|
|
97688
|
-
},
|
|
97689
|
-
{
|
|
97690
|
-
id: "vehicle",
|
|
97691
|
-
name: "Vehicle"
|
|
97692
|
-
},
|
|
97693
|
-
{
|
|
97694
|
-
id: "animal",
|
|
97695
|
-
name: "Animal"
|
|
97696
|
-
},
|
|
97697
|
-
{
|
|
97698
|
-
id: "package",
|
|
97699
|
-
name: "Package"
|
|
97700
|
-
}
|
|
97701
|
-
].map((l) => l.id));
|
|
97702
|
-
var COCO_TO_MACRO = {
|
|
97703
|
-
mapping: {
|
|
97704
|
-
person: "person",
|
|
97705
|
-
bicycle: "vehicle",
|
|
97706
|
-
car: "vehicle",
|
|
97707
|
-
motorcycle: "vehicle",
|
|
97708
|
-
airplane: "vehicle",
|
|
97709
|
-
bus: "vehicle",
|
|
97710
|
-
train: "vehicle",
|
|
97711
|
-
truck: "vehicle",
|
|
97712
|
-
boat: "vehicle",
|
|
97713
|
-
bird: "animal",
|
|
97714
|
-
cat: "animal",
|
|
97715
|
-
dog: "animal",
|
|
97716
|
-
horse: "animal",
|
|
97717
|
-
sheep: "animal",
|
|
97718
|
-
cow: "animal",
|
|
97719
|
-
elephant: "animal",
|
|
97720
|
-
bear: "animal",
|
|
97721
|
-
zebra: "animal",
|
|
97722
|
-
giraffe: "animal",
|
|
97723
|
-
suitcase: "package",
|
|
97724
|
-
backpack: "package",
|
|
97725
|
-
handbag: "package"
|
|
97726
|
-
},
|
|
97727
|
-
preserveOriginal: false
|
|
97728
|
-
};
|
|
97729
98100
|
var AUDIO_MACRO_LABELS = [
|
|
97730
98101
|
{
|
|
97731
98102
|
id: "speech",
|
|
@@ -110292,9 +110663,9 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
|
|
|
110292
110663
|
*/
|
|
110293
110664
|
var AirQualitySensorStatusSchema = object({
|
|
110294
110665
|
/** Carbon dioxide concentration in ppm. */
|
|
110295
|
-
|
|
110666
|
+
co2: number().min(0).optional().meta(readingMeta("concentration-volume")),
|
|
110296
110667
|
/** Total volatile organic compounds in ppb. */
|
|
110297
|
-
|
|
110668
|
+
voc: number().min(0).optional().meta(readingMeta("concentration-ppb")),
|
|
110298
110669
|
/** Particulate matter ≤ 2.5 μm in µg/m³. */
|
|
110299
110670
|
pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
|
|
110300
110671
|
/** Particulate matter ≤ 10 μm in µg/m³. */
|
|
@@ -110327,12 +110698,12 @@ var airQualitySensorCapability = {
|
|
|
110327
110698
|
* History (D850): recorded series.
|
|
110328
110699
|
*/
|
|
110329
110700
|
history: { fields: {
|
|
110330
|
-
|
|
110701
|
+
co2: {
|
|
110331
110702
|
kind: "gauge",
|
|
110332
110703
|
unit: "ppm",
|
|
110333
110704
|
deadband: 5
|
|
110334
110705
|
},
|
|
110335
|
-
|
|
110706
|
+
voc: {
|
|
110336
110707
|
kind: "gauge",
|
|
110337
110708
|
unit: "ppb",
|
|
110338
110709
|
deadband: 5
|
|
@@ -110363,7 +110734,7 @@ var airQualitySensorCapability = {
|
|
|
110363
110734
|
*/
|
|
110364
110735
|
var AmbientLightSensorStatusSchema = object({
|
|
110365
110736
|
/** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
|
|
110366
|
-
|
|
110737
|
+
illuminance: number().min(0).nullable().meta(readingMeta("illuminance", {
|
|
110367
110738
|
precision: 0,
|
|
110368
110739
|
label: "Illuminance"
|
|
110369
110740
|
})),
|
|
@@ -110392,7 +110763,7 @@ var ambientLightSensorCapability = {
|
|
|
110392
110763
|
/**
|
|
110393
110764
|
* History (D850): recorded series. Wide range, so a RELATIVE deadband (10 %).
|
|
110394
110765
|
*/
|
|
110395
|
-
history: { fields: {
|
|
110766
|
+
history: { fields: { illuminance: {
|
|
110396
110767
|
kind: "gauge",
|
|
110397
110768
|
unit: "lx",
|
|
110398
110769
|
deadbandPct: 10,
|
|
@@ -110531,6 +110902,84 @@ var audioMetricsCapability = {
|
|
|
110531
110902
|
*/
|
|
110532
110903
|
history: "none"
|
|
110533
110904
|
};
|
|
110905
|
+
method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
|
|
110906
|
+
actionId: string().min(1),
|
|
110907
|
+
variables: record(string(), ActionVariableValueSchema).optional()
|
|
110908
|
+
}), _void(), {
|
|
110909
|
+
kind: "mutation",
|
|
110910
|
+
auth: "admin"
|
|
110911
|
+
}), method(object({ actionId: string().min(1) }), _void(), {
|
|
110912
|
+
kind: "mutation",
|
|
110913
|
+
auth: "admin"
|
|
110914
|
+
});
|
|
110915
|
+
/**
|
|
110916
|
+
* actions — the ONE surface for runnable actions, for the UI and for
|
|
110917
|
+
* automations. Provided by the core-blocks builtin on the hub.
|
|
110918
|
+
*
|
|
110919
|
+
* An action is a LIST ENTRY, not a device (D883): either internal (cap and wait
|
|
110920
|
+
* steps, kept in the hub's `actions` record kind) or declared by an addon over
|
|
110921
|
+
* `action-source`. `list` asks every `action-source` provider LIVE and overlays
|
|
110922
|
+
* the run status from the hub's `runs` ledger; there is no catalog here to
|
|
110923
|
+
* drift (D3).
|
|
110924
|
+
*
|
|
110925
|
+
* **Permissions follow the camera, as linked devices do.** An action is linked
|
|
110926
|
+
* to cameras by the operator (`setLinks`, D356: nothing links itself). A
|
|
110927
|
+
* non-admin may `get` / `run` an action only through a camera they may see that
|
|
110928
|
+
* links it: they name the camera (`deviceId`, a top-level field so the scope
|
|
110929
|
+
* gate sees it) and the provider refuses an action that camera does not link.
|
|
110930
|
+
* An admin, and an in-process caller (an automation block), may run any action.
|
|
110931
|
+
* Every refusal is typed (`[not-linked] …`) and logged.
|
|
110932
|
+
*
|
|
110933
|
+
* A second `run` of an action that is already running is REFUSED — traced and
|
|
110934
|
+
* logged — never queued.
|
|
110935
|
+
*/
|
|
110936
|
+
var ActionIdInput = object({ actionId: string().min(1) });
|
|
110937
|
+
var ActionLinksSchema = object({
|
|
110938
|
+
deviceId: number().int().nonnegative(),
|
|
110939
|
+
actionIds: array(string().min(1)).max(100)
|
|
110940
|
+
});
|
|
110941
|
+
method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
|
|
110942
|
+
actionId: string().min(1),
|
|
110943
|
+
deviceId: number().int().nonnegative().optional()
|
|
110944
|
+
}), object({ action: ActionEntrySchema.nullable() }), {
|
|
110945
|
+
role: "viewer",
|
|
110946
|
+
auth: "protected",
|
|
110947
|
+
kind: "query",
|
|
110948
|
+
caller: "required"
|
|
110949
|
+
}), method(object({
|
|
110950
|
+
actionId: string().min(1),
|
|
110951
|
+
variables: record(string(), ActionVariableValueSchema).optional(),
|
|
110952
|
+
wait: boolean().optional(),
|
|
110953
|
+
origin: ActionRunOriginSchema.optional(),
|
|
110954
|
+
deviceId: number().int().nonnegative().optional()
|
|
110955
|
+
}), ActionRunResultSchema, {
|
|
110956
|
+
role: "viewer",
|
|
110957
|
+
auth: "protected",
|
|
110958
|
+
kind: "mutation",
|
|
110959
|
+
caller: "required"
|
|
110960
|
+
}), method(ActionIdInput, _void(), {
|
|
110961
|
+
kind: "mutation",
|
|
110962
|
+
auth: "admin"
|
|
110963
|
+
}), method(ActionIdInput, object({ traces: array(AutomationRunTraceSchema) }), { auth: "admin" }), method(object({ action: ActionInputSchema }), object({ action: ActionEntrySchema }), {
|
|
110964
|
+
kind: "mutation",
|
|
110965
|
+
auth: "admin"
|
|
110966
|
+
}), method(object({
|
|
110967
|
+
actionId: string().min(1),
|
|
110968
|
+
action: ActionInputSchema.partial()
|
|
110969
|
+
}), object({ action: ActionEntrySchema }), {
|
|
110970
|
+
kind: "mutation",
|
|
110971
|
+
auth: "admin"
|
|
110972
|
+
}), method(ActionIdInput, _void(), {
|
|
110973
|
+
kind: "mutation",
|
|
110974
|
+
auth: "admin"
|
|
110975
|
+
}), method(object({ deviceId: number().int().nonnegative() }), array(ActionEntrySchema), {
|
|
110976
|
+
role: "viewer",
|
|
110977
|
+
auth: "protected",
|
|
110978
|
+
kind: "query"
|
|
110979
|
+
}), method(object({ deviceId: number().int().nonnegative() }), ActionLinksSchema, { auth: "admin" }), method(ActionLinksSchema, ActionLinksSchema, {
|
|
110980
|
+
kind: "mutation",
|
|
110981
|
+
auth: "admin"
|
|
110982
|
+
});
|
|
110534
110983
|
/**
|
|
110535
110984
|
* Automation-control cap. Models HA `automation.*` entities on
|
|
110536
110985
|
* `DeviceType.Automation`. An automation is a trigger+condition+
|
|
@@ -110627,7 +111076,7 @@ var BatteryStatusSchema = object({
|
|
|
110627
111076
|
* Consumers must SKIP a null rather than coerce it — `battery-band` already
|
|
110628
111077
|
* declines to band a non-finite reading, which is the correct shape.
|
|
110629
111078
|
*/
|
|
110630
|
-
|
|
111079
|
+
level: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
|
|
110631
111080
|
/**
|
|
110632
111081
|
* Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
|
|
110633
111082
|
* Reolink-specific for the Solar Panel 2 accessory (will become
|
|
@@ -110637,7 +111086,7 @@ var BatteryStatusSchema = object({
|
|
|
110637
111086
|
* `'unknown'` is the absence of an answer: the provider has not read the
|
|
110638
111087
|
* power fields yet, or the firmware reported neither. Before it existed,
|
|
110639
111088
|
* two unreadable fields produced `'none'`, and a camera nobody had reached
|
|
110640
|
-
* was drawn "on battery alone" — the same D315 defect `
|
|
111089
|
+
* was drawn "on battery alone" — the same D315 defect `level` closed
|
|
110641
111090
|
* by going nullable. Consumers SKIP it (no charger state published, no
|
|
110642
111091
|
* glyph, no HomeKit ChargingState) rather than coerce it either way.
|
|
110643
111092
|
*/
|
|
@@ -110661,7 +111110,7 @@ var BatteryStatusSchema = object({
|
|
|
110661
111110
|
* that only report charge. A second reading on this cap, not a second
|
|
110662
111111
|
* `numeric-sensor` on the same device.
|
|
110663
111112
|
*/
|
|
110664
|
-
|
|
111113
|
+
voltage: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
|
|
110665
111114
|
/**
|
|
110666
111115
|
* Ms epoch of the last time the device PROVED it was reachable — a
|
|
110667
111116
|
* completed firmware round-trip, an observed wake, or an inbound push
|
|
@@ -110695,7 +111144,7 @@ var BatteryStatusSchema = object({
|
|
|
110695
111144
|
/**
|
|
110696
111145
|
* True when the source is a BINARY low-battery indicator (HA
|
|
110697
111146
|
* `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
|
|
110698
|
-
* charge level — `
|
|
111147
|
+
* charge level — `level` is then a coarse stand-in (100 = normal,
|
|
110699
111148
|
* sub-threshold = low). UI MUST render "Normal"/"Low" instead of a
|
|
110700
111149
|
* misleading exact percentage. Absent/false → genuine 0–100 % reading.
|
|
110701
111150
|
*/
|
|
@@ -110715,15 +111164,15 @@ var BATTERY_DEVICE_TYPES = [
|
|
|
110715
111164
|
var batteryCustomize = {
|
|
110716
111165
|
version: 1,
|
|
110717
111166
|
targets: [{
|
|
110718
|
-
id: "
|
|
111167
|
+
id: "level",
|
|
110719
111168
|
cardinality: "scalar",
|
|
110720
111169
|
vocabulary: {
|
|
110721
111170
|
cap: "battery",
|
|
110722
|
-
stateField: "
|
|
111171
|
+
stateField: "level"
|
|
110723
111172
|
},
|
|
110724
111173
|
deviceTypes: BATTERY_DEVICE_TYPES,
|
|
110725
111174
|
apply: "add",
|
|
110726
|
-
path: "
|
|
111175
|
+
path: "level",
|
|
110727
111176
|
sources: [{
|
|
110728
111177
|
kind: "field",
|
|
110729
111178
|
valueKinds: ["number", "string"],
|
|
@@ -110817,7 +111266,7 @@ onStatusChanged: { data: object({
|
|
|
110817
111266
|
schema: BatteryStatusSchema,
|
|
110818
111267
|
kind: "push",
|
|
110819
111268
|
empty: {
|
|
110820
|
-
|
|
111269
|
+
level: null,
|
|
110821
111270
|
charging: "unknown",
|
|
110822
111271
|
sleeping: false,
|
|
110823
111272
|
lastUpdated: 0
|
|
@@ -110844,11 +111293,11 @@ onStatusChanged: { data: object({
|
|
|
110844
111293
|
* History (D850): recorded series. The generic capture replaces the `battery` series of `pipeline-analytics:stats-5m`.
|
|
110845
111294
|
*/
|
|
110846
111295
|
history: { fields: {
|
|
110847
|
-
|
|
111296
|
+
level: {
|
|
110848
111297
|
kind: "gauge",
|
|
110849
111298
|
unit: "%"
|
|
110850
111299
|
},
|
|
110851
|
-
|
|
111300
|
+
voltage: {
|
|
110852
111301
|
kind: "gauge",
|
|
110853
111302
|
unit: "V"
|
|
110854
111303
|
}
|
|
@@ -110908,14 +111357,14 @@ var binaryCapability = {
|
|
|
110908
111357
|
* controls to this single-method surface.
|
|
110909
111358
|
*
|
|
110910
111359
|
* The cap is intentionally minimal: a single `setBrightness({deviceId,
|
|
110911
|
-
*
|
|
111360
|
+
* level})` mutation plus the auto-injected `getStatus`. Drivers
|
|
110912
111361
|
* that expose richer controls (color temperature, scenes, schedules)
|
|
110913
111362
|
* should surface those via the device's `getSettingsUISchema()`
|
|
110914
111363
|
* instead of bloating this cap.
|
|
110915
111364
|
*/
|
|
110916
111365
|
var BrightnessStatusSchema = object({
|
|
110917
111366
|
/** Current level as 0..100 inclusive. Firmware-reported. */
|
|
110918
|
-
|
|
111367
|
+
level: number().min(0).max(100).meta(readingMeta("percentage")),
|
|
110919
111368
|
/** Ms epoch of the last operator-driven change. Useful for UI freshness. */
|
|
110920
111369
|
lastChangedAt: number()
|
|
110921
111370
|
});
|
|
@@ -110927,7 +111376,7 @@ var brightnessCapability = {
|
|
|
110927
111376
|
deviceTypes: [DeviceType.Light],
|
|
110928
111377
|
methods: { setBrightness: method(object({
|
|
110929
111378
|
deviceId: number().int().nonnegative(),
|
|
110930
|
-
|
|
111379
|
+
level: number().min(0).max(100)
|
|
110931
111380
|
}), _void(), {
|
|
110932
111381
|
kind: "mutation",
|
|
110933
111382
|
auth: "protected"
|
|
@@ -110940,7 +111389,7 @@ var brightnessCapability = {
|
|
|
110940
111389
|
*/
|
|
110941
111390
|
onBrightnessChanged: { data: object({
|
|
110942
111391
|
deviceId: number(),
|
|
110943
|
-
|
|
111392
|
+
level: number().min(0).max(100),
|
|
110944
111393
|
lastChangedAt: number()
|
|
110945
111394
|
}) } },
|
|
110946
111395
|
status: {
|
|
@@ -110963,7 +111412,7 @@ onBrightnessChanged: { data: object({
|
|
|
110963
111412
|
/**
|
|
110964
111413
|
* History (D850): recorded series. Actuator tier: level keeps every change.
|
|
110965
111414
|
*/
|
|
110966
|
-
history: { fields: {
|
|
111415
|
+
history: { fields: { level: {
|
|
110967
111416
|
kind: "gauge",
|
|
110968
111417
|
unit: "%",
|
|
110969
111418
|
minIntervalMs: 0
|
|
@@ -111577,11 +112026,11 @@ var ColorInputSchema = discriminatedUnion("mode", [
|
|
|
111577
112026
|
}),
|
|
111578
112027
|
object({
|
|
111579
112028
|
mode: literal("mired"),
|
|
111580
|
-
|
|
112029
|
+
colorTemperature: number().int().min(50).max(1e3)
|
|
111581
112030
|
})
|
|
111582
112031
|
]);
|
|
111583
112032
|
var ColorStatusSchema = object({
|
|
111584
|
-
/** Active color mode — which of `rgb` / `hsv` / `
|
|
112033
|
+
/** Active color mode — which of `rgb` / `hsv` / `colorTemperature` reflects
|
|
111585
112034
|
* the bulb's current state. */
|
|
111586
112035
|
mode: _enum([
|
|
111587
112036
|
"rgb",
|
|
@@ -111593,7 +112042,7 @@ var ColorStatusSchema = object({
|
|
|
111593
112042
|
/** Populated when `mode === 'hsv'`. */
|
|
111594
112043
|
hsv: HsvTripletSchema.optional(),
|
|
111595
112044
|
/** Populated when `mode === 'mired'`. */
|
|
111596
|
-
|
|
112045
|
+
colorTemperature: number().int().optional().meta(readingMeta("colour-temperature")),
|
|
111597
112046
|
/** Ms epoch of the last operator-driven change. */
|
|
111598
112047
|
lastChangedAt: number()
|
|
111599
112048
|
});
|
|
@@ -111625,7 +112074,7 @@ onColorChanged: { data: object({
|
|
|
111625
112074
|
]),
|
|
111626
112075
|
rgb: RgbTripletSchema.optional(),
|
|
111627
112076
|
hsv: HsvTripletSchema.optional(),
|
|
111628
|
-
|
|
112077
|
+
colorTemperature: number().int().optional(),
|
|
111629
112078
|
lastChangedAt: number()
|
|
111630
112079
|
}) } },
|
|
111631
112080
|
status: {
|
|
@@ -112514,11 +112963,11 @@ var DishwasherStatusSchema = object({
|
|
|
112514
112963
|
/** Feature keys of the programs the appliance currently offers — drives the UI selector. */
|
|
112515
112964
|
availablePrograms: array(string()),
|
|
112516
112965
|
/** Seconds remaining on the active cycle, or null when not running / not known. */
|
|
112517
|
-
|
|
112966
|
+
remaining: number().int().nonnegative().nullable().meta(readingMeta("duration")),
|
|
112518
112967
|
/** Seconds the delayed start is set to (time until the cycle starts), or null. */
|
|
112519
|
-
|
|
112968
|
+
delayedStart: number().int().nonnegative().nullable().meta(readingMeta("duration")),
|
|
112520
112969
|
/** Cycle completion 0..100, or null when not applicable. */
|
|
112521
|
-
|
|
112970
|
+
progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
112522
112971
|
/** True when the door is reported open; null when the door state is not known. */
|
|
112523
112972
|
doorOpen: boolean().nullable(),
|
|
112524
112973
|
/** True when the door is latched (locked) during a cycle; null when not reported. */
|
|
@@ -112737,14 +113186,14 @@ var dishwasherCapability = {
|
|
|
112737
113186
|
runtimeState: DishwasherStatusSchema,
|
|
112738
113187
|
durability: "session",
|
|
112739
113188
|
history: { fields: {
|
|
112740
|
-
|
|
113189
|
+
progress: {
|
|
112741
113190
|
kind: "gauge",
|
|
112742
113191
|
unit: "%",
|
|
112743
113192
|
deadband: 2
|
|
112744
113193
|
},
|
|
112745
113194
|
state: { kind: "enum" },
|
|
112746
113195
|
operationState: { kind: "enum" },
|
|
112747
|
-
|
|
113196
|
+
remaining: {
|
|
112748
113197
|
kind: "gauge",
|
|
112749
113198
|
unit: "s",
|
|
112750
113199
|
minIntervalMs: 6e4
|
|
@@ -113335,7 +113784,7 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
|
|
|
113335
113784
|
var FanControlStatusSchema = object({
|
|
113336
113785
|
/** Active speed as 0..100 inclusive. Null when the device has no
|
|
113337
113786
|
* speed surface (single-speed fan). */
|
|
113338
|
-
|
|
113787
|
+
speed: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
113339
113788
|
/** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
|
|
113340
113789
|
* a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
|
|
113341
113790
|
percentageStep: number().positive().optional().meta(readingMeta("percentage")),
|
|
@@ -113362,7 +113811,7 @@ var fanControlCapability = {
|
|
|
113362
113811
|
methods: {
|
|
113363
113812
|
setPercentage: method(object({
|
|
113364
113813
|
deviceId: number().int().nonnegative(),
|
|
113365
|
-
|
|
113814
|
+
speed: number().min(0).max(100)
|
|
113366
113815
|
}), _void(), {
|
|
113367
113816
|
kind: "mutation",
|
|
113368
113817
|
auth: "protected"
|
|
@@ -113395,7 +113844,7 @@ var fanControlCapability = {
|
|
|
113395
113844
|
},
|
|
113396
113845
|
/**
|
|
113397
113846
|
* Runtime-state slice — mirrored by the kernel. UI fan speed
|
|
113398
|
-
* sliders read `
|
|
113847
|
+
* sliders read `speed` for live updates.
|
|
113399
113848
|
*/
|
|
113400
113849
|
runtimeState: FanControlStatusSchema,
|
|
113401
113850
|
/**
|
|
@@ -113409,7 +113858,7 @@ var fanControlCapability = {
|
|
|
113409
113858
|
* History (D850): recorded series. Actuator tier: speed keeps every change.
|
|
113410
113859
|
*/
|
|
113411
113860
|
history: { fields: {
|
|
113412
|
-
|
|
113861
|
+
speed: {
|
|
113413
113862
|
kind: "gauge",
|
|
113414
113863
|
unit: "%",
|
|
113415
113864
|
minIntervalMs: 0
|
|
@@ -113854,7 +114303,7 @@ var humidifierCapability = {
|
|
|
113854
114303
|
*/
|
|
113855
114304
|
var HumiditySensorStatusSchema = object({
|
|
113856
114305
|
/** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
|
|
113857
|
-
|
|
114306
|
+
humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
|
|
113858
114307
|
precision: 0,
|
|
113859
114308
|
label: "Humidity"
|
|
113860
114309
|
})),
|
|
@@ -113883,7 +114332,7 @@ var humiditySensorCapability = {
|
|
|
113883
114332
|
/**
|
|
113884
114333
|
* History (D850): recorded series.
|
|
113885
114334
|
*/
|
|
113886
|
-
history: { fields: {
|
|
114335
|
+
history: { fields: { humidity: {
|
|
113887
114336
|
kind: "gauge",
|
|
113888
114337
|
unit: "%",
|
|
113889
114338
|
deadband: .5
|
|
@@ -114523,7 +114972,7 @@ var LawnMowerControlStatusSchema = object({
|
|
|
114523
114972
|
batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
114524
114973
|
/** 0..100 mowing-completion percentage of the current task, or null when no
|
|
114525
114974
|
* task is active / progress is unavailable. */
|
|
114526
|
-
|
|
114975
|
+
progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
114527
114976
|
/** Current device/event code (dynamic — mostly status, sometimes an error),
|
|
114528
114977
|
* or null when unknown. */
|
|
114529
114978
|
currentCode: number().nullable().meta(readingMeta("none")),
|
|
@@ -115023,7 +115472,7 @@ var lockControlCapability = {
|
|
|
115023
115472
|
volatileStateFields: ["lastChangedAt"]
|
|
115024
115473
|
};
|
|
115025
115474
|
/**
|
|
115026
|
-
* A monotonic total between resets (D944). Energy uses `power-meter.
|
|
115475
|
+
* A monotonic total between resets (D944). Energy uses `power-meter.energy`
|
|
115027
115476
|
* so dashboards keep one series; every other instantaneous reading lands here.
|
|
115028
115477
|
* `value` is the time integral in the source quantity's accumulated base
|
|
115029
115478
|
* (base-unit seconds, or kWh on the power meter). `null` is a gap, never zero.
|
|
@@ -115117,10 +115566,10 @@ var MediaPlayerStatusSchema = object({
|
|
|
115117
115566
|
currentMedia: MediaInfoSchema.nullable(),
|
|
115118
115567
|
/** Current playback position in ms. Null when not seekable or
|
|
115119
115568
|
* nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
|
|
115120
|
-
|
|
115569
|
+
position: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
|
|
115121
115570
|
/** Total duration of the current media in ms. Null when unknown
|
|
115122
115571
|
* (live stream) or nothing is playing. */
|
|
115123
|
-
|
|
115572
|
+
duration: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
|
|
115124
115573
|
/** Shuffle toggle. Null when no shuffle surface.
|
|
115125
115574
|
* Pair with `DeviceFeature.MediaPlayerShuffle`. */
|
|
115126
115575
|
shuffle: boolean().nullable(),
|
|
@@ -115159,7 +115608,7 @@ var mediaPlayerCapability = {
|
|
|
115159
115608
|
}),
|
|
115160
115609
|
seek: method(object({
|
|
115161
115610
|
deviceId: number().int().nonnegative(),
|
|
115162
|
-
|
|
115611
|
+
position: number().int().nonnegative()
|
|
115163
115612
|
}), _void(), {
|
|
115164
115613
|
kind: "mutation",
|
|
115165
115614
|
auth: "protected"
|
|
@@ -115231,7 +115680,7 @@ var mediaPlayerCapability = {
|
|
|
115231
115680
|
/**
|
|
115232
115681
|
* History (D850): volume (a level an operator or an automation sets; the
|
|
115233
115682
|
* default 10 s floor folds a slider drag into its end value) and mute.
|
|
115234
|
-
* `
|
|
115683
|
+
* `position` is not recorded: it moves continuously while playing.
|
|
115235
115684
|
*/
|
|
115236
115685
|
history: { fields: {
|
|
115237
115686
|
volumeLevel: {
|
|
@@ -116330,9 +116779,9 @@ var NetworkLinkStatusSchema = object({
|
|
|
116330
116779
|
* one whose reading has not landed must not be drawn at 0 %. Consumers
|
|
116331
116780
|
* SKIP a null rather than coerce it.
|
|
116332
116781
|
*/
|
|
116333
|
-
|
|
116782
|
+
signal: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
116334
116783
|
/** Raw received signal strength in dBm, when the firmware reports one. */
|
|
116335
|
-
|
|
116784
|
+
rssi: number().optional().meta(readingMeta("signal-level")),
|
|
116336
116785
|
/** Network name of a wireless link, when the firmware reports it. */
|
|
116337
116786
|
ssid: string().optional(),
|
|
116338
116787
|
/** Ms epoch of the last observation. Lets consumers reason about freshness. */
|
|
@@ -116341,7 +116790,7 @@ var NetworkLinkStatusSchema = object({
|
|
|
116341
116790
|
/** The slice a provider seeds before its first read: nothing is known yet. */
|
|
116342
116791
|
var NETWORK_LINK_UNKNOWN = {
|
|
116343
116792
|
type: "unknown",
|
|
116344
|
-
|
|
116793
|
+
signal: null,
|
|
116345
116794
|
lastUpdated: 0
|
|
116346
116795
|
};
|
|
116347
116796
|
/**
|
|
@@ -116402,13 +116851,13 @@ onStatusChanged: { data: object({
|
|
|
116402
116851
|
* History (D850): recorded series.
|
|
116403
116852
|
*/
|
|
116404
116853
|
history: { fields: {
|
|
116405
|
-
|
|
116854
|
+
signal: {
|
|
116406
116855
|
kind: "gauge",
|
|
116407
116856
|
unit: "%",
|
|
116408
116857
|
deadband: 3,
|
|
116409
116858
|
minIntervalMs: 6e4
|
|
116410
116859
|
},
|
|
116411
|
-
|
|
116860
|
+
rssi: {
|
|
116412
116861
|
kind: "gauge",
|
|
116413
116862
|
unit: "dBm",
|
|
116414
116863
|
deadband: 2,
|
|
@@ -117728,16 +118177,16 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
|
|
|
117728
118177
|
* in real-world devices.
|
|
117729
118178
|
*/
|
|
117730
118179
|
var PowerMeterStatusSchema = object({
|
|
117731
|
-
/** Instantaneous power draw
|
|
117732
|
-
|
|
118180
|
+
/** Instantaneous power draw (watts). */
|
|
118181
|
+
power: number().optional().meta(readingMeta("power", { label: "Power" })),
|
|
117733
118182
|
/** Cumulative energy in kilowatt-hours since the meter was reset. */
|
|
117734
|
-
|
|
117735
|
-
/** Voltage
|
|
117736
|
-
|
|
118183
|
+
energy: number().optional().meta(readingMeta("energy", { label: "Energy" })),
|
|
118184
|
+
/** Voltage (volts). */
|
|
118185
|
+
voltage: number().optional().meta(readingMeta("voltage", { label: "Voltage" })),
|
|
117737
118186
|
/** Current in amperes. */
|
|
117738
|
-
|
|
118187
|
+
current: number().optional().meta(readingMeta("current", { label: "Current" })),
|
|
117739
118188
|
/** Apparent power in volt-amperes. */
|
|
117740
|
-
|
|
118189
|
+
apparentPower: number().optional().meta(readingMeta("apparent-power")),
|
|
117741
118190
|
/** Power factor (0..1) if reported. */
|
|
117742
118191
|
powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
|
|
117743
118192
|
/** Ms epoch when the slice was last updated. */
|
|
@@ -117795,14 +118244,14 @@ function powerTarget(id, unit) {
|
|
|
117795
118244
|
advancedExpression: true
|
|
117796
118245
|
};
|
|
117797
118246
|
}
|
|
117798
|
-
/** "Power and energy" extension:
|
|
118247
|
+
/** "Power and energy" extension: power, energy, voltage and current from other devices (D951). */
|
|
117799
118248
|
var powerMeterCustomize = {
|
|
117800
118249
|
version: 1,
|
|
117801
118250
|
targets: [
|
|
117802
|
-
powerTarget("
|
|
117803
|
-
powerTarget("
|
|
117804
|
-
powerTarget("
|
|
117805
|
-
powerTarget("
|
|
118251
|
+
powerTarget("power", "W"),
|
|
118252
|
+
powerTarget("energy", "kWh"),
|
|
118253
|
+
powerTarget("voltage", "V"),
|
|
118254
|
+
powerTarget("current", "A")
|
|
117806
118255
|
]
|
|
117807
118256
|
};
|
|
117808
118257
|
var powerMeterCapability = {
|
|
@@ -117818,41 +118267,41 @@ var powerMeterCapability = {
|
|
|
117818
118267
|
},
|
|
117819
118268
|
runtimeState: PowerMeterStatusSchema,
|
|
117820
118269
|
/**
|
|
117821
|
-
* Runtime-state durability: **restored** — as `numeric-sensor`; `
|
|
118270
|
+
* Runtime-state durability: **restored** — as `numeric-sensor`; `energy` is an accumulator whose restored value is the baseline.
|
|
117822
118271
|
*
|
|
117823
118272
|
* See `RuntimeStateDurability`. Enforced by
|
|
117824
118273
|
* `scripts/check-runtime-state-durability.ts`.
|
|
117825
118274
|
*/
|
|
117826
118275
|
durability: "restored",
|
|
117827
118276
|
/**
|
|
117828
|
-
* History (D850): recorded series. Gauges are floored at 30 s; `
|
|
118277
|
+
* History (D850): recorded series. Gauges are floored at 30 s; `energy` is the one monotonic meter (counter).
|
|
117829
118278
|
*/
|
|
117830
118279
|
history: { fields: {
|
|
117831
|
-
|
|
118280
|
+
power: {
|
|
117832
118281
|
kind: "gauge",
|
|
117833
118282
|
unit: "W",
|
|
117834
118283
|
deadband: 1,
|
|
117835
118284
|
minIntervalMs: 3e4
|
|
117836
118285
|
},
|
|
117837
|
-
|
|
118286
|
+
energy: {
|
|
117838
118287
|
kind: "counter",
|
|
117839
118288
|
unit: "kWh",
|
|
117840
118289
|
deadband: .01,
|
|
117841
118290
|
minIntervalMs: 6e4
|
|
117842
118291
|
},
|
|
117843
|
-
|
|
118292
|
+
voltage: {
|
|
117844
118293
|
kind: "gauge",
|
|
117845
118294
|
unit: "V",
|
|
117846
118295
|
deadband: 1,
|
|
117847
118296
|
minIntervalMs: 3e4
|
|
117848
118297
|
},
|
|
117849
|
-
|
|
118298
|
+
current: {
|
|
117850
118299
|
kind: "gauge",
|
|
117851
118300
|
unit: "A",
|
|
117852
118301
|
deadband: .05,
|
|
117853
118302
|
minIntervalMs: 3e4
|
|
117854
118303
|
},
|
|
117855
|
-
|
|
118304
|
+
apparentPower: {
|
|
117856
118305
|
kind: "gauge",
|
|
117857
118306
|
unit: "VA",
|
|
117858
118307
|
deadband: 1,
|
|
@@ -117902,9 +118351,6 @@ var PresenceStatusSchema = object({
|
|
|
117902
118351
|
location: string().nullable(),
|
|
117903
118352
|
/** GPS coordinates when available. Null otherwise. */
|
|
117904
118353
|
gps: GpsLocationSchema.nullable(),
|
|
117905
|
-
/** Optional battery level of the tracking device (0..100). Null
|
|
117906
|
-
* when not reported. */
|
|
117907
|
-
batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
117908
118354
|
/** Ms epoch when the slice was last updated. */
|
|
117909
118355
|
lastChangedAt: number()
|
|
117910
118356
|
});
|
|
@@ -117933,14 +118379,7 @@ var presenceCapability = {
|
|
|
117933
118379
|
* `scripts/check-runtime-state-durability.ts`.
|
|
117934
118380
|
*/
|
|
117935
118381
|
durability: "restored",
|
|
117936
|
-
|
|
117937
|
-
* History (D850): recorded series.
|
|
117938
|
-
*/
|
|
117939
|
-
history: { fields: { batteryPercent: {
|
|
117940
|
-
kind: "gauge",
|
|
117941
|
-
unit: "%",
|
|
117942
|
-
deadband: 1
|
|
117943
|
-
} } },
|
|
118382
|
+
history: "none",
|
|
117944
118383
|
/** Clock fields: written, but excluded from the compare that decides
|
|
117945
118384
|
* whether persisting is worth a SQLite commit. */
|
|
117946
118385
|
volatileStateFields: ["lastChangedAt"]
|
|
@@ -117955,7 +118394,7 @@ var presenceCapability = {
|
|
|
117955
118394
|
*/
|
|
117956
118395
|
var PressureSensorStatusSchema = object({
|
|
117957
118396
|
/** Current pressure in hPa. `null` when the sample was refused (D393). */
|
|
117958
|
-
|
|
118397
|
+
pressure: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
|
|
117959
118398
|
/** Ms epoch when the slice was last updated. */
|
|
117960
118399
|
lastFetchedAt: number()
|
|
117961
118400
|
});
|
|
@@ -117981,7 +118420,7 @@ var pressureSensorCapability = {
|
|
|
117981
118420
|
/**
|
|
117982
118421
|
* History (D850): recorded series.
|
|
117983
118422
|
*/
|
|
117984
|
-
history: { fields: {
|
|
118423
|
+
history: { fields: { pressure: {
|
|
117985
118424
|
kind: "gauge",
|
|
117986
118425
|
unit: "hPa",
|
|
117987
118426
|
deadband: .1
|
|
@@ -121688,7 +122127,7 @@ var tamperCapability = {
|
|
|
121688
122127
|
*/
|
|
121689
122128
|
var TemperatureSensorStatusSchema = object({
|
|
121690
122129
|
/** Current temperature in Celsius. `null` when the sample was refused (D393). */
|
|
121691
|
-
|
|
122130
|
+
temperature: number().nullable().meta(readingMeta("temperature", {
|
|
121692
122131
|
precision: 1,
|
|
121693
122132
|
label: "Temperature"
|
|
121694
122133
|
})),
|
|
@@ -121717,7 +122156,7 @@ var temperatureSensorCapability = {
|
|
|
121717
122156
|
/**
|
|
121718
122157
|
* History (D850): recorded series.
|
|
121719
122158
|
*/
|
|
121720
|
-
history: { fields: {
|
|
122159
|
+
history: { fields: { temperature: {
|
|
121721
122160
|
kind: "gauge",
|
|
121722
122161
|
unit: "°C",
|
|
121723
122162
|
deadband: .1
|
|
@@ -122014,7 +122453,7 @@ var VacuumControlStatusSchema = object({
|
|
|
122014
122453
|
/** Speed tokens the hardware accepts — drives the UI selector. */
|
|
122015
122454
|
availableFanSpeeds: array(string()),
|
|
122016
122455
|
/** 0..100 cleaning-completion percentage of the current task, or null. */
|
|
122017
|
-
|
|
122456
|
+
progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
122018
122457
|
/** Current error code (0 / null = no error). */
|
|
122019
122458
|
errorCode: number().nullable().meta(readingMeta("none")),
|
|
122020
122459
|
/** Human label for {@link errorCode}, or null when none / undecodable. */
|
|
@@ -124096,7 +124535,7 @@ var BaseDevice = class {
|
|
|
124096
124535
|
* `device-local-state.ts`. Drivers access via:
|
|
124097
124536
|
*
|
|
124098
124537
|
* `this.state.battery.sleeping = true` // patches the battery slice
|
|
124099
|
-
* `const pct = this.state.battery.
|
|
124538
|
+
* `const pct = this.state.battery.level` // reads the battery slice
|
|
124100
124539
|
* `this.state.deviceStatus.online = true` // mirrors `markOnline(true)`
|
|
124101
124540
|
*
|
|
124102
124541
|
* Adding a new cap with `runtimeState:` automatically extends this
|
|
@@ -124136,7 +124575,7 @@ var BaseDevice = class {
|
|
|
124136
124575
|
* shape; drivers don't redeclare it, they just write through.
|
|
124137
124576
|
*
|
|
124138
124577
|
* Read: `this.runtimeState.getCapState('battery')` →
|
|
124139
|
-
* `{
|
|
124578
|
+
* `{level, charging, sleeping, lastUpdated}` for any
|
|
124140
124579
|
* provider that registers `batteryCapability`.
|
|
124141
124580
|
* Write: `this.runtimeState.setCapState('battery', { … })`.
|
|
124142
124581
|
*
|
|
@@ -125326,6 +125765,104 @@ Object.freeze({
|
|
|
125326
125765
|
access: "create",
|
|
125327
125766
|
role: "admin"
|
|
125328
125767
|
},
|
|
125768
|
+
"actions.create": {
|
|
125769
|
+
capName: "actions",
|
|
125770
|
+
capScope: "system",
|
|
125771
|
+
addonId: null,
|
|
125772
|
+
access: "create",
|
|
125773
|
+
role: "admin"
|
|
125774
|
+
},
|
|
125775
|
+
"actions.delete": {
|
|
125776
|
+
capName: "actions",
|
|
125777
|
+
capScope: "system",
|
|
125778
|
+
addonId: null,
|
|
125779
|
+
access: "delete",
|
|
125780
|
+
role: "admin"
|
|
125781
|
+
},
|
|
125782
|
+
"actions.get": {
|
|
125783
|
+
capName: "actions",
|
|
125784
|
+
capScope: "system",
|
|
125785
|
+
addonId: null,
|
|
125786
|
+
access: "view",
|
|
125787
|
+
role: "viewer"
|
|
125788
|
+
},
|
|
125789
|
+
"actions.getLinks": {
|
|
125790
|
+
capName: "actions",
|
|
125791
|
+
capScope: "system",
|
|
125792
|
+
addonId: null,
|
|
125793
|
+
access: "view",
|
|
125794
|
+
role: "admin"
|
|
125795
|
+
},
|
|
125796
|
+
"actions.getTraces": {
|
|
125797
|
+
capName: "actions",
|
|
125798
|
+
capScope: "system",
|
|
125799
|
+
addonId: null,
|
|
125800
|
+
access: "view",
|
|
125801
|
+
role: "admin"
|
|
125802
|
+
},
|
|
125803
|
+
"actions.list": {
|
|
125804
|
+
capName: "actions",
|
|
125805
|
+
capScope: "system",
|
|
125806
|
+
addonId: null,
|
|
125807
|
+
access: "view",
|
|
125808
|
+
role: "admin"
|
|
125809
|
+
},
|
|
125810
|
+
"actions.listForCamera": {
|
|
125811
|
+
capName: "actions",
|
|
125812
|
+
capScope: "system",
|
|
125813
|
+
addonId: null,
|
|
125814
|
+
access: "view",
|
|
125815
|
+
role: "viewer"
|
|
125816
|
+
},
|
|
125817
|
+
"actions.run": {
|
|
125818
|
+
capName: "actions",
|
|
125819
|
+
capScope: "system",
|
|
125820
|
+
addonId: null,
|
|
125821
|
+
access: "create",
|
|
125822
|
+
role: "viewer"
|
|
125823
|
+
},
|
|
125824
|
+
"actions.setLinks": {
|
|
125825
|
+
capName: "actions",
|
|
125826
|
+
capScope: "system",
|
|
125827
|
+
addonId: null,
|
|
125828
|
+
access: "create",
|
|
125829
|
+
role: "admin"
|
|
125830
|
+
},
|
|
125831
|
+
"actions.stop": {
|
|
125832
|
+
capName: "actions",
|
|
125833
|
+
capScope: "system",
|
|
125834
|
+
addonId: null,
|
|
125835
|
+
access: "create",
|
|
125836
|
+
role: "admin"
|
|
125837
|
+
},
|
|
125838
|
+
"actions.update": {
|
|
125839
|
+
capName: "actions",
|
|
125840
|
+
capScope: "system",
|
|
125841
|
+
addonId: null,
|
|
125842
|
+
access: "create",
|
|
125843
|
+
role: "admin"
|
|
125844
|
+
},
|
|
125845
|
+
"actionSource.listActions": {
|
|
125846
|
+
capName: "action-source",
|
|
125847
|
+
capScope: "system",
|
|
125848
|
+
addonId: null,
|
|
125849
|
+
access: "view",
|
|
125850
|
+
role: "admin"
|
|
125851
|
+
},
|
|
125852
|
+
"actionSource.runAction": {
|
|
125853
|
+
capName: "action-source",
|
|
125854
|
+
capScope: "system",
|
|
125855
|
+
addonId: null,
|
|
125856
|
+
access: "create",
|
|
125857
|
+
role: "admin"
|
|
125858
|
+
},
|
|
125859
|
+
"actionSource.stopAction": {
|
|
125860
|
+
capName: "action-source",
|
|
125861
|
+
capScope: "system",
|
|
125862
|
+
addonId: null,
|
|
125863
|
+
access: "create",
|
|
125864
|
+
role: "admin"
|
|
125865
|
+
},
|
|
125329
125866
|
"addonPages.listPages": {
|
|
125330
125867
|
capName: "addon-pages",
|
|
125331
125868
|
capScope: "system",
|
|
@@ -126607,6 +127144,13 @@ Object.freeze({
|
|
|
126607
127144
|
access: "view",
|
|
126608
127145
|
role: "admin"
|
|
126609
127146
|
},
|
|
127147
|
+
"coreBlocks.getAutomationRunState": {
|
|
127148
|
+
capName: "core-blocks",
|
|
127149
|
+
capScope: "system",
|
|
127150
|
+
addonId: null,
|
|
127151
|
+
access: "view",
|
|
127152
|
+
role: "admin"
|
|
127153
|
+
},
|
|
126610
127154
|
"coreBlocks.getBlockSettings": {
|
|
126611
127155
|
capName: "core-blocks",
|
|
126612
127156
|
capScope: "system",
|
|
@@ -126628,6 +127172,13 @@ Object.freeze({
|
|
|
126628
127172
|
access: "view",
|
|
126629
127173
|
role: "admin"
|
|
126630
127174
|
},
|
|
127175
|
+
"coreBlocks.getTraces": {
|
|
127176
|
+
capName: "core-blocks",
|
|
127177
|
+
capScope: "system",
|
|
127178
|
+
addonId: null,
|
|
127179
|
+
access: "view",
|
|
127180
|
+
role: "admin"
|
|
127181
|
+
},
|
|
126631
127182
|
"coreBlocks.getTypeDefs": {
|
|
126632
127183
|
capName: "core-blocks",
|
|
126633
127184
|
capScope: "system",
|
|
@@ -126663,6 +127214,13 @@ Object.freeze({
|
|
|
126663
127214
|
access: "create",
|
|
126664
127215
|
role: "admin"
|
|
126665
127216
|
},
|
|
127217
|
+
"coreBlocks.reportAutomationRun": {
|
|
127218
|
+
capName: "core-blocks",
|
|
127219
|
+
capScope: "system",
|
|
127220
|
+
addonId: null,
|
|
127221
|
+
access: "create",
|
|
127222
|
+
role: "admin"
|
|
127223
|
+
},
|
|
126666
127224
|
"coreBlocks.restart": {
|
|
126667
127225
|
capName: "core-blocks",
|
|
126668
127226
|
capScope: "system",
|
|
@@ -135236,7 +135794,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
135236
135794
|
label: t("Full brightness", "Luminosità massima"),
|
|
135237
135795
|
phrase: t("Set {device} to full brightness", "Porta {device} alla massima luminosità"),
|
|
135238
135796
|
icon: "sun",
|
|
135239
|
-
args: {
|
|
135797
|
+
args: { level: 100 }
|
|
135240
135798
|
},
|
|
135241
135799
|
{
|
|
135242
135800
|
id: "brightness.half",
|
|
@@ -135244,7 +135802,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
135244
135802
|
label: t("Half brightness", "Luminosità a metà"),
|
|
135245
135803
|
phrase: t("Dim {device} to half", "Abbassa {device} a metà luminosità"),
|
|
135246
135804
|
icon: "sun-dim",
|
|
135247
|
-
args: {
|
|
135805
|
+
args: { level: 50 }
|
|
135248
135806
|
},
|
|
135249
135807
|
{
|
|
135250
135808
|
id: "brightness.off",
|
|
@@ -135252,16 +135810,16 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
135252
135810
|
label: t("Brightness off", "Luminosità a zero"),
|
|
135253
135811
|
phrase: t("Dim {device} to zero", "Abbassa {device} a zero"),
|
|
135254
135812
|
icon: "lightbulb-off",
|
|
135255
|
-
args: {
|
|
135813
|
+
args: { level: 0 }
|
|
135256
135814
|
},
|
|
135257
135815
|
{
|
|
135258
135816
|
id: "brightness.set",
|
|
135259
135817
|
method: "setBrightness",
|
|
135260
135818
|
label: t("Set brightness", "Imposta la luminosità"),
|
|
135261
|
-
phrase: t("Set the brightness of {device} to {
|
|
135819
|
+
phrase: t("Set the brightness of {device} to {level}", "Imposta la luminosità di {device} a {level}"),
|
|
135262
135820
|
icon: "sun-medium",
|
|
135263
135821
|
openArgs: [{
|
|
135264
|
-
key: "
|
|
135822
|
+
key: "level",
|
|
135265
135823
|
label: PERCENT,
|
|
135266
135824
|
offer: {
|
|
135267
135825
|
kind: "number",
|
|
@@ -135275,13 +135833,13 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
135275
135833
|
],
|
|
135276
135834
|
state: [{
|
|
135277
135835
|
kind: "numeric",
|
|
135278
|
-
field: "
|
|
135836
|
+
field: "level",
|
|
135279
135837
|
quantity: t("the brightness", "la luminosità"),
|
|
135280
135838
|
label: t("Brightness", "Luminosità"),
|
|
135281
135839
|
unit: "%",
|
|
135282
135840
|
icon: "sun"
|
|
135283
135841
|
}],
|
|
135284
|
-
events: { onBrightnessChanged: { notOperatorFacing: "the `
|
|
135842
|
+
events: { onBrightnessChanged: { notOperatorFacing: "the `level` state trigger reads the same change" } }
|
|
135285
135843
|
},
|
|
135286
135844
|
color: {
|
|
135287
135845
|
scope: "device",
|
|
@@ -135295,7 +135853,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
135295
135853
|
icon: "sun",
|
|
135296
135854
|
args: { color: {
|
|
135297
135855
|
mode: "mired",
|
|
135298
|
-
|
|
135856
|
+
colorTemperature: 370
|
|
135299
135857
|
} }
|
|
135300
135858
|
},
|
|
135301
135859
|
{
|
|
@@ -135306,7 +135864,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
135306
135864
|
icon: "snowflake",
|
|
135307
135865
|
args: { color: {
|
|
135308
135866
|
mode: "mired",
|
|
135309
|
-
|
|
135867
|
+
colorTemperature: 200
|
|
135310
135868
|
} }
|
|
135311
135869
|
},
|
|
135312
135870
|
{
|
|
@@ -135920,7 +136478,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
135920
136478
|
label: t("Fan off", "Ventilatore spento"),
|
|
135921
136479
|
phrase: t("Stop {device}", "Ferma {device}"),
|
|
135922
136480
|
icon: "power-off",
|
|
135923
|
-
args: {
|
|
136481
|
+
args: { speed: 0 }
|
|
135924
136482
|
},
|
|
135925
136483
|
{
|
|
135926
136484
|
id: "fan.full",
|
|
@@ -135928,16 +136486,16 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
135928
136486
|
label: t("Fan full", "Ventilatore al massimo"),
|
|
135929
136487
|
phrase: t("Run {device} at full speed", "Porta {device} alla massima velocità"),
|
|
135930
136488
|
icon: "fan",
|
|
135931
|
-
args: {
|
|
136489
|
+
args: { speed: 100 }
|
|
135932
136490
|
},
|
|
135933
136491
|
{
|
|
135934
136492
|
id: "fan.speed",
|
|
135935
136493
|
method: "setPercentage",
|
|
135936
136494
|
label: t("Set speed", "Imposta la velocità"),
|
|
135937
|
-
phrase: t("Set the speed of {device} to {
|
|
136495
|
+
phrase: t("Set the speed of {device} to {speed}", "Imposta la velocità di {device} al {speed}"),
|
|
135938
136496
|
icon: "fan",
|
|
135939
136497
|
openArgs: [{
|
|
135940
|
-
key: "
|
|
136498
|
+
key: "speed",
|
|
135941
136499
|
label: t("Speed", "Velocità"),
|
|
135942
136500
|
offer: {
|
|
135943
136501
|
kind: "number",
|
|
@@ -135999,7 +136557,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
135999
136557
|
],
|
|
136000
136558
|
state: [{
|
|
136001
136559
|
kind: "numeric",
|
|
136002
|
-
field: "
|
|
136560
|
+
field: "speed",
|
|
136003
136561
|
quantity: t("the speed", "la velocità"),
|
|
136004
136562
|
label: t("Speed", "Velocità"),
|
|
136005
136563
|
unit: "%",
|
|
@@ -136797,7 +137355,7 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
136797
137355
|
},
|
|
136798
137356
|
{
|
|
136799
137357
|
kind: "numeric",
|
|
136800
|
-
field: "
|
|
137358
|
+
field: "progress",
|
|
136801
137359
|
quantity: t("the cycle progress", "l’avanzamento del lavaggio"),
|
|
136802
137360
|
label: t("Progress", "Avanzamento"),
|
|
136803
137361
|
unit: "%",
|
|
@@ -137155,10 +137713,10 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
137155
137713
|
id: "media.seek",
|
|
137156
137714
|
method: "seek",
|
|
137157
137715
|
label: t("Seek", "Vai al punto"),
|
|
137158
|
-
phrase: t("Move {device} to {
|
|
137716
|
+
phrase: t("Move {device} to {position}", "Porta {device} al punto {position}"),
|
|
137159
137717
|
icon: "fast-forward",
|
|
137160
137718
|
openArgs: [{
|
|
137161
|
-
key: "
|
|
137719
|
+
key: "position",
|
|
137162
137720
|
label: t("Position", "Posizione"),
|
|
137163
137721
|
offer: {
|
|
137164
137722
|
kind: "number",
|
|
@@ -138207,7 +138765,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138207
138765
|
state: [
|
|
138208
138766
|
{
|
|
138209
138767
|
kind: "numeric",
|
|
138210
|
-
field: "
|
|
138768
|
+
field: "co2",
|
|
138211
138769
|
quantity: t("the CO₂", "la CO₂"),
|
|
138212
138770
|
label: t("CO₂", "CO₂"),
|
|
138213
138771
|
unit: "ppm",
|
|
@@ -138215,7 +138773,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138215
138773
|
},
|
|
138216
138774
|
{
|
|
138217
138775
|
kind: "numeric",
|
|
138218
|
-
field: "
|
|
138776
|
+
field: "voc",
|
|
138219
138777
|
quantity: t("the VOC level", "il livello di COV"),
|
|
138220
138778
|
label: t("VOC", "COV"),
|
|
138221
138779
|
unit: "ppb",
|
|
@@ -138251,7 +138809,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138251
138809
|
noun: t("Light level", "Luminosità ambiente"),
|
|
138252
138810
|
state: [{
|
|
138253
138811
|
kind: "numeric",
|
|
138254
|
-
field: "
|
|
138812
|
+
field: "illuminance",
|
|
138255
138813
|
quantity: t("the light level", "la luminosità"),
|
|
138256
138814
|
label: t("Illuminance", "Illuminamento"),
|
|
138257
138815
|
unit: "lx",
|
|
@@ -138454,7 +139012,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138454
139012
|
noun: t("Humidity", "Umidità"),
|
|
138455
139013
|
state: [{
|
|
138456
139014
|
kind: "numeric",
|
|
138457
|
-
field: "
|
|
139015
|
+
field: "humidity",
|
|
138458
139016
|
quantity: t("the humidity", "l’umidità"),
|
|
138459
139017
|
label: t("Humidity", "Umidità"),
|
|
138460
139018
|
unit: "%",
|
|
@@ -138502,7 +139060,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138502
139060
|
state: [
|
|
138503
139061
|
{
|
|
138504
139062
|
kind: "numeric",
|
|
138505
|
-
field: "
|
|
139063
|
+
field: "power",
|
|
138506
139064
|
quantity: t("the power draw", "il consumo"),
|
|
138507
139065
|
label: t("Power", "Potenza"),
|
|
138508
139066
|
unit: "W",
|
|
@@ -138510,7 +139068,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138510
139068
|
},
|
|
138511
139069
|
{
|
|
138512
139070
|
kind: "numeric",
|
|
138513
|
-
field: "
|
|
139071
|
+
field: "energy",
|
|
138514
139072
|
quantity: t("the energy used", "l’energia consumata"),
|
|
138515
139073
|
label: t("Energy", "Energia"),
|
|
138516
139074
|
unit: "kWh",
|
|
@@ -138518,7 +139076,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138518
139076
|
},
|
|
138519
139077
|
{
|
|
138520
139078
|
kind: "numeric",
|
|
138521
|
-
field: "
|
|
139079
|
+
field: "voltage",
|
|
138522
139080
|
quantity: t("the voltage", "la tensione"),
|
|
138523
139081
|
label: t("Voltage", "Tensione"),
|
|
138524
139082
|
unit: "V",
|
|
@@ -138526,7 +139084,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138526
139084
|
},
|
|
138527
139085
|
{
|
|
138528
139086
|
kind: "numeric",
|
|
138529
|
-
field: "
|
|
139087
|
+
field: "current",
|
|
138530
139088
|
quantity: t("the current", "la corrente"),
|
|
138531
139089
|
label: t("Current", "Corrente"),
|
|
138532
139090
|
unit: "A",
|
|
@@ -138543,13 +139101,6 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138543
139101
|
label: t("Where", "Posizione"),
|
|
138544
139102
|
phrase: t("When {device} is at {value}", "Quando {device} è in {value}"),
|
|
138545
139103
|
icon: "map-pin"
|
|
138546
|
-
}, {
|
|
138547
|
-
kind: "numeric",
|
|
138548
|
-
field: "batteryPercent",
|
|
138549
|
-
quantity: t("the phone battery", "la batteria del telefono"),
|
|
138550
|
-
label: t("Battery", "Batteria"),
|
|
138551
|
-
unit: "%",
|
|
138552
|
-
icon: "battery"
|
|
138553
139104
|
}]
|
|
138554
139105
|
},
|
|
138555
139106
|
"pressure-sensor": {
|
|
@@ -138557,7 +139108,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138557
139108
|
noun: t("Pressure", "Pressione"),
|
|
138558
139109
|
state: [{
|
|
138559
139110
|
kind: "numeric",
|
|
138560
|
-
field: "
|
|
139111
|
+
field: "pressure",
|
|
138561
139112
|
quantity: t("the pressure", "la pressione"),
|
|
138562
139113
|
label: t("Pressure", "Pressione"),
|
|
138563
139114
|
unit: "hPa",
|
|
@@ -138593,7 +139144,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138593
139144
|
noun: t("Temperature", "Temperatura"),
|
|
138594
139145
|
state: [{
|
|
138595
139146
|
kind: "numeric",
|
|
138596
|
-
field: "
|
|
139147
|
+
field: "temperature",
|
|
138597
139148
|
quantity: t("the temperature", "la temperatura"),
|
|
138598
139149
|
label: t("Temperature", "Temperatura"),
|
|
138599
139150
|
unit: "°C",
|
|
@@ -138664,7 +139215,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138664
139215
|
state: [
|
|
138665
139216
|
{
|
|
138666
139217
|
kind: "numeric",
|
|
138667
|
-
field: "
|
|
139218
|
+
field: "level",
|
|
138668
139219
|
quantity: t("the battery level", "il livello della batteria"),
|
|
138669
139220
|
label: t("Battery", "Batteria"),
|
|
138670
139221
|
unit: "%",
|
|
@@ -138706,7 +139257,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138706
139257
|
icon: "battery-charging"
|
|
138707
139258
|
}
|
|
138708
139259
|
],
|
|
138709
|
-
events: { onStatusChanged: { notOperatorFacing: "the
|
|
139260
|
+
events: { onStatusChanged: { notOperatorFacing: "the level / sleeping / charging state triggers each read one field of this change" } },
|
|
138710
139261
|
extraEvents: [{
|
|
138711
139262
|
category: EventCategory.DeviceAwake,
|
|
138712
139263
|
label: t("Woke up", "Risvegliato"),
|
|
@@ -138724,7 +139275,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
138724
139275
|
noun: t("Network link", "Collegamento di rete"),
|
|
138725
139276
|
state: [{
|
|
138726
139277
|
kind: "numeric",
|
|
138727
|
-
field: "
|
|
139278
|
+
field: "signal",
|
|
138728
139279
|
quantity: t("the signal strength", "l’intensità del segnale"),
|
|
138729
139280
|
label: t("Signal", "Segnale"),
|
|
138730
139281
|
unit: "%",
|
|
@@ -138817,6 +139368,8 @@ var ML = "model and training machinery — no device action";
|
|
|
138817
139368
|
var INTERNAL = "internal plumbing between addons — never operator-facing";
|
|
138818
139369
|
/** Every system-scoped cap, with why it is not a device's vocabulary. */
|
|
138819
139370
|
var SYSTEM_CAP_VOCABULARY = {
|
|
139371
|
+
"action-source": system(INTERNAL),
|
|
139372
|
+
actions: system("declared actions — run from the Actions page and from the cameras that link them"),
|
|
138820
139373
|
"addon-pages": system(UI_PLUMBING),
|
|
138821
139374
|
"addon-pages-source": system(UI_PLUMBING),
|
|
138822
139375
|
"addon-routes": system(UI_PLUMBING),
|
|
@@ -139013,6 +139566,31 @@ Object.freeze({
|
|
|
139013
139566
|
form: "single",
|
|
139014
139567
|
optional: false
|
|
139015
139568
|
}],
|
|
139569
|
+
"actions.get": [{
|
|
139570
|
+
name: "deviceId",
|
|
139571
|
+
form: "single",
|
|
139572
|
+
optional: true
|
|
139573
|
+
}],
|
|
139574
|
+
"actions.getLinks": [{
|
|
139575
|
+
name: "deviceId",
|
|
139576
|
+
form: "single",
|
|
139577
|
+
optional: false
|
|
139578
|
+
}],
|
|
139579
|
+
"actions.listForCamera": [{
|
|
139580
|
+
name: "deviceId",
|
|
139581
|
+
form: "single",
|
|
139582
|
+
optional: false
|
|
139583
|
+
}],
|
|
139584
|
+
"actions.run": [{
|
|
139585
|
+
name: "deviceId",
|
|
139586
|
+
form: "single",
|
|
139587
|
+
optional: true
|
|
139588
|
+
}],
|
|
139589
|
+
"actions.setLinks": [{
|
|
139590
|
+
name: "deviceId",
|
|
139591
|
+
form: "single",
|
|
139592
|
+
optional: false
|
|
139593
|
+
}],
|
|
139016
139594
|
"addonSettings.getDeviceSettings": [{
|
|
139017
139595
|
name: "deviceId",
|
|
139018
139596
|
form: "single",
|
|
@@ -143184,6 +143762,7 @@ function ingestReading(input) {
|
|
|
143184
143762
|
value: converted
|
|
143185
143763
|
};
|
|
143186
143764
|
}
|
|
143765
|
+
String.raw`(?<=^|["'(\s>,\[{])`;
|
|
143187
143766
|
var MB = 1024 * 1024;
|
|
143188
143767
|
1024 * MB, 3072 * MB;
|
|
143189
143768
|
//#endregion
|
|
@@ -143310,6 +143889,334 @@ function buildConnectionFormSchema() {
|
|
|
143310
143889
|
] };
|
|
143311
143890
|
}
|
|
143312
143891
|
//#endregion
|
|
143892
|
+
//#region src/dreame-vacuum-decode.ts
|
|
143893
|
+
/**
|
|
143894
|
+
* Pure decode helpers for the Dreame vacuum. Translate the library's MIoT enum
|
|
143895
|
+
* values into the camstack `vacuum-control` slice shape. Unit-tested in
|
|
143896
|
+
* isolation (no facade, no network) — mirrors `homematic-decode.ts`.
|
|
143897
|
+
*/
|
|
143898
|
+
/**
|
|
143899
|
+
* Map a Dreame {@link MiotState} (siid 2 piid 1) onto the camstack canonical
|
|
143900
|
+
* `VacuumState` lifecycle (`idle` / `cleaning` / `paused` / `returning` /
|
|
143901
|
+
* `docked` / `error`). A `null` state (not yet observed) settles on `idle`.
|
|
143902
|
+
*
|
|
143903
|
+
* The MIoT state space is large (~50 values); we collapse it onto the six
|
|
143904
|
+
* canonical states by family. Anything unrecognised falls through to `idle`
|
|
143905
|
+
* (the safe neutral) so a firmware-added code never throws.
|
|
143906
|
+
*/
|
|
143907
|
+
function mapMiotStateToVacuumState(state) {
|
|
143908
|
+
if (state === null) return "idle";
|
|
143909
|
+
switch (state) {
|
|
143910
|
+
case MiotState.Charging:
|
|
143911
|
+
case MiotState.ChargingComplete:
|
|
143912
|
+
case MiotState.IntelligentCharging:
|
|
143913
|
+
case MiotState.CleanWashboardBase:
|
|
143914
|
+
case MiotState.AutoRepairBase:
|
|
143915
|
+
case MiotState.WaterSupplyDrainTest:
|
|
143916
|
+
case MiotState.CleanMopRefillWater:
|
|
143917
|
+
case MiotState.AutoEmptying:
|
|
143918
|
+
case MiotState.Emptying:
|
|
143919
|
+
case MiotState.Draining:
|
|
143920
|
+
case MiotState.AutoWaterDraining:
|
|
143921
|
+
case MiotState.Sanitizing: return "docked";
|
|
143922
|
+
case MiotState.MopDrying:
|
|
143923
|
+
case MiotState.DustBagDrying:
|
|
143924
|
+
case MiotState.SanitizingWithDry: return "drying";
|
|
143925
|
+
case MiotState.ReturningToCharge:
|
|
143926
|
+
case MiotState.ReturningToWash:
|
|
143927
|
+
case MiotState.ReturnInstallMop:
|
|
143928
|
+
case MiotState.ReturnRemoveMop:
|
|
143929
|
+
case MiotState.ReturnToEmpty:
|
|
143930
|
+
case MiotState.ReturningToDrain: return "returning";
|
|
143931
|
+
case MiotState.Paused:
|
|
143932
|
+
case MiotState.PausedAlt:
|
|
143933
|
+
case MiotState.PausedCleaning:
|
|
143934
|
+
case MiotState.DustBagDryingPaused:
|
|
143935
|
+
case MiotState.CameraMonitoringPaused:
|
|
143936
|
+
case MiotState.FindingPetPaused:
|
|
143937
|
+
case MiotState.InitialDeepCleanPaused:
|
|
143938
|
+
case MiotState.ChangingMopPaused:
|
|
143939
|
+
case MiotState.FloorMaintainingPaused: return "paused";
|
|
143940
|
+
case MiotState.Standby:
|
|
143941
|
+
case MiotState.WaitingForTask: return "idle";
|
|
143942
|
+
default: return "cleaning";
|
|
143943
|
+
}
|
|
143944
|
+
}
|
|
143945
|
+
/**
|
|
143946
|
+
* The camstack `fanSpeed` token for a Dreame {@link SuctionLevel} — the enum
|
|
143947
|
+
* member NAME, lower-cased (`Quiet` → `'quiet'`). Returns `null` for an
|
|
143948
|
+
* unobserved / unknown level.
|
|
143949
|
+
*/
|
|
143950
|
+
function suctionLevelToken(level) {
|
|
143951
|
+
if (level === null) return null;
|
|
143952
|
+
const name = SuctionLevel[level];
|
|
143953
|
+
return typeof name === "string" ? name.toLowerCase() : null;
|
|
143954
|
+
}
|
|
143955
|
+
/**
|
|
143956
|
+
* Resolve a camstack `fanSpeed` token back to the Dreame {@link SuctionLevel}
|
|
143957
|
+
* enum value, validated against the model's `supported` levels. Returns null
|
|
143958
|
+
* when the token doesn't match a supported level (the caller rejects the set).
|
|
143959
|
+
*/
|
|
143960
|
+
function tokenToSuctionLevel(token, supported) {
|
|
143961
|
+
const wanted = token.trim().toLowerCase();
|
|
143962
|
+
for (const level of supported) if (suctionLevelToken(level) === wanted) return level;
|
|
143963
|
+
return null;
|
|
143964
|
+
}
|
|
143965
|
+
/** The list of accepted `fanSpeed` tokens for a model's supported suction set. */
|
|
143966
|
+
function supportedFanSpeedTokens(supported) {
|
|
143967
|
+
const tokens = [];
|
|
143968
|
+
for (const level of supported) {
|
|
143969
|
+
const token = suctionLevelToken(level);
|
|
143970
|
+
if (token !== null) tokens.push(token);
|
|
143971
|
+
}
|
|
143972
|
+
return tokens;
|
|
143973
|
+
}
|
|
143974
|
+
/**
|
|
143975
|
+
* Clamp a raw battery reading (already a 0..100 percentage from the library)
|
|
143976
|
+
* to the `vacuum-control` slice's `batteryLevel` field. Returns null for a
|
|
143977
|
+
* null/NaN/out-of-band reading so the slice stays valid (the schema rejects
|
|
143978
|
+
* a number outside 0..100).
|
|
143979
|
+
*/
|
|
143980
|
+
function clampBatteryLevel$1(value) {
|
|
143981
|
+
if (value === null || !Number.isFinite(value)) return null;
|
|
143982
|
+
return Math.max(0, Math.min(100, value));
|
|
143983
|
+
}
|
|
143984
|
+
/**
|
|
143985
|
+
* MiotError codes that are BENIGN completion / reminder states, not active
|
|
143986
|
+
* faults. A fresh cloud-shadow read of an idle docked robot legitimately
|
|
143987
|
+
* returns `TaskComplete` (68); it is not an error to show. The Error sensor
|
|
143988
|
+
* (`dreame-entity-catalog.ts`) and the vacuum card both read this set.
|
|
143989
|
+
* Extend it if other non-fault codes surface as spurious "errors".
|
|
143990
|
+
*/
|
|
143991
|
+
var BENIGN_ERROR_CODES = new Set([MiotError.TaskComplete]);
|
|
143992
|
+
/** The card's error label: null for no error and for a benign completion code. */
|
|
143993
|
+
function vacuumErrorLabel(code) {
|
|
143994
|
+
if (!code || BENIGN_ERROR_CODES.has(code)) return null;
|
|
143995
|
+
return `Error ${code}`;
|
|
143996
|
+
}
|
|
143997
|
+
/** Clamp a 0..100 percentage (progress, coverage, …), null-safe. */
|
|
143998
|
+
function clampPercent$1(value) {
|
|
143999
|
+
if (value === null || !Number.isFinite(value)) return null;
|
|
144000
|
+
return Math.max(0, Math.min(100, value));
|
|
144001
|
+
}
|
|
144002
|
+
/**
|
|
144003
|
+
* Where each {@link VacuumTankProps} field lives (siid/piid). Property ids and
|
|
144004
|
+
* enum values are the HA Dreame integration's (Tasshack `dreame-vacuum`
|
|
144005
|
+
* `types.py`, `DreameVacuumProperty` → siid/piid map; same on `dev` and
|
|
144006
|
+
* v2.0.0b25, checked 2026-09-26). 27/1 and 27/4 were already read here; 27/2 and
|
|
144007
|
+
* 27/3 are their neighbours on the same station service. The robot pushes them
|
|
144008
|
+
* on change; the consumables refresh also re-reads them from the shadow.
|
|
144009
|
+
*/
|
|
144010
|
+
var VACUUM_TANK_PROPS = {
|
|
144011
|
+
cleanWaterTankStatus: {
|
|
144012
|
+
siid: 27,
|
|
144013
|
+
piid: 1
|
|
144014
|
+
},
|
|
144015
|
+
waterTank: {
|
|
144016
|
+
siid: 4,
|
|
144017
|
+
piid: 6
|
|
144018
|
+
},
|
|
144019
|
+
detergentStatus: {
|
|
144020
|
+
siid: 27,
|
|
144021
|
+
piid: 4
|
|
144022
|
+
},
|
|
144023
|
+
dustCollection: {
|
|
144024
|
+
siid: 15,
|
|
144025
|
+
piid: 3
|
|
144026
|
+
},
|
|
144027
|
+
dirtyWaterTankStatus: {
|
|
144028
|
+
siid: 27,
|
|
144029
|
+
piid: 2
|
|
144030
|
+
},
|
|
144031
|
+
dustBagStatus: {
|
|
144032
|
+
siid: 27,
|
|
144033
|
+
piid: 3
|
|
144034
|
+
}
|
|
144035
|
+
};
|
|
144036
|
+
/**
|
|
144037
|
+
* Clean-water tank `status`: prefer the richer clean-water-tank-status (27/1),
|
|
144038
|
+
* fall back to the coarser water-tank presence (4/6). `null` when neither maps
|
|
144039
|
+
* to a known good/low reading (NOT_INSTALLED / CHECKING / unknown). Pure.
|
|
144040
|
+
*/
|
|
144041
|
+
function cleanWaterTankStatus(props) {
|
|
144042
|
+
if (props.cleanWaterTankStatus !== null) {
|
|
144043
|
+
if (props.cleanWaterTankStatus === 0) return "ok";
|
|
144044
|
+
if (props.cleanWaterTankStatus === 2) return "low";
|
|
144045
|
+
return null;
|
|
144046
|
+
}
|
|
144047
|
+
if (props.waterTank === 1 || props.waterTank === 10) return "ok";
|
|
144048
|
+
return null;
|
|
144049
|
+
}
|
|
144050
|
+
/** Detergent tank `status` from detergent-status (27/4). Pure. */
|
|
144051
|
+
function detergentTankStatus(value) {
|
|
144052
|
+
if (value === 0) return "ok";
|
|
144053
|
+
if (value === 2) return "low";
|
|
144054
|
+
return null;
|
|
144055
|
+
}
|
|
144056
|
+
/**
|
|
144057
|
+
* Dust-bin `status` from dust-collection (15/3); OVER_USE reads as `full`. A dust
|
|
144058
|
+
* bag that is missing or asks to be checked (27/3 NOT_INSTALLED / CHECK) is never
|
|
144059
|
+
* `ok` — the enum does not say "full" either, so it is unknown. Pure.
|
|
144060
|
+
*/
|
|
144061
|
+
function dustBinTankStatus(dustCollection, dustBag) {
|
|
144062
|
+
if (dustCollection === 2) return "full";
|
|
144063
|
+
if (dustBag !== null && dustBag !== 0) return null;
|
|
144064
|
+
if (dustCollection === 1) return "ok";
|
|
144065
|
+
return null;
|
|
144066
|
+
}
|
|
144067
|
+
/**
|
|
144068
|
+
* Dirty-water tank `status` from dirty-water-tank-status (27/2). The robot has
|
|
144069
|
+
* ONE value for "not installed or full" and cannot tell them apart; lifting the
|
|
144070
|
+
* tank out to EMPTY it reads exactly like a full one, so that value is unknown
|
|
144071
|
+
* (`null`) — never `full`, which would raise a false "tank full" at the moment
|
|
144072
|
+
* someone is fixing it (controller ruling 2026-09-26). Absent or any other
|
|
144073
|
+
* value is unknown too, never `ok`.
|
|
144074
|
+
*
|
|
144075
|
+
* The unambiguous "full" is the robot's own error `MiotError.DirtyWaterTankFull`
|
|
144076
|
+
* (86, the library's `errorCode`, siid 2 piid 2): while it is raised the tank is
|
|
144077
|
+
* `full` whatever 27/2 says; once it clears, 27/2 decides again. Pure.
|
|
144078
|
+
*/
|
|
144079
|
+
function dirtyWaterTankStatus(value, errorCode) {
|
|
144080
|
+
if (errorCode === MiotError.DirtyWaterTankFull) return "full";
|
|
144081
|
+
if (value === 0) return "ok";
|
|
144082
|
+
return null;
|
|
144083
|
+
}
|
|
144084
|
+
/**
|
|
144085
|
+
* The four tanks (clean-water / dirty-water / detergent / dust-bin) decoded from the model's {@link VacuumCapabilities}, with the
|
|
144086
|
+
* discrete `status` populated from the live MIoT enum props when supplied. Pure.
|
|
144087
|
+
*
|
|
144088
|
+
* The nodedreame library exposes NO tank FILL-LEVEL telemetry — the cloud shadow
|
|
144089
|
+
* (verified against a real `dreame.vacuum.r2538z`) carries only the water-VOLUME
|
|
144090
|
+
* *setting* (a mop-flow level, NOT a tank fill). We therefore never fabricate a
|
|
144091
|
+
* `level`; `level` is always `null`. The cap contract is honoured on PRESENCE,
|
|
144092
|
+
* with `status` derived from the tank-status enums:
|
|
144093
|
+
*
|
|
144094
|
+
* - a field is `null` when the hardware HAS NO such tank (capability flags), and
|
|
144095
|
+
* - `{ level: null, status }` when the tank EXISTS — `status` from the enum
|
|
144096
|
+
* prop ('ok' / 'low' / 'full'), or `null` when the device reports none.
|
|
144097
|
+
*
|
|
144098
|
+
* Presence is derived from the capability record:
|
|
144099
|
+
* - clean-water ← `canMop` (a mopping robot carries a clean-water reservoir)
|
|
144100
|
+
* - dirty-water ← `canMopWash` (a self-wash dock has a recovery/dirty tank)
|
|
144101
|
+
* - detergent ← `hasDetergentReservoir`
|
|
144102
|
+
* - dust-bin ← `canAutoEmpty` (an auto-empty robot has an onboard dust bin)
|
|
144103
|
+
*/
|
|
144104
|
+
function decodeVacuumTanks(caps, props, errorCode = null) {
|
|
144105
|
+
const tank = (present, status) => present ? {
|
|
144106
|
+
level: null,
|
|
144107
|
+
status
|
|
144108
|
+
} : null;
|
|
144109
|
+
return {
|
|
144110
|
+
cleanWater: tank(Boolean(caps?.canMop), props ? cleanWaterTankStatus(props) : null),
|
|
144111
|
+
dirtyWater: tank(Boolean(caps?.canMopWash), dirtyWaterTankStatus(props?.dirtyWaterTankStatus ?? null, errorCode)),
|
|
144112
|
+
detergent: tank(Boolean(caps?.hasDetergentReservoir), props ? detergentTankStatus(props.detergentStatus) : null),
|
|
144113
|
+
dustBin: tank(Boolean(caps?.canAutoEmpty), props ? dustBinTankStatus(props.dustCollection, props.dustBagStatus) : null)
|
|
144114
|
+
};
|
|
144115
|
+
}
|
|
144116
|
+
/** How each tank is named and what it is, in the order the panel lists them. */
|
|
144117
|
+
var TANK_ITEMS = [
|
|
144118
|
+
{
|
|
144119
|
+
key: "cleanWater",
|
|
144120
|
+
label: "Clean water tank",
|
|
144121
|
+
kind: "supply",
|
|
144122
|
+
form: "tank"
|
|
144123
|
+
},
|
|
144124
|
+
{
|
|
144125
|
+
key: "dirtyWater",
|
|
144126
|
+
label: "Dirty water tank",
|
|
144127
|
+
kind: "waste",
|
|
144128
|
+
form: "tank"
|
|
144129
|
+
},
|
|
144130
|
+
{
|
|
144131
|
+
key: "detergent",
|
|
144132
|
+
label: "Detergent",
|
|
144133
|
+
kind: "supply",
|
|
144134
|
+
form: "product"
|
|
144135
|
+
},
|
|
144136
|
+
{
|
|
144137
|
+
key: "dustBin",
|
|
144138
|
+
label: "Dust bin",
|
|
144139
|
+
kind: "waste",
|
|
144140
|
+
form: "bin"
|
|
144141
|
+
}
|
|
144142
|
+
];
|
|
144143
|
+
/**
|
|
144144
|
+
* The tanks as `consumables` items (D915): every PRESENT tank becomes a
|
|
144145
|
+
* `supply` (clean water, detergent) or `waste` (dirty water, dust bin) item
|
|
144146
|
+
* keyed by its old field name, drawn as a `tank` (the two water tanks), a
|
|
144147
|
+
* `product` (detergent) or a `bin` (the dust bin) — the key the notification centre's tank
|
|
144148
|
+
* episodes were built on, so an open episode carries on. A tank is never
|
|
144149
|
+
* resettable: emptying or refilling it is the reset. Pure.
|
|
144150
|
+
*/
|
|
144151
|
+
function vacuumTankItems(tanks) {
|
|
144152
|
+
return TANK_ITEMS.flatMap(({ key, label, kind, form }) => {
|
|
144153
|
+
const tank = tanks[key];
|
|
144154
|
+
if (tank === null) return [];
|
|
144155
|
+
return [{
|
|
144156
|
+
key,
|
|
144157
|
+
label,
|
|
144158
|
+
level: tank.level,
|
|
144159
|
+
status: tank.status,
|
|
144160
|
+
kind,
|
|
144161
|
+
form,
|
|
144162
|
+
lastResetAt: null,
|
|
144163
|
+
resettable: false
|
|
144164
|
+
}];
|
|
144165
|
+
});
|
|
144166
|
+
}
|
|
144167
|
+
/**
|
|
144168
|
+
* Clamp a raw consumable percentage to `[0, 100]`, or return `null` when the
|
|
144169
|
+
* value is absent / non-finite. The library reports remaining life as a 0..100
|
|
144170
|
+
* integer; we clamp defensively so an out-of-range firmware value never violates
|
|
144171
|
+
* the cap schema.
|
|
144172
|
+
*/
|
|
144173
|
+
function clampConsumablePct(value) {
|
|
144174
|
+
if (value === null || !Number.isFinite(value)) return null;
|
|
144175
|
+
return Math.max(0, Math.min(100, value));
|
|
144176
|
+
}
|
|
144177
|
+
/**
|
|
144178
|
+
* The `consumables` cap items for the consumables a vacuum REPORTS.
|
|
144179
|
+
*
|
|
144180
|
+
* PRESENCE-driven (mirrors HA): the library's `supportedConsumables` enumerates
|
|
144181
|
+
* ONLY the consumables this model reports (a present MIoT life property), each
|
|
144182
|
+
* with its remaining-life % and whether a reset action exists. No hardcoded
|
|
144183
|
+
* per-model list: a consumable the device does not expose never appears.
|
|
144184
|
+
*
|
|
144185
|
+
* `status` is `null` — the provider reports the level, the notification rule
|
|
144186
|
+
* decides what is low. The slice clocks are `nextConsumablesSlice`'s job. Pure.
|
|
144187
|
+
*/
|
|
144188
|
+
function buildVacuumConsumableItems(readings) {
|
|
144189
|
+
return readings.map((c) => ({
|
|
144190
|
+
key: c.key,
|
|
144191
|
+
label: c.label,
|
|
144192
|
+
level: clampConsumablePct(c.leftPct),
|
|
144193
|
+
status: null,
|
|
144194
|
+
lastResetAt: null,
|
|
144195
|
+
resettable: c.resettable,
|
|
144196
|
+
form: "part"
|
|
144197
|
+
}));
|
|
144198
|
+
}
|
|
144199
|
+
/** Structural equality of two JSON-shaped values, key order ignored. */
|
|
144200
|
+
function jsonEqual(a, b) {
|
|
144201
|
+
if (a === b) return true;
|
|
144202
|
+
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]));
|
|
144203
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
|
|
144204
|
+
const ka = Object.keys(a);
|
|
144205
|
+
const kb = Object.keys(b);
|
|
144206
|
+
return ka.length === kb.length && ka.every((k) => jsonEqual(Reflect.get(a, k), Reflect.get(b, k)));
|
|
144207
|
+
}
|
|
144208
|
+
/**
|
|
144209
|
+
* The `vacuum-control` slice to write, or `null` when only the clock moved.
|
|
144210
|
+
* `recomputeSlice` runs on EVERY push; without this each one was a write (and a
|
|
144211
|
+
* `device.state-changed`) even when nothing the robot reported had changed. Pure.
|
|
144212
|
+
*/
|
|
144213
|
+
function nextVacuumControlSlice(prev, next) {
|
|
144214
|
+
if (prev === void 0) return next;
|
|
144215
|
+
const { lastChangedAt: _prevAt, ...prevContent } = prev;
|
|
144216
|
+
const { lastChangedAt: _nextAt, ...nextContent } = next;
|
|
144217
|
+
return jsonEqual(prevContent, nextContent) ? null : next;
|
|
144218
|
+
}
|
|
144219
|
+
//#endregion
|
|
143313
144220
|
//#region src/dreame-entity-catalog.ts
|
|
143314
144221
|
var STATS = "Statistics";
|
|
143315
144222
|
var MAINT = "Maintenance";
|
|
@@ -143956,14 +144863,6 @@ function enumOptions(e) {
|
|
|
143956
144863
|
label: humanizeEnumName(name)
|
|
143957
144864
|
}));
|
|
143958
144865
|
}
|
|
143959
|
-
/**
|
|
143960
|
-
* MiotError codes that are BENIGN completion / reminder states, not active
|
|
143961
|
-
* faults. HA does not surface these as errors — a fresh cloud-shadow read of an
|
|
143962
|
-
* idle docked robot legitimately returns `TaskComplete` (68). We relabel them to
|
|
143963
|
-
* the `Clear` label so the Error sensor only shows real faults (HA parity).
|
|
143964
|
-
* Extend this set if other non-fault codes surface as spurious "errors".
|
|
143965
|
-
*/
|
|
143966
|
-
var BENIGN_ERROR_CODES = new Set([MiotError.TaskComplete]);
|
|
143967
144866
|
/** `MiotError` options with the benign completion codes remapped to the `Clear`
|
|
143968
144867
|
* label, so the Error sensor reads as no-error for them (matches HA). */
|
|
143969
144868
|
function errorEnumOptions() {
|
|
@@ -145321,321 +146220,6 @@ var DreameButtonDevice = class extends DreameChildDevice {
|
|
|
145321
146220
|
recomputeSlice() {}
|
|
145322
146221
|
};
|
|
145323
146222
|
//#endregion
|
|
145324
|
-
//#region src/dreame-vacuum-decode.ts
|
|
145325
|
-
/**
|
|
145326
|
-
* Pure decode helpers for the Dreame vacuum. Translate the library's MIoT enum
|
|
145327
|
-
* values into the camstack `vacuum-control` slice shape. Unit-tested in
|
|
145328
|
-
* isolation (no facade, no network) — mirrors `homematic-decode.ts`.
|
|
145329
|
-
*/
|
|
145330
|
-
/**
|
|
145331
|
-
* Map a Dreame {@link MiotState} (siid 2 piid 1) onto the camstack canonical
|
|
145332
|
-
* `VacuumState` lifecycle (`idle` / `cleaning` / `paused` / `returning` /
|
|
145333
|
-
* `docked` / `error`). A `null` state (not yet observed) settles on `idle`.
|
|
145334
|
-
*
|
|
145335
|
-
* The MIoT state space is large (~50 values); we collapse it onto the six
|
|
145336
|
-
* canonical states by family. Anything unrecognised falls through to `idle`
|
|
145337
|
-
* (the safe neutral) so a firmware-added code never throws.
|
|
145338
|
-
*/
|
|
145339
|
-
function mapMiotStateToVacuumState(state) {
|
|
145340
|
-
if (state === null) return "idle";
|
|
145341
|
-
switch (state) {
|
|
145342
|
-
case MiotState.Charging:
|
|
145343
|
-
case MiotState.ChargingComplete:
|
|
145344
|
-
case MiotState.IntelligentCharging:
|
|
145345
|
-
case MiotState.CleanWashboardBase:
|
|
145346
|
-
case MiotState.AutoRepairBase:
|
|
145347
|
-
case MiotState.WaterSupplyDrainTest:
|
|
145348
|
-
case MiotState.CleanMopRefillWater:
|
|
145349
|
-
case MiotState.AutoEmptying:
|
|
145350
|
-
case MiotState.Emptying:
|
|
145351
|
-
case MiotState.Draining:
|
|
145352
|
-
case MiotState.AutoWaterDraining:
|
|
145353
|
-
case MiotState.Sanitizing: return "docked";
|
|
145354
|
-
case MiotState.MopDrying:
|
|
145355
|
-
case MiotState.DustBagDrying:
|
|
145356
|
-
case MiotState.SanitizingWithDry: return "drying";
|
|
145357
|
-
case MiotState.ReturningToCharge:
|
|
145358
|
-
case MiotState.ReturningToWash:
|
|
145359
|
-
case MiotState.ReturnInstallMop:
|
|
145360
|
-
case MiotState.ReturnRemoveMop:
|
|
145361
|
-
case MiotState.ReturnToEmpty:
|
|
145362
|
-
case MiotState.ReturningToDrain: return "returning";
|
|
145363
|
-
case MiotState.Paused:
|
|
145364
|
-
case MiotState.PausedAlt:
|
|
145365
|
-
case MiotState.PausedCleaning:
|
|
145366
|
-
case MiotState.DustBagDryingPaused:
|
|
145367
|
-
case MiotState.CameraMonitoringPaused:
|
|
145368
|
-
case MiotState.FindingPetPaused:
|
|
145369
|
-
case MiotState.InitialDeepCleanPaused:
|
|
145370
|
-
case MiotState.ChangingMopPaused:
|
|
145371
|
-
case MiotState.FloorMaintainingPaused: return "paused";
|
|
145372
|
-
case MiotState.Standby:
|
|
145373
|
-
case MiotState.WaitingForTask: return "idle";
|
|
145374
|
-
default: return "cleaning";
|
|
145375
|
-
}
|
|
145376
|
-
}
|
|
145377
|
-
/**
|
|
145378
|
-
* The camstack `fanSpeed` token for a Dreame {@link SuctionLevel} — the enum
|
|
145379
|
-
* member NAME, lower-cased (`Quiet` → `'quiet'`). Returns `null` for an
|
|
145380
|
-
* unobserved / unknown level.
|
|
145381
|
-
*/
|
|
145382
|
-
function suctionLevelToken(level) {
|
|
145383
|
-
if (level === null) return null;
|
|
145384
|
-
const name = SuctionLevel[level];
|
|
145385
|
-
return typeof name === "string" ? name.toLowerCase() : null;
|
|
145386
|
-
}
|
|
145387
|
-
/**
|
|
145388
|
-
* Resolve a camstack `fanSpeed` token back to the Dreame {@link SuctionLevel}
|
|
145389
|
-
* enum value, validated against the model's `supported` levels. Returns null
|
|
145390
|
-
* when the token doesn't match a supported level (the caller rejects the set).
|
|
145391
|
-
*/
|
|
145392
|
-
function tokenToSuctionLevel(token, supported) {
|
|
145393
|
-
const wanted = token.trim().toLowerCase();
|
|
145394
|
-
for (const level of supported) if (suctionLevelToken(level) === wanted) return level;
|
|
145395
|
-
return null;
|
|
145396
|
-
}
|
|
145397
|
-
/** The list of accepted `fanSpeed` tokens for a model's supported suction set. */
|
|
145398
|
-
function supportedFanSpeedTokens(supported) {
|
|
145399
|
-
const tokens = [];
|
|
145400
|
-
for (const level of supported) {
|
|
145401
|
-
const token = suctionLevelToken(level);
|
|
145402
|
-
if (token !== null) tokens.push(token);
|
|
145403
|
-
}
|
|
145404
|
-
return tokens;
|
|
145405
|
-
}
|
|
145406
|
-
/**
|
|
145407
|
-
* Clamp a raw battery reading (already a 0..100 percentage from the library)
|
|
145408
|
-
* to the `vacuum-control` slice's `batteryLevel` field. Returns null for a
|
|
145409
|
-
* null/NaN/out-of-band reading so the slice stays valid (the schema rejects
|
|
145410
|
-
* a number outside 0..100).
|
|
145411
|
-
*/
|
|
145412
|
-
function clampBatteryLevel$1(value) {
|
|
145413
|
-
if (value === null || !Number.isFinite(value)) return null;
|
|
145414
|
-
return Math.max(0, Math.min(100, value));
|
|
145415
|
-
}
|
|
145416
|
-
/** Clamp a 0..100 percentage (progress, coverage, …), null-safe. */
|
|
145417
|
-
function clampPercent$1(value) {
|
|
145418
|
-
if (value === null || !Number.isFinite(value)) return null;
|
|
145419
|
-
return Math.max(0, Math.min(100, value));
|
|
145420
|
-
}
|
|
145421
|
-
/**
|
|
145422
|
-
* Where each {@link VacuumTankProps} field lives (siid/piid). Property ids and
|
|
145423
|
-
* enum values are the HA Dreame integration's (Tasshack `dreame-vacuum`
|
|
145424
|
-
* `types.py`, `DreameVacuumProperty` → siid/piid map; same on `dev` and
|
|
145425
|
-
* v2.0.0b25, checked 2026-09-26). 27/1 and 27/4 were already read here; 27/2 and
|
|
145426
|
-
* 27/3 are their neighbours on the same station service. The robot pushes them
|
|
145427
|
-
* on change; the consumables refresh also re-reads them from the shadow.
|
|
145428
|
-
*/
|
|
145429
|
-
var VACUUM_TANK_PROPS = {
|
|
145430
|
-
cleanWaterTankStatus: {
|
|
145431
|
-
siid: 27,
|
|
145432
|
-
piid: 1
|
|
145433
|
-
},
|
|
145434
|
-
waterTank: {
|
|
145435
|
-
siid: 4,
|
|
145436
|
-
piid: 6
|
|
145437
|
-
},
|
|
145438
|
-
detergentStatus: {
|
|
145439
|
-
siid: 27,
|
|
145440
|
-
piid: 4
|
|
145441
|
-
},
|
|
145442
|
-
dustCollection: {
|
|
145443
|
-
siid: 15,
|
|
145444
|
-
piid: 3
|
|
145445
|
-
},
|
|
145446
|
-
dirtyWaterTankStatus: {
|
|
145447
|
-
siid: 27,
|
|
145448
|
-
piid: 2
|
|
145449
|
-
},
|
|
145450
|
-
dustBagStatus: {
|
|
145451
|
-
siid: 27,
|
|
145452
|
-
piid: 3
|
|
145453
|
-
}
|
|
145454
|
-
};
|
|
145455
|
-
/**
|
|
145456
|
-
* Clean-water tank `status`: prefer the richer clean-water-tank-status (27/1),
|
|
145457
|
-
* fall back to the coarser water-tank presence (4/6). `null` when neither maps
|
|
145458
|
-
* to a known good/low reading (NOT_INSTALLED / CHECKING / unknown). Pure.
|
|
145459
|
-
*/
|
|
145460
|
-
function cleanWaterTankStatus(props) {
|
|
145461
|
-
if (props.cleanWaterTankStatus !== null) {
|
|
145462
|
-
if (props.cleanWaterTankStatus === 0) return "ok";
|
|
145463
|
-
if (props.cleanWaterTankStatus === 2) return "low";
|
|
145464
|
-
return null;
|
|
145465
|
-
}
|
|
145466
|
-
if (props.waterTank === 1 || props.waterTank === 10) return "ok";
|
|
145467
|
-
return null;
|
|
145468
|
-
}
|
|
145469
|
-
/** Detergent tank `status` from detergent-status (27/4). Pure. */
|
|
145470
|
-
function detergentTankStatus(value) {
|
|
145471
|
-
if (value === 0) return "ok";
|
|
145472
|
-
if (value === 2) return "low";
|
|
145473
|
-
return null;
|
|
145474
|
-
}
|
|
145475
|
-
/**
|
|
145476
|
-
* Dust-bin `status` from dust-collection (15/3); OVER_USE reads as `full`. A dust
|
|
145477
|
-
* bag that is missing or asks to be checked (27/3 NOT_INSTALLED / CHECK) is never
|
|
145478
|
-
* `ok` — the enum does not say "full" either, so it is unknown. Pure.
|
|
145479
|
-
*/
|
|
145480
|
-
function dustBinTankStatus(dustCollection, dustBag) {
|
|
145481
|
-
if (dustCollection === 2) return "full";
|
|
145482
|
-
if (dustBag !== null && dustBag !== 0) return null;
|
|
145483
|
-
if (dustCollection === 1) return "ok";
|
|
145484
|
-
return null;
|
|
145485
|
-
}
|
|
145486
|
-
/**
|
|
145487
|
-
* Dirty-water tank `status` from dirty-water-tank-status (27/2). The robot has
|
|
145488
|
-
* ONE value for "not installed or full" and cannot tell them apart; lifting the
|
|
145489
|
-
* tank out to EMPTY it reads exactly like a full one, so that value is unknown
|
|
145490
|
-
* (`null`) — never `full`, which would raise a false "tank full" at the moment
|
|
145491
|
-
* someone is fixing it (controller ruling 2026-09-26). Absent or any other
|
|
145492
|
-
* value is unknown too, never `ok`.
|
|
145493
|
-
*
|
|
145494
|
-
* The unambiguous "full" is the robot's own error `MiotError.DirtyWaterTankFull`
|
|
145495
|
-
* (86, the library's `errorCode`, siid 2 piid 2): while it is raised the tank is
|
|
145496
|
-
* `full` whatever 27/2 says; once it clears, 27/2 decides again. Pure.
|
|
145497
|
-
*/
|
|
145498
|
-
function dirtyWaterTankStatus(value, errorCode) {
|
|
145499
|
-
if (errorCode === MiotError.DirtyWaterTankFull) return "full";
|
|
145500
|
-
if (value === 0) return "ok";
|
|
145501
|
-
return null;
|
|
145502
|
-
}
|
|
145503
|
-
/**
|
|
145504
|
-
* The four tanks (clean-water / dirty-water / detergent / dust-bin) decoded from the model's {@link VacuumCapabilities}, with the
|
|
145505
|
-
* discrete `status` populated from the live MIoT enum props when supplied. Pure.
|
|
145506
|
-
*
|
|
145507
|
-
* The nodedreame library exposes NO tank FILL-LEVEL telemetry — the cloud shadow
|
|
145508
|
-
* (verified against a real `dreame.vacuum.r2538z`) carries only the water-VOLUME
|
|
145509
|
-
* *setting* (a mop-flow level, NOT a tank fill). We therefore never fabricate a
|
|
145510
|
-
* `level`; `level` is always `null`. The cap contract is honoured on PRESENCE,
|
|
145511
|
-
* with `status` derived from the tank-status enums:
|
|
145512
|
-
*
|
|
145513
|
-
* - a field is `null` when the hardware HAS NO such tank (capability flags), and
|
|
145514
|
-
* - `{ level: null, status }` when the tank EXISTS — `status` from the enum
|
|
145515
|
-
* prop ('ok' / 'low' / 'full'), or `null` when the device reports none.
|
|
145516
|
-
*
|
|
145517
|
-
* Presence is derived from the capability record:
|
|
145518
|
-
* - clean-water ← `canMop` (a mopping robot carries a clean-water reservoir)
|
|
145519
|
-
* - dirty-water ← `canMopWash` (a self-wash dock has a recovery/dirty tank)
|
|
145520
|
-
* - detergent ← `hasDetergentReservoir`
|
|
145521
|
-
* - dust-bin ← `canAutoEmpty` (an auto-empty robot has an onboard dust bin)
|
|
145522
|
-
*/
|
|
145523
|
-
function decodeVacuumTanks(caps, props, errorCode = null) {
|
|
145524
|
-
const tank = (present, status) => present ? {
|
|
145525
|
-
level: null,
|
|
145526
|
-
status
|
|
145527
|
-
} : null;
|
|
145528
|
-
return {
|
|
145529
|
-
cleanWater: tank(Boolean(caps?.canMop), props ? cleanWaterTankStatus(props) : null),
|
|
145530
|
-
dirtyWater: tank(Boolean(caps?.canMopWash), dirtyWaterTankStatus(props?.dirtyWaterTankStatus ?? null, errorCode)),
|
|
145531
|
-
detergent: tank(Boolean(caps?.hasDetergentReservoir), props ? detergentTankStatus(props.detergentStatus) : null),
|
|
145532
|
-
dustBin: tank(Boolean(caps?.canAutoEmpty), props ? dustBinTankStatus(props.dustCollection, props.dustBagStatus) : null)
|
|
145533
|
-
};
|
|
145534
|
-
}
|
|
145535
|
-
/** How each tank is named and what it is, in the order the panel lists them. */
|
|
145536
|
-
var TANK_ITEMS = [
|
|
145537
|
-
{
|
|
145538
|
-
key: "cleanWater",
|
|
145539
|
-
label: "Clean water tank",
|
|
145540
|
-
kind: "supply",
|
|
145541
|
-
form: "tank"
|
|
145542
|
-
},
|
|
145543
|
-
{
|
|
145544
|
-
key: "dirtyWater",
|
|
145545
|
-
label: "Dirty water tank",
|
|
145546
|
-
kind: "waste",
|
|
145547
|
-
form: "tank"
|
|
145548
|
-
},
|
|
145549
|
-
{
|
|
145550
|
-
key: "detergent",
|
|
145551
|
-
label: "Detergent",
|
|
145552
|
-
kind: "supply",
|
|
145553
|
-
form: "product"
|
|
145554
|
-
},
|
|
145555
|
-
{
|
|
145556
|
-
key: "dustBin",
|
|
145557
|
-
label: "Dust bin",
|
|
145558
|
-
kind: "waste",
|
|
145559
|
-
form: "bin"
|
|
145560
|
-
}
|
|
145561
|
-
];
|
|
145562
|
-
/**
|
|
145563
|
-
* The tanks as `consumables` items (D915): every PRESENT tank becomes a
|
|
145564
|
-
* `supply` (clean water, detergent) or `waste` (dirty water, dust bin) item
|
|
145565
|
-
* keyed by its old field name, drawn as a `tank` (the two water tanks), a
|
|
145566
|
-
* `product` (detergent) or a `bin` (the dust bin) — the key the notification centre's tank
|
|
145567
|
-
* episodes were built on, so an open episode carries on. A tank is never
|
|
145568
|
-
* resettable: emptying or refilling it is the reset. Pure.
|
|
145569
|
-
*/
|
|
145570
|
-
function vacuumTankItems(tanks) {
|
|
145571
|
-
return TANK_ITEMS.flatMap(({ key, label, kind, form }) => {
|
|
145572
|
-
const tank = tanks[key];
|
|
145573
|
-
if (tank === null) return [];
|
|
145574
|
-
return [{
|
|
145575
|
-
key,
|
|
145576
|
-
label,
|
|
145577
|
-
level: tank.level,
|
|
145578
|
-
status: tank.status,
|
|
145579
|
-
kind,
|
|
145580
|
-
form,
|
|
145581
|
-
lastResetAt: null,
|
|
145582
|
-
resettable: false
|
|
145583
|
-
}];
|
|
145584
|
-
});
|
|
145585
|
-
}
|
|
145586
|
-
/**
|
|
145587
|
-
* Clamp a raw consumable percentage to `[0, 100]`, or return `null` when the
|
|
145588
|
-
* value is absent / non-finite. The library reports remaining life as a 0..100
|
|
145589
|
-
* integer; we clamp defensively so an out-of-range firmware value never violates
|
|
145590
|
-
* the cap schema.
|
|
145591
|
-
*/
|
|
145592
|
-
function clampConsumablePct(value) {
|
|
145593
|
-
if (value === null || !Number.isFinite(value)) return null;
|
|
145594
|
-
return Math.max(0, Math.min(100, value));
|
|
145595
|
-
}
|
|
145596
|
-
/**
|
|
145597
|
-
* The `consumables` cap items for the consumables a vacuum REPORTS.
|
|
145598
|
-
*
|
|
145599
|
-
* PRESENCE-driven (mirrors HA): the library's `supportedConsumables` enumerates
|
|
145600
|
-
* ONLY the consumables this model reports (a present MIoT life property), each
|
|
145601
|
-
* with its remaining-life % and whether a reset action exists. No hardcoded
|
|
145602
|
-
* per-model list: a consumable the device does not expose never appears.
|
|
145603
|
-
*
|
|
145604
|
-
* `status` is `null` — the provider reports the level, the notification rule
|
|
145605
|
-
* decides what is low. The slice clocks are `nextConsumablesSlice`'s job. Pure.
|
|
145606
|
-
*/
|
|
145607
|
-
function buildVacuumConsumableItems(readings) {
|
|
145608
|
-
return readings.map((c) => ({
|
|
145609
|
-
key: c.key,
|
|
145610
|
-
label: c.label,
|
|
145611
|
-
level: clampConsumablePct(c.leftPct),
|
|
145612
|
-
status: null,
|
|
145613
|
-
lastResetAt: null,
|
|
145614
|
-
resettable: c.resettable,
|
|
145615
|
-
form: "part"
|
|
145616
|
-
}));
|
|
145617
|
-
}
|
|
145618
|
-
/** Structural equality of two JSON-shaped values, key order ignored. */
|
|
145619
|
-
function jsonEqual(a, b) {
|
|
145620
|
-
if (a === b) return true;
|
|
145621
|
-
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]));
|
|
145622
|
-
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
|
|
145623
|
-
const ka = Object.keys(a);
|
|
145624
|
-
const kb = Object.keys(b);
|
|
145625
|
-
return ka.length === kb.length && ka.every((k) => jsonEqual(Reflect.get(a, k), Reflect.get(b, k)));
|
|
145626
|
-
}
|
|
145627
|
-
/**
|
|
145628
|
-
* The `vacuum-control` slice to write, or `null` when only the clock moved.
|
|
145629
|
-
* `recomputeSlice` runs on EVERY push; without this each one was a write (and a
|
|
145630
|
-
* `device.state-changed`) even when nothing the robot reported had changed. Pure.
|
|
145631
|
-
*/
|
|
145632
|
-
function nextVacuumControlSlice(prev, next) {
|
|
145633
|
-
if (prev === void 0) return next;
|
|
145634
|
-
const { lastChangedAt: _prevAt, ...prevContent } = prev;
|
|
145635
|
-
const { lastChangedAt: _nextAt, ...nextContent } = next;
|
|
145636
|
-
return jsonEqual(prevContent, nextContent) ? null : next;
|
|
145637
|
-
}
|
|
145638
|
-
//#endregion
|
|
145639
146223
|
//#region src/devices/dreame-camera-stream.ts
|
|
145640
146224
|
/**
|
|
145641
146225
|
* The robot camera as a stream the broker can pull — the pure facts.
|
|
@@ -145670,7 +146254,7 @@ function clampLevel(level) {
|
|
|
145670
146254
|
function cameraBatterySlice(facts) {
|
|
145671
146255
|
const charging = facts.charging === null ? "unknown" : facts.charging === CHARGING_STATUS_CHARGING ? "dc" : "none";
|
|
145672
146256
|
return {
|
|
145673
|
-
|
|
146257
|
+
level: clampLevel(facts.level),
|
|
145674
146258
|
charging,
|
|
145675
146259
|
sleeping: !facts.streamOpen,
|
|
145676
146260
|
lastUpdated: facts.now,
|
|
@@ -146079,7 +146663,7 @@ function networkLinkFromBars(bars, now) {
|
|
|
146079
146663
|
if (bars === null || !Number.isFinite(bars)) return null;
|
|
146080
146664
|
return {
|
|
146081
146665
|
type: "wifi",
|
|
146082
|
-
|
|
146666
|
+
signal: bars === 0 ? null : signalPercentFromBars(bars, 4),
|
|
146083
146667
|
lastUpdated: now
|
|
146084
146668
|
};
|
|
146085
146669
|
}
|
|
@@ -193282,7 +193866,7 @@ var SHADOW_POLL_MS = 12e4;
|
|
|
193282
193866
|
var COLD_START = {
|
|
193283
193867
|
activity: "idle",
|
|
193284
193868
|
batteryLevel: null,
|
|
193285
|
-
|
|
193869
|
+
progress: null,
|
|
193286
193870
|
currentCode: null,
|
|
193287
193871
|
currentCodeLabel: null,
|
|
193288
193872
|
severity: "info",
|
|
@@ -193396,11 +193980,11 @@ var DreameMowerDevice = class extends DreameChildDevice {
|
|
|
193396
193980
|
const status = mower.status;
|
|
193397
193981
|
const code = mower.faultRaw;
|
|
193398
193982
|
const livePct = clampPercent(mower.mowingProgressPct);
|
|
193399
|
-
const prevPct = this.runtimeState.getCapState(CAP_NAME$9)?.
|
|
193983
|
+
const prevPct = this.runtimeState.getCapState(CAP_NAME$9)?.progress;
|
|
193400
193984
|
const slice = {
|
|
193401
193985
|
activity: mapMowerStatusToActivity(status),
|
|
193402
193986
|
batteryLevel: clampBatteryLevel(mower.battery),
|
|
193403
|
-
|
|
193987
|
+
progress: livePct ?? prevPct ?? null,
|
|
193404
193988
|
currentCode: code,
|
|
193405
193989
|
currentCodeLabel: code !== null ? MowerFault[code] ?? null : null,
|
|
193406
193990
|
severity: mower.faultSeverity,
|
|
@@ -194340,7 +194924,7 @@ var CONTROL_COLD_START = {
|
|
|
194340
194924
|
batteryLevel: null,
|
|
194341
194925
|
fanSpeed: null,
|
|
194342
194926
|
availableFanSpeeds: [],
|
|
194343
|
-
|
|
194927
|
+
progress: null,
|
|
194344
194928
|
errorCode: null,
|
|
194345
194929
|
errorLabel: null,
|
|
194346
194930
|
lastChangedAt: 0
|
|
@@ -194494,9 +195078,9 @@ var DreameVacuumDevice = class extends DreameChildDevice {
|
|
|
194494
195078
|
batteryLevel: clampBatteryLevel$1(vacuum.battery),
|
|
194495
195079
|
fanSpeed: suctionLevelToken(vacuum.suction),
|
|
194496
195080
|
availableFanSpeeds: supportedFanSpeedTokens(vacuum.supportedSuctionLevels),
|
|
194497
|
-
|
|
195081
|
+
progress: clampPercent$1(vacuum.taskProgressPct),
|
|
194498
195082
|
errorCode: vacuum.errorCode,
|
|
194499
|
-
errorLabel: vacuum.errorCode
|
|
195083
|
+
errorLabel: vacuumErrorLabel(vacuum.errorCode),
|
|
194500
195084
|
lastChangedAt: Date.now()
|
|
194501
195085
|
};
|
|
194502
195086
|
const toWrite = nextVacuumControlSlice(this.runtimeState.getCapState(CAP_NAME$1), controlSlice);
|
|
@@ -196465,4 +197049,4 @@ var DreameProviderAddon = class extends BaseDeviceProvider {
|
|
|
196465
197049
|
}
|
|
196466
197050
|
};
|
|
196467
197051
|
//#endregion
|
|
196468
|
-
export { DreameProviderAddon,
|
|
197052
|
+
export { DreameProviderAddon, mapMiotStateToVacuumState as a, tokenToSuctionLevel as c, dreameConfigSchema as d, toNodreameOptions as f, deviceTypeForKind as i, buildConnectionFormSchema as l, classifyDreameModel as n, suctionLevelToken as o, controlCapForKind as r, supportedFanSpeedTokens as s, mapMowerStatusToActivity as t, connectionFromSettings as u };
|