@camstack/addon-matter-broker 0.2.150 → 0.2.152
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 +8 -1
- package/README.md +27 -8
- package/dist/addon.js +1529 -290
- package/dist/addon.mjs +1529 -290
- package/package.json +1 -1
package/dist/addon.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { networkInterfaces, tmpdir, uptime } from "node:os";
|
|
|
11
11
|
import { finished } from "node:stream/promises";
|
|
12
12
|
import { createConnection, createServer as createServer$2 } from "node:net";
|
|
13
13
|
import * as dgram from "node:dgram";
|
|
14
|
-
//#region ../types/dist/event-category-
|
|
14
|
+
//#region ../types/dist/event-category-DmZDPDf_.mjs
|
|
15
15
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
16
16
|
EventCategory["SystemBoot"] = "system.boot";
|
|
17
17
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -638,7 +638,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
638
638
|
* (a track was promoted to a stationary-registry entry) or departs (the
|
|
639
639
|
* object moved / was removed). Telemetry (D8): lossy, drives a UI refresh of
|
|
640
640
|
* the dedicated "Stationary" section — never the live event feed. Payload:
|
|
641
|
-
* `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp }`.
|
|
641
|
+
* `{ deviceId, entryId, className, phase:'appeared'|'departed', timestamp, observedAt, zones }`.
|
|
642
642
|
*/
|
|
643
643
|
EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
|
|
644
644
|
/**
|
|
@@ -776,6 +776,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
776
776
|
*/
|
|
777
777
|
EventCategory["AccessoriesChanged"] = "accessories.onAccessoriesChanged";
|
|
778
778
|
/**
|
|
779
|
+
* An action run started or ended. Emitted by the `actions` surface.
|
|
780
|
+
* Payload: `ActionRunChanged` (`{ actionId, runId, isRunning, outcome?, lastRunAt, lastError }`).
|
|
781
|
+
*/
|
|
782
|
+
EventCategory["ActionsRunChanged"] = "actions.onRunChanged";
|
|
783
|
+
/**
|
|
779
784
|
* Progress update from a running model conversion job.
|
|
780
785
|
* Payload: `{ kind: 'model-convert', phase, sessionId?, pct?, detail? }`.
|
|
781
786
|
* Emitted by `addon-model-studio` on the converting node.
|
|
@@ -5587,7 +5592,7 @@ var ZodIssueCode = {
|
|
|
5587
5592
|
var ZodFirstPartyTypeKind;
|
|
5588
5593
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5589
5594
|
//#endregion
|
|
5590
|
-
//#region ../types/dist/sleep-
|
|
5595
|
+
//#region ../types/dist/sleep-Crlmu_tO.mjs
|
|
5591
5596
|
/**
|
|
5592
5597
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5593
5598
|
* window to float samples (D455).
|
|
@@ -8895,6 +8900,8 @@ Object.freeze({ "doorbell": "lastPressedAt" });
|
|
|
8895
8900
|
/** Every capability name, so addon code can check a name without the schema graph. */
|
|
8896
8901
|
var CAP_NAMES = Object.freeze([
|
|
8897
8902
|
"accessories",
|
|
8903
|
+
"action-source",
|
|
8904
|
+
"actions",
|
|
8898
8905
|
"addon-pages",
|
|
8899
8906
|
"addon-pages-source",
|
|
8900
8907
|
"addon-routes",
|
|
@@ -8968,6 +8975,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
8968
8975
|
"filesystem-browse",
|
|
8969
8976
|
"flood",
|
|
8970
8977
|
"gas",
|
|
8978
|
+
"group-active",
|
|
8971
8979
|
"group-commands",
|
|
8972
8980
|
"group-status",
|
|
8973
8981
|
"heating-valve",
|
|
@@ -8990,6 +8998,7 @@ var CAP_NAMES = Object.freeze([
|
|
|
8990
8998
|
"login-method",
|
|
8991
8999
|
"media-player",
|
|
8992
9000
|
"mesh-network",
|
|
9001
|
+
"meter",
|
|
8993
9002
|
"metrics-provider",
|
|
8994
9003
|
"model-convert",
|
|
8995
9004
|
"model-distributor",
|
|
@@ -9081,11 +9090,11 @@ var CAP_NAMES = Object.freeze([
|
|
|
9081
9090
|
Object.freeze({
|
|
9082
9091
|
"air-quality-sensor": [
|
|
9083
9092
|
{
|
|
9084
|
-
"path": "
|
|
9093
|
+
"path": "co2",
|
|
9085
9094
|
"valueType": "number"
|
|
9086
9095
|
},
|
|
9087
9096
|
{
|
|
9088
|
-
"path": "
|
|
9097
|
+
"path": "voc",
|
|
9089
9098
|
"valueType": "number"
|
|
9090
9099
|
},
|
|
9091
9100
|
{
|
|
@@ -9135,7 +9144,7 @@ Object.freeze({
|
|
|
9135
9144
|
}
|
|
9136
9145
|
],
|
|
9137
9146
|
"ambient-light-sensor": [{
|
|
9138
|
-
"path": "
|
|
9147
|
+
"path": "illuminance",
|
|
9139
9148
|
"valueType": "number"
|
|
9140
9149
|
}],
|
|
9141
9150
|
"audio-metrics": [
|
|
@@ -9196,7 +9205,7 @@ Object.freeze({
|
|
|
9196
9205
|
],
|
|
9197
9206
|
"battery": [
|
|
9198
9207
|
{
|
|
9199
|
-
"path": "
|
|
9208
|
+
"path": "level",
|
|
9200
9209
|
"valueType": "number"
|
|
9201
9210
|
},
|
|
9202
9211
|
{
|
|
@@ -9218,7 +9227,7 @@ Object.freeze({
|
|
|
9218
9227
|
"valueType": "number"
|
|
9219
9228
|
},
|
|
9220
9229
|
{
|
|
9221
|
-
"path": "
|
|
9230
|
+
"path": "voltage",
|
|
9222
9231
|
"valueType": "number"
|
|
9223
9232
|
},
|
|
9224
9233
|
{
|
|
@@ -9239,7 +9248,7 @@ Object.freeze({
|
|
|
9239
9248
|
"valueType": "boolean"
|
|
9240
9249
|
}],
|
|
9241
9250
|
"brightness": [{
|
|
9242
|
-
"path": "
|
|
9251
|
+
"path": "level",
|
|
9243
9252
|
"valueType": "number"
|
|
9244
9253
|
}],
|
|
9245
9254
|
"broker": [{
|
|
@@ -9440,7 +9449,7 @@ Object.freeze({
|
|
|
9440
9449
|
"valueType": "number"
|
|
9441
9450
|
},
|
|
9442
9451
|
{
|
|
9443
|
-
"path": "
|
|
9452
|
+
"path": "colorTemperature",
|
|
9444
9453
|
"valueType": "number"
|
|
9445
9454
|
}
|
|
9446
9455
|
],
|
|
@@ -9619,15 +9628,15 @@ Object.freeze({
|
|
|
9619
9628
|
"valueType": "string"
|
|
9620
9629
|
},
|
|
9621
9630
|
{
|
|
9622
|
-
"path": "
|
|
9631
|
+
"path": "remaining",
|
|
9623
9632
|
"valueType": "number"
|
|
9624
9633
|
},
|
|
9625
9634
|
{
|
|
9626
|
-
"path": "
|
|
9635
|
+
"path": "delayedStart",
|
|
9627
9636
|
"valueType": "number"
|
|
9628
9637
|
},
|
|
9629
9638
|
{
|
|
9630
|
-
"path": "
|
|
9639
|
+
"path": "progress",
|
|
9631
9640
|
"valueType": "number"
|
|
9632
9641
|
},
|
|
9633
9642
|
{
|
|
@@ -9741,7 +9750,7 @@ Object.freeze({
|
|
|
9741
9750
|
],
|
|
9742
9751
|
"fan-control": [
|
|
9743
9752
|
{
|
|
9744
|
-
"path": "
|
|
9753
|
+
"path": "speed",
|
|
9745
9754
|
"valueType": "number"
|
|
9746
9755
|
},
|
|
9747
9756
|
{
|
|
@@ -9788,6 +9797,10 @@ Object.freeze({
|
|
|
9788
9797
|
"path": "detected",
|
|
9789
9798
|
"valueType": "boolean"
|
|
9790
9799
|
}],
|
|
9800
|
+
"group-active": [{
|
|
9801
|
+
"path": "active",
|
|
9802
|
+
"valueType": "boolean"
|
|
9803
|
+
}],
|
|
9791
9804
|
"group-status": [
|
|
9792
9805
|
{
|
|
9793
9806
|
"path": "included",
|
|
@@ -9837,7 +9850,7 @@ Object.freeze({
|
|
|
9837
9850
|
}
|
|
9838
9851
|
],
|
|
9839
9852
|
"humidity-sensor": [{
|
|
9840
|
-
"path": "
|
|
9853
|
+
"path": "humidity",
|
|
9841
9854
|
"valueType": "number"
|
|
9842
9855
|
}],
|
|
9843
9856
|
"image": [{
|
|
@@ -9947,7 +9960,7 @@ Object.freeze({
|
|
|
9947
9960
|
"valueType": "number"
|
|
9948
9961
|
},
|
|
9949
9962
|
{
|
|
9950
|
-
"path": "
|
|
9963
|
+
"path": "progress",
|
|
9951
9964
|
"valueType": "number"
|
|
9952
9965
|
},
|
|
9953
9966
|
{
|
|
@@ -10026,11 +10039,11 @@ Object.freeze({
|
|
|
10026
10039
|
"valueType": "string"
|
|
10027
10040
|
},
|
|
10028
10041
|
{
|
|
10029
|
-
"path": "
|
|
10042
|
+
"path": "position",
|
|
10030
10043
|
"valueType": "number"
|
|
10031
10044
|
},
|
|
10032
10045
|
{
|
|
10033
|
-
"path": "
|
|
10046
|
+
"path": "duration",
|
|
10034
10047
|
"valueType": "number"
|
|
10035
10048
|
},
|
|
10036
10049
|
{
|
|
@@ -10047,6 +10060,10 @@ Object.freeze({
|
|
|
10047
10060
|
]
|
|
10048
10061
|
}
|
|
10049
10062
|
],
|
|
10063
|
+
"meter": [{
|
|
10064
|
+
"path": "value",
|
|
10065
|
+
"valueType": "number"
|
|
10066
|
+
}],
|
|
10050
10067
|
"motion": [
|
|
10051
10068
|
{
|
|
10052
10069
|
"path": "detected",
|
|
@@ -10128,11 +10145,11 @@ Object.freeze({
|
|
|
10128
10145
|
]
|
|
10129
10146
|
},
|
|
10130
10147
|
{
|
|
10131
|
-
"path": "
|
|
10148
|
+
"path": "signal",
|
|
10132
10149
|
"valueType": "number"
|
|
10133
10150
|
},
|
|
10134
10151
|
{
|
|
10135
|
-
"path": "
|
|
10152
|
+
"path": "rssi",
|
|
10136
10153
|
"valueType": "number"
|
|
10137
10154
|
},
|
|
10138
10155
|
{
|
|
@@ -10187,6 +10204,18 @@ Object.freeze({
|
|
|
10187
10204
|
"path": "foodLevel",
|
|
10188
10205
|
"valueType": "number"
|
|
10189
10206
|
},
|
|
10207
|
+
{
|
|
10208
|
+
"path": "bowlFood",
|
|
10209
|
+
"valueType": "number"
|
|
10210
|
+
},
|
|
10211
|
+
{
|
|
10212
|
+
"path": "eating",
|
|
10213
|
+
"valueType": "boolean"
|
|
10214
|
+
},
|
|
10215
|
+
{
|
|
10216
|
+
"path": "supportsMarkRefilled",
|
|
10217
|
+
"valueType": "boolean"
|
|
10218
|
+
},
|
|
10190
10219
|
{
|
|
10191
10220
|
"path": "food1",
|
|
10192
10221
|
"valueType": "number"
|
|
@@ -10251,23 +10280,23 @@ Object.freeze({
|
|
|
10251
10280
|
],
|
|
10252
10281
|
"power-meter": [
|
|
10253
10282
|
{
|
|
10254
|
-
"path": "
|
|
10283
|
+
"path": "power",
|
|
10255
10284
|
"valueType": "number"
|
|
10256
10285
|
},
|
|
10257
10286
|
{
|
|
10258
|
-
"path": "
|
|
10287
|
+
"path": "energy",
|
|
10259
10288
|
"valueType": "number"
|
|
10260
10289
|
},
|
|
10261
10290
|
{
|
|
10262
|
-
"path": "
|
|
10291
|
+
"path": "voltage",
|
|
10263
10292
|
"valueType": "number"
|
|
10264
10293
|
},
|
|
10265
10294
|
{
|
|
10266
|
-
"path": "
|
|
10295
|
+
"path": "current",
|
|
10267
10296
|
"valueType": "number"
|
|
10268
10297
|
},
|
|
10269
10298
|
{
|
|
10270
|
-
"path": "
|
|
10299
|
+
"path": "apparentPower",
|
|
10271
10300
|
"valueType": "number"
|
|
10272
10301
|
},
|
|
10273
10302
|
{
|
|
@@ -10295,14 +10324,10 @@ Object.freeze({
|
|
|
10295
10324
|
{
|
|
10296
10325
|
"path": "gps.accuracyMeters",
|
|
10297
10326
|
"valueType": "number"
|
|
10298
|
-
},
|
|
10299
|
-
{
|
|
10300
|
-
"path": "batteryPercent",
|
|
10301
|
-
"valueType": "number"
|
|
10302
10327
|
}
|
|
10303
10328
|
],
|
|
10304
10329
|
"pressure-sensor": [{
|
|
10305
|
-
"path": "
|
|
10330
|
+
"path": "pressure",
|
|
10306
10331
|
"valueType": "number"
|
|
10307
10332
|
}],
|
|
10308
10333
|
"privacy-mask": [{
|
|
@@ -10616,7 +10641,7 @@ Object.freeze({
|
|
|
10616
10641
|
"valueType": "boolean"
|
|
10617
10642
|
}],
|
|
10618
10643
|
"temperature-sensor": [{
|
|
10619
|
-
"path": "
|
|
10644
|
+
"path": "temperature",
|
|
10620
10645
|
"valueType": "number"
|
|
10621
10646
|
}],
|
|
10622
10647
|
"update": [
|
|
@@ -10664,7 +10689,7 @@ Object.freeze({
|
|
|
10664
10689
|
"valueType": "string"
|
|
10665
10690
|
},
|
|
10666
10691
|
{
|
|
10667
|
-
"path": "
|
|
10692
|
+
"path": "progress",
|
|
10668
10693
|
"valueType": "number"
|
|
10669
10694
|
},
|
|
10670
10695
|
{
|
|
@@ -10790,7 +10815,7 @@ object({
|
|
|
10790
10815
|
params: record(string$2(), union([string$2(), number()])).optional()
|
|
10791
10816
|
});
|
|
10792
10817
|
//#endregion
|
|
10793
|
-
//#region ../types/dist/err-msg-
|
|
10818
|
+
//#region ../types/dist/err-msg-DIorGYeC.mjs
|
|
10794
10819
|
var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
10795
10820
|
DeviceType["Camera"] = "camera";
|
|
10796
10821
|
DeviceType["Hub"] = "hub";
|
|
@@ -10884,8 +10909,14 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
|
|
|
10884
10909
|
DeviceType["PetFeeder"] = "pet-feeder";
|
|
10885
10910
|
/** Built-in dishwasher — program lifecycle, door / remote-start gates,
|
|
10886
10911
|
* salt and rinse-aid levels. Installed with the `dishwasher` cap.
|
|
10887
|
-
* Sources: native Bosch Home Connect, reusable by similar integrations.
|
|
10912
|
+
* Sources: native Bosch Home Connect, reusable by similar integrations.
|
|
10913
|
+
* A plug-built dishwasher carries running-state and power instead. */
|
|
10888
10914
|
DeviceType["Dishwasher"] = "dishwasher";
|
|
10915
|
+
/** Washing machine. A plug-built one is running while its power stays
|
|
10916
|
+
* above a threshold. No program cap — that surface is the dishwasher's. */
|
|
10917
|
+
DeviceType["Washer"] = "washer";
|
|
10918
|
+
/** Tumble dryer. Same plug-built running surface as {@link Washer}. */
|
|
10919
|
+
DeviceType["Dryer"] = "dryer";
|
|
10889
10920
|
return DeviceType;
|
|
10890
10921
|
}({});
|
|
10891
10922
|
var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
|
|
@@ -11143,7 +11174,7 @@ function errMsg(err) {
|
|
|
11143
11174
|
return String(err);
|
|
11144
11175
|
}
|
|
11145
11176
|
//#endregion
|
|
11146
|
-
//#region ../types/dist/composition-
|
|
11177
|
+
//#region ../types/dist/composition-CZImLpOI.mjs
|
|
11147
11178
|
/**
|
|
11148
11179
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11149
11180
|
*
|
|
@@ -11458,6 +11489,13 @@ var table = {
|
|
|
11458
11489
|
apply: () => {
|
|
11459
11490
|
throw new ExpressionEvalError("latchedAt: stateful evaluation context is required");
|
|
11460
11491
|
}
|
|
11492
|
+
},
|
|
11493
|
+
schmitt: {
|
|
11494
|
+
minArgs: 3,
|
|
11495
|
+
maxArgs: 5,
|
|
11496
|
+
apply: () => {
|
|
11497
|
+
throw new ExpressionEvalError("schmitt: stateful evaluation context is required");
|
|
11498
|
+
}
|
|
11461
11499
|
}
|
|
11462
11500
|
};
|
|
11463
11501
|
Object.freeze(Object.assign(Object.create(null), table));
|
|
@@ -11899,6 +11937,99 @@ function validateExpressionSource(src) {
|
|
|
11899
11937
|
return null;
|
|
11900
11938
|
}
|
|
11901
11939
|
/**
|
|
11940
|
+
* One power reading becomes a washer, dryer or dishwasher (D944 helper).
|
|
11941
|
+
*
|
|
11942
|
+
* Standby of the control board is under 5 W; a motor or a heater is far
|
|
11943
|
+
* above that, so 5 W is the line. A door lock or a short pump spike is not
|
|
11944
|
+
* a cycle, so running starts only after 30 s above the line.
|
|
11945
|
+
*
|
|
11946
|
+
* A wash, dry or dishwasher pause (soak, heat-wait, heater off between
|
|
11947
|
+
* bursts) lasts several minutes. Ending the cycle on the first dip would
|
|
11948
|
+
* report "finished" in the middle of the program, so the stop dwell is
|
|
11949
|
+
* 10 minutes on every type. The number is stored per type so one machine
|
|
11950
|
+
* can move later without moving the others.
|
|
11951
|
+
*/
|
|
11952
|
+
var APPLIANCE_TEMPLATE_ID = "appliance";
|
|
11953
|
+
var APPLIANCE_KINDS = [
|
|
11954
|
+
"washer",
|
|
11955
|
+
"dryer",
|
|
11956
|
+
"dishwasher"
|
|
11957
|
+
];
|
|
11958
|
+
DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
|
|
11959
|
+
/**
|
|
11960
|
+
* Statistics and integrals over a history read (D944).
|
|
11961
|
+
*
|
|
11962
|
+
* Pure: the same points always give the same answer, so a restart recomputes
|
|
11963
|
+
* rather than trusting a buffer. A gauge mean is time-weighted (value × ms),
|
|
11964
|
+
* never the mean of the samples. A hole with no held value is not integrated
|
|
11965
|
+
* as zero. A truncated read is not published.
|
|
11966
|
+
*/
|
|
11967
|
+
var STATISTIC_STATS = [
|
|
11968
|
+
"min",
|
|
11969
|
+
"max",
|
|
11970
|
+
"mean",
|
|
11971
|
+
"sum",
|
|
11972
|
+
"count",
|
|
11973
|
+
"change"
|
|
11974
|
+
];
|
|
11975
|
+
/**
|
|
11976
|
+
* The range a statistics or integral helper asks history for (D944).
|
|
11977
|
+
*
|
|
11978
|
+
* Calendar edges are the hub's timezone, never UTC midnight: a day is 23, 24
|
|
11979
|
+
* or 25 hours across a DST change. A tier is eligible only when its retention
|
|
11980
|
+
* is strictly longer than the longest that range can be. Forever qualifies.
|
|
11981
|
+
* Equal to a tier's retention is a refusal, not a short answer.
|
|
11982
|
+
*/
|
|
11983
|
+
var MINUTE_MS = 6e4;
|
|
11984
|
+
var HOUR_MS = 60 * MINUTE_MS;
|
|
11985
|
+
var DAY_MS = 24 * HOUR_MS;
|
|
11986
|
+
var ROLLING_SPANS = [
|
|
11987
|
+
"1h",
|
|
11988
|
+
"24h",
|
|
11989
|
+
"2d",
|
|
11990
|
+
"7d",
|
|
11991
|
+
"30d",
|
|
11992
|
+
"custom"
|
|
11993
|
+
];
|
|
11994
|
+
var CALENDAR_PERIODS = [
|
|
11995
|
+
"today",
|
|
11996
|
+
"yesterday",
|
|
11997
|
+
"this-week",
|
|
11998
|
+
"this-month"
|
|
11999
|
+
];
|
|
12000
|
+
var RESET_PERIODS = [
|
|
12001
|
+
"never",
|
|
12002
|
+
"daily",
|
|
12003
|
+
"weekly",
|
|
12004
|
+
"monthly"
|
|
12005
|
+
];
|
|
12006
|
+
5 * MINUTE_MS;
|
|
12007
|
+
24 * HOUR_MS, 2 * DAY_MS, 7 * DAY_MS, 30 * DAY_MS;
|
|
12008
|
+
7 * DAY_MS, 30 * DAY_MS, 365 * DAY_MS;
|
|
12009
|
+
var rollingWindowObject = object({
|
|
12010
|
+
kind: literal("rolling"),
|
|
12011
|
+
span: _enum(ROLLING_SPANS),
|
|
12012
|
+
customMinutes: number().int().min(5).optional()
|
|
12013
|
+
}).check((payload) => {
|
|
12014
|
+
if (payload.value.span === "custom" && payload.value.customMinutes === void 0) payload.issues.push({
|
|
12015
|
+
code: "custom",
|
|
12016
|
+
message: "a custom range needs a duration in minutes",
|
|
12017
|
+
path: ["customMinutes"],
|
|
12018
|
+
input: payload.value
|
|
12019
|
+
});
|
|
12020
|
+
if (payload.value.span !== "custom" && payload.value.customMinutes !== void 0) payload.issues.push({
|
|
12021
|
+
code: "custom",
|
|
12022
|
+
message: "only a custom range carries a duration",
|
|
12023
|
+
path: ["customMinutes"],
|
|
12024
|
+
input: payload.value
|
|
12025
|
+
});
|
|
12026
|
+
});
|
|
12027
|
+
var RollingWindowSchema = rollingWindowObject;
|
|
12028
|
+
var StatisticWindowSchema = discriminatedUnion("kind", [rollingWindowObject, object({
|
|
12029
|
+
kind: literal("calendar"),
|
|
12030
|
+
period: _enum(CALENDAR_PERIODS)
|
|
12031
|
+
})]);
|
|
12032
|
+
/**
|
|
11902
12033
|
* A composition source identity: `(addonId, stableId)`, never a numeric device
|
|
11903
12034
|
* id. Split out so helper schemas can name it without a cycle through
|
|
11904
12035
|
* `composition.ts` (D659).
|
|
@@ -11924,7 +12055,13 @@ var GROUP_TEMPLATE_IDS = [
|
|
|
11924
12055
|
"occupancy",
|
|
11925
12056
|
"binary",
|
|
11926
12057
|
"switch",
|
|
11927
|
-
"light"
|
|
12058
|
+
"light",
|
|
12059
|
+
"fan",
|
|
12060
|
+
"cover",
|
|
12061
|
+
"valve",
|
|
12062
|
+
"lock",
|
|
12063
|
+
"media-player",
|
|
12064
|
+
"climate"
|
|
11928
12065
|
];
|
|
11929
12066
|
var NUMERIC_AGGREGATES = [
|
|
11930
12067
|
"mean",
|
|
@@ -11954,14 +12091,73 @@ var HelperMemberSchema = object({
|
|
|
11954
12091
|
cap: string$2().min(1),
|
|
11955
12092
|
fieldPath: string$2().min(1)
|
|
11956
12093
|
});
|
|
11957
|
-
var HelperSpecSchema = discriminatedUnion("kind", [
|
|
11958
|
-
|
|
11959
|
-
|
|
11960
|
-
|
|
11961
|
-
|
|
11962
|
-
|
|
11963
|
-
|
|
11964
|
-
|
|
12094
|
+
var HelperSpecSchema = discriminatedUnion("kind", [
|
|
12095
|
+
object({
|
|
12096
|
+
kind: literal("group"),
|
|
12097
|
+
templateId: GroupTemplateIdSchema,
|
|
12098
|
+
aggregate: AggregateReducerSchema,
|
|
12099
|
+
members: array(HelperMemberSchema).min(1).max(64),
|
|
12100
|
+
/** Actuator groups only. Readings leave this unset. */
|
|
12101
|
+
active: ActiveCriterionSchema.optional()
|
|
12102
|
+
}),
|
|
12103
|
+
object({
|
|
12104
|
+
kind: literal("appliance"),
|
|
12105
|
+
templateId: literal(APPLIANCE_TEMPLATE_ID),
|
|
12106
|
+
appliance: _enum(APPLIANCE_KINDS),
|
|
12107
|
+
thresholdWatts: number().positive(),
|
|
12108
|
+
startDwellMs: number().int().nonnegative(),
|
|
12109
|
+
stopDwellMs: number().int().nonnegative(),
|
|
12110
|
+
member: HelperMemberSchema
|
|
12111
|
+
}),
|
|
12112
|
+
object({
|
|
12113
|
+
kind: literal("statistic"),
|
|
12114
|
+
stat: _enum(STATISTIC_STATS),
|
|
12115
|
+
window: StatisticWindowSchema,
|
|
12116
|
+
source: HelperMemberSchema
|
|
12117
|
+
}),
|
|
12118
|
+
object({
|
|
12119
|
+
kind: literal("integral"),
|
|
12120
|
+
reset: _enum(RESET_PERIODS),
|
|
12121
|
+
output: _enum(["energy", "meter"]),
|
|
12122
|
+
source: HelperMemberSchema
|
|
12123
|
+
}),
|
|
12124
|
+
object({
|
|
12125
|
+
kind: literal("threshold"),
|
|
12126
|
+
source: HelperMemberSchema,
|
|
12127
|
+
high: number(),
|
|
12128
|
+
low: number(),
|
|
12129
|
+
onForMs: number().int().nonnegative(),
|
|
12130
|
+
offForMs: number().int().nonnegative()
|
|
12131
|
+
}),
|
|
12132
|
+
object({
|
|
12133
|
+
kind: literal("arithmetic"),
|
|
12134
|
+
op: _enum([
|
|
12135
|
+
"sum",
|
|
12136
|
+
"difference",
|
|
12137
|
+
"ratio"
|
|
12138
|
+
]),
|
|
12139
|
+
inputs: array(HelperMemberSchema).min(2).max(64)
|
|
12140
|
+
}),
|
|
12141
|
+
object({
|
|
12142
|
+
kind: literal("expression"),
|
|
12143
|
+
expr: string$2().min(1),
|
|
12144
|
+
inputs: array(object({
|
|
12145
|
+
name: string$2().min(1),
|
|
12146
|
+
member: HelperMemberSchema
|
|
12147
|
+
})).min(1).max(64),
|
|
12148
|
+
output: _enum(["number", "boolean"])
|
|
12149
|
+
}),
|
|
12150
|
+
object({
|
|
12151
|
+
kind: literal("derivative"),
|
|
12152
|
+
window: RollingWindowSchema,
|
|
12153
|
+
per: _enum([
|
|
12154
|
+
"second",
|
|
12155
|
+
"minute",
|
|
12156
|
+
"hour"
|
|
12157
|
+
]),
|
|
12158
|
+
source: HelperMemberSchema
|
|
12159
|
+
})
|
|
12160
|
+
]);
|
|
11965
12161
|
/** A composed device's stableId is this prefix plus the block id. */
|
|
11966
12162
|
var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
|
|
11967
12163
|
/** Longest snippet a `code` source may carry (slice 4). */
|
|
@@ -12022,6 +12218,12 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
|
|
|
12022
12218
|
"all"
|
|
12023
12219
|
]),
|
|
12024
12220
|
reads: array(CompositionFieldReadSchema).min(1).max(64),
|
|
12221
|
+
/**
|
|
12222
|
+
* Set when the reads are an enum state, not a yes/no: the listed states
|
|
12223
|
+
* count as active (a cover is `open`). A state outside the list is inactive;
|
|
12224
|
+
* a read that is not a state at all is left out. Only `any` / `all` use it.
|
|
12225
|
+
*/
|
|
12226
|
+
activeWhen: array(string$2().min(1)).min(1).optional(),
|
|
12025
12227
|
/** `value` is the reading; the others are the member counts. */
|
|
12026
12228
|
publish: _enum([
|
|
12027
12229
|
"value",
|
|
@@ -12035,6 +12237,26 @@ var CompositionFieldSourceSchema = discriminatedUnion("kind", [
|
|
|
12035
12237
|
"omit",
|
|
12036
12238
|
"offline"
|
|
12037
12239
|
])
|
|
12240
|
+
}),
|
|
12241
|
+
object({
|
|
12242
|
+
kind: literal("series"),
|
|
12243
|
+
read: CompositionFieldReadSchema,
|
|
12244
|
+
/** `value` is the statistic or the integral. The others are the gap counts. */
|
|
12245
|
+
publish: _enum([
|
|
12246
|
+
"value",
|
|
12247
|
+
"included",
|
|
12248
|
+
"excluded",
|
|
12249
|
+
"total"
|
|
12250
|
+
]),
|
|
12251
|
+
/**
|
|
12252
|
+
* What an unanswered `value` does. Counts ignore this. `offline` leaves the
|
|
12253
|
+
* device unseeded until history answers, so a restart cannot publish 0.
|
|
12254
|
+
*/
|
|
12255
|
+
empty: _enum([
|
|
12256
|
+
"null",
|
|
12257
|
+
"omit",
|
|
12258
|
+
"offline"
|
|
12259
|
+
])
|
|
12038
12260
|
})
|
|
12039
12261
|
]);
|
|
12040
12262
|
var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
|
|
@@ -15472,6 +15694,14 @@ var AutomationConditionLeafSchema = object({
|
|
|
15472
15694
|
array(union([string$2(), number()]))
|
|
15473
15695
|
])
|
|
15474
15696
|
});
|
|
15697
|
+
var AutomationConditionStationaryPresentSchema = object({
|
|
15698
|
+
kind: literal("stationary-present"),
|
|
15699
|
+
deviceId: number().int().nonnegative(),
|
|
15700
|
+
/** Class names; a macro class matches every raw class that maps to it. */
|
|
15701
|
+
classes: array(string$2().min(1)).min(1).optional(),
|
|
15702
|
+
/** The parked object must be in at least one of these zones. */
|
|
15703
|
+
zones: array(string$2().min(1)).min(1).optional()
|
|
15704
|
+
});
|
|
15475
15705
|
/**
|
|
15476
15706
|
* The expression leaf, declared as a plain object rather than an intersection
|
|
15477
15707
|
* with {@link ExpressionSourceSchema}: a discriminated union has to be able to
|
|
@@ -15505,7 +15735,8 @@ var AutomationConditionSchema = lazy(() => discriminatedUnion("kind", [
|
|
|
15505
15735
|
child: AutomationConditionSchema
|
|
15506
15736
|
}),
|
|
15507
15737
|
AutomationConditionLeafSchema,
|
|
15508
|
-
AutomationConditionExpressionSchema
|
|
15738
|
+
AutomationConditionExpressionSchema,
|
|
15739
|
+
AutomationConditionStationaryPresentSchema
|
|
15509
15740
|
]));
|
|
15510
15741
|
/**
|
|
15511
15742
|
* What starts a run.
|
|
@@ -15546,7 +15777,34 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
|
|
|
15546
15777
|
kind: literal("schedule"),
|
|
15547
15778
|
cron: string$2().min(1).max(120)
|
|
15548
15779
|
}),
|
|
15549
|
-
object({ kind: literal("manual") })
|
|
15780
|
+
object({ kind: literal("manual") }),
|
|
15781
|
+
object({
|
|
15782
|
+
kind: literal("detection"),
|
|
15783
|
+
/** Class names. A macro class (`animal`, `vehicle`, `person`, `package`)
|
|
15784
|
+
* matches every raw class that maps to it; any other name matches
|
|
15785
|
+
* exactly. */
|
|
15786
|
+
classes: array(string$2().min(1)).min(1),
|
|
15787
|
+
deviceIds: array(number().int().nonnegative()).min(1),
|
|
15788
|
+
/** The track must have visited at least one of these zones. */
|
|
15789
|
+
zones: array(string$2().min(1)).min(1).optional(),
|
|
15790
|
+
/** Best-so-far confidence the track must reach. */
|
|
15791
|
+
minScore: number().min(0).max(1).optional()
|
|
15792
|
+
}),
|
|
15793
|
+
object({
|
|
15794
|
+
kind: literal("stationary"),
|
|
15795
|
+
deviceIds: array(number().int().nonnegative()).min(1),
|
|
15796
|
+
/** Class names; a macro class matches like the detection trigger. */
|
|
15797
|
+
classes: array(string$2().min(1)).min(1).optional(),
|
|
15798
|
+
/** The parked object must be in at least one of these zones. */
|
|
15799
|
+
zones: array(string$2().min(1)).min(1).optional(),
|
|
15800
|
+
/** `appeared`: it became parked. `departed`: the registry retired it.
|
|
15801
|
+
* The vocabulary of `PipelineAnalyticsStationaryChanged`. */
|
|
15802
|
+
phase: _enum(["appeared", "departed"])
|
|
15803
|
+
}),
|
|
15804
|
+
object({
|
|
15805
|
+
kind: literal("interval"),
|
|
15806
|
+
minutes: number().int().min(1).max(1440)
|
|
15807
|
+
})
|
|
15550
15808
|
]);
|
|
15551
15809
|
/**
|
|
15552
15810
|
* One action step.
|
|
@@ -15566,28 +15824,47 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
|
|
|
15566
15824
|
* is called. The runner still receives a number and is untouched; the
|
|
15567
15825
|
* resolution is the recipe's job, not the runner's.
|
|
15568
15826
|
*/
|
|
15827
|
+
var AutomationWaitStepSchema = object({
|
|
15828
|
+
kind: literal("wait"),
|
|
15829
|
+
seconds: number().min(0).max(300)
|
|
15830
|
+
});
|
|
15831
|
+
var AutomationCapStepSchema = object({
|
|
15832
|
+
kind: literal("cap"),
|
|
15833
|
+
/**
|
|
15834
|
+
* One device, several same-family devices (locks / climate family / …),
|
|
15835
|
+
* or `{ $var }` for the device that triggered the run. An array is
|
|
15836
|
+
* fan-out at codegen — each id gets the same cap.method call.
|
|
15837
|
+
*/
|
|
15838
|
+
deviceId: union([
|
|
15839
|
+
number().int(),
|
|
15840
|
+
array(number().int()).min(1),
|
|
15841
|
+
object({ $var: string$2().min(1) })
|
|
15842
|
+
]),
|
|
15843
|
+
cap: string$2().min(1),
|
|
15844
|
+
method: string$2().min(1),
|
|
15845
|
+
/** Values may carry `{{vars.x}}` slots, or be exactly `{ $var: 'name' }`, which
|
|
15846
|
+
* SUBSTITUTE and do not evaluate (§3.2.3). Anything beyond substitution is
|
|
15847
|
+
* the expression leaf. */
|
|
15848
|
+
args: record(string$2(), unknown()).optional()
|
|
15849
|
+
});
|
|
15850
|
+
/**
|
|
15851
|
+
* The steps an internal Action may contain: cap and wait, and nothing that can
|
|
15852
|
+
* call back into an Action — so an Action can never reach itself (no cycles).
|
|
15853
|
+
*/
|
|
15854
|
+
var ActionStepSchema = discriminatedUnion("kind", [AutomationWaitStepSchema, AutomationCapStepSchema]);
|
|
15569
15855
|
var AutomationActionSchema = discriminatedUnion("kind", [
|
|
15856
|
+
AutomationWaitStepSchema,
|
|
15857
|
+
AutomationCapStepSchema,
|
|
15570
15858
|
object({
|
|
15571
|
-
kind: literal("
|
|
15572
|
-
|
|
15573
|
-
|
|
15574
|
-
|
|
15575
|
-
|
|
15576
|
-
|
|
15577
|
-
|
|
15578
|
-
|
|
15579
|
-
|
|
15580
|
-
*/
|
|
15581
|
-
deviceId: union([
|
|
15582
|
-
number().int(),
|
|
15583
|
-
array(number().int()).min(1),
|
|
15584
|
-
object({ $var: string$2().min(1) })
|
|
15585
|
-
]),
|
|
15586
|
-
cap: string$2().min(1),
|
|
15587
|
-
method: string$2().min(1),
|
|
15588
|
-
/** Values may carry `{{vars.x}}` slots, which SUBSTITUTE and do not
|
|
15589
|
-
* evaluate (§3.2.3). Anything beyond substitution is the expression leaf. */
|
|
15590
|
-
args: record(string$2(), unknown()).optional()
|
|
15859
|
+
kind: literal("action"),
|
|
15860
|
+
/** An entry of the `actions` surface (`internal:<uuid>` or `<addonId>:<localId>`). */
|
|
15861
|
+
actionId: string$2().min(1),
|
|
15862
|
+
/** Values for the action's declared variables; the others take their default. */
|
|
15863
|
+
variables: record(string$2(), union([
|
|
15864
|
+
string$2(),
|
|
15865
|
+
number(),
|
|
15866
|
+
boolean()
|
|
15867
|
+
])).optional()
|
|
15591
15868
|
}),
|
|
15592
15869
|
object({
|
|
15593
15870
|
kind: literal("code"),
|
|
@@ -15596,10 +15873,357 @@ var AutomationActionSchema = discriminatedUnion("kind", [
|
|
|
15596
15873
|
code: string$2().min(1).max(2e4)
|
|
15597
15874
|
})
|
|
15598
15875
|
]);
|
|
15876
|
+
/**
|
|
15877
|
+
* How a trigger that arrives while the automation is busy (or cooling down) is
|
|
15878
|
+
* handled. One of the automation's execution modes, never a property of its
|
|
15879
|
+
* actions. Absent means `single`.
|
|
15880
|
+
*
|
|
15881
|
+
* - `single` — drop the trigger while a run is in progress (traced + logged).
|
|
15882
|
+
* - `restart` — abort the run in progress (at its next step boundary), start fresh.
|
|
15883
|
+
* - `queued` — queue up to `max` triggers, drop the rest (traced).
|
|
15884
|
+
* - `parallel` — up to `max` runs at once, drop the rest (traced).
|
|
15885
|
+
* - `cooldown` — `single`, and no new run until `minutes` after the last run
|
|
15886
|
+
* STARTED; persisted across a restart. A manual trigger bypasses only this
|
|
15887
|
+
* wait, never the single/queued limits.
|
|
15888
|
+
*/
|
|
15889
|
+
var AutomationRunModeSchema = discriminatedUnion("mode", [
|
|
15890
|
+
object({ mode: literal("single") }),
|
|
15891
|
+
object({ mode: literal("restart") }),
|
|
15892
|
+
object({
|
|
15893
|
+
mode: literal("queued"),
|
|
15894
|
+
max: number().int().min(1).max(10)
|
|
15895
|
+
}),
|
|
15896
|
+
object({
|
|
15897
|
+
mode: literal("parallel"),
|
|
15898
|
+
max: number().int().min(1).max(10)
|
|
15899
|
+
}),
|
|
15900
|
+
object({
|
|
15901
|
+
mode: literal("cooldown"),
|
|
15902
|
+
minutes: number().int().min(1).max(1440)
|
|
15903
|
+
})
|
|
15904
|
+
]);
|
|
15599
15905
|
var AutomationRecipeSchema = object({
|
|
15600
15906
|
triggers: array(AutomationTriggerSchema),
|
|
15601
15907
|
conditions: AutomationConditionSchema.optional(),
|
|
15602
|
-
actions: array(AutomationActionSchema)
|
|
15908
|
+
actions: array(AutomationActionSchema),
|
|
15909
|
+
runMode: AutomationRunModeSchema.optional(),
|
|
15910
|
+
/** How many run traces the automation keeps (default {@link DEFAULT_TRACE_KEEP}). */
|
|
15911
|
+
keepTraces: number().int().min(1).max(100).optional()
|
|
15912
|
+
});
|
|
15913
|
+
new Set([
|
|
15914
|
+
{
|
|
15915
|
+
id: "person",
|
|
15916
|
+
name: "Person"
|
|
15917
|
+
},
|
|
15918
|
+
{
|
|
15919
|
+
id: "vehicle",
|
|
15920
|
+
name: "Vehicle"
|
|
15921
|
+
},
|
|
15922
|
+
{
|
|
15923
|
+
id: "animal",
|
|
15924
|
+
name: "Animal"
|
|
15925
|
+
},
|
|
15926
|
+
{
|
|
15927
|
+
id: "package",
|
|
15928
|
+
name: "Package"
|
|
15929
|
+
}
|
|
15930
|
+
].map((l) => l.id));
|
|
15931
|
+
var COCO_TO_MACRO = {
|
|
15932
|
+
mapping: {
|
|
15933
|
+
person: "person",
|
|
15934
|
+
bicycle: "vehicle",
|
|
15935
|
+
car: "vehicle",
|
|
15936
|
+
motorcycle: "vehicle",
|
|
15937
|
+
airplane: "vehicle",
|
|
15938
|
+
bus: "vehicle",
|
|
15939
|
+
train: "vehicle",
|
|
15940
|
+
truck: "vehicle",
|
|
15941
|
+
boat: "vehicle",
|
|
15942
|
+
bird: "animal",
|
|
15943
|
+
cat: "animal",
|
|
15944
|
+
dog: "animal",
|
|
15945
|
+
horse: "animal",
|
|
15946
|
+
sheep: "animal",
|
|
15947
|
+
cow: "animal",
|
|
15948
|
+
elephant: "animal",
|
|
15949
|
+
bear: "animal",
|
|
15950
|
+
zebra: "animal",
|
|
15951
|
+
giraffe: "animal",
|
|
15952
|
+
suitcase: "package",
|
|
15953
|
+
backpack: "package",
|
|
15954
|
+
handbag: "package"
|
|
15955
|
+
},
|
|
15956
|
+
preserveOriginal: false
|
|
15957
|
+
};
|
|
15958
|
+
new Set(Object.values(COCO_TO_MACRO.mapping));
|
|
15959
|
+
/**
|
|
15960
|
+
* Run traces — what an automation (or, later, an Action run) DID, node by node,
|
|
15961
|
+
* so the editor can paint every block green or red with its reason.
|
|
15962
|
+
*
|
|
15963
|
+
* A trace is data, not a log: it covers the WHOLE tree — the trigger stage,
|
|
15964
|
+
* every condition node (containers and leaves, including the ones a
|
|
15965
|
+
* short-circuit never reached), the run-mode verdict and every action step —
|
|
15966
|
+
* each addressed by a stable path (`conditions.0.1`, `actions.2`, or `steps.N`
|
|
15967
|
+
* for an Action run). Skipped
|
|
15968
|
+
* evaluations are traced too, and identical consecutive skips collapse into a
|
|
15969
|
+
* repeat count so a trigger that fails every 5 minutes cannot flush real runs.
|
|
15970
|
+
*
|
|
15971
|
+
* {@link AutomationStepTraceSchema} is deliberately free of automation
|
|
15972
|
+
* vocabulary so an Action run records the same step shape.
|
|
15973
|
+
*/
|
|
15974
|
+
/** A value a trace may show: JSON primitives and short lists, nothing deeper. */
|
|
15975
|
+
var TraceValueSchema = union([
|
|
15976
|
+
string$2(),
|
|
15977
|
+
number(),
|
|
15978
|
+
boolean(),
|
|
15979
|
+
_null(),
|
|
15980
|
+
array(union([string$2(), number()]))
|
|
15981
|
+
]);
|
|
15982
|
+
var AutomationRunOutcomeSchema = _enum([
|
|
15983
|
+
"ran",
|
|
15984
|
+
"skipped-trigger",
|
|
15985
|
+
"skipped-conditions",
|
|
15986
|
+
"skipped-cooldown",
|
|
15987
|
+
"skipped-overlap",
|
|
15988
|
+
"failed",
|
|
15989
|
+
"aborted"
|
|
15990
|
+
]);
|
|
15991
|
+
/** `not-evaluated` = a short-circuit never reached the node. */
|
|
15992
|
+
var TraceNodeOutcomeSchema = _enum([
|
|
15993
|
+
"pass",
|
|
15994
|
+
"fail",
|
|
15995
|
+
"not-evaluated"
|
|
15996
|
+
]);
|
|
15997
|
+
var AutomationTriggerTraceSchema = object({
|
|
15998
|
+
kind: string$2().min(1),
|
|
15999
|
+
outcome: _enum(["pass", "fail"]),
|
|
16000
|
+
/** Why an event was rejected ("class person not in [dog]") or what fired. */
|
|
16001
|
+
reason: string$2(),
|
|
16002
|
+
/** deviceId, className, trackId, score … — only what the trigger exposes. */
|
|
16003
|
+
vars: record(string$2(), TraceValueSchema)
|
|
16004
|
+
});
|
|
16005
|
+
var AutomationConditionTraceNodeSchema = object({
|
|
16006
|
+
/** `conditions` for the root, `conditions.<i>` for its i-th child, … */
|
|
16007
|
+
path: string$2().min(1),
|
|
16008
|
+
kind: _enum([
|
|
16009
|
+
"all",
|
|
16010
|
+
"any",
|
|
16011
|
+
"not",
|
|
16012
|
+
"condition",
|
|
16013
|
+
"expression",
|
|
16014
|
+
"stationary-present"
|
|
16015
|
+
]),
|
|
16016
|
+
outcome: TraceNodeOutcomeSchema,
|
|
16017
|
+
reason: string$2(),
|
|
16018
|
+
deviceId: number().int().optional(),
|
|
16019
|
+
cap: string$2().optional(),
|
|
16020
|
+
fieldPath: string$2().optional(),
|
|
16021
|
+
operator: string$2().optional(),
|
|
16022
|
+
expected: TraceValueSchema.optional(),
|
|
16023
|
+
/** What the mirror held; `null` when it held nothing (unknown). */
|
|
16024
|
+
observed: TraceValueSchema.optional()
|
|
16025
|
+
});
|
|
16026
|
+
var AutomationRunVerdictSchema = object({
|
|
16027
|
+
mode: AutomationRunModeSchema,
|
|
16028
|
+
decision: _enum([
|
|
16029
|
+
"run",
|
|
16030
|
+
"queued",
|
|
16031
|
+
"restart",
|
|
16032
|
+
"drop",
|
|
16033
|
+
"cooldown",
|
|
16034
|
+
"bypass"
|
|
16035
|
+
]),
|
|
16036
|
+
reason: string$2()
|
|
16037
|
+
});
|
|
16038
|
+
/** One executed (or not-run) step — shared by automations and Action runs. */
|
|
16039
|
+
var AutomationStepTraceSchema = object({
|
|
16040
|
+
/** `actions.<i>` */
|
|
16041
|
+
path: string$2().min(1),
|
|
16042
|
+
kind: string$2().min(1),
|
|
16043
|
+
cap: string$2().optional(),
|
|
16044
|
+
method: string$2().optional(),
|
|
16045
|
+
deviceId: number().int().optional(),
|
|
16046
|
+
startedAt: number().optional(),
|
|
16047
|
+
endedAt: number().optional(),
|
|
16048
|
+
outcome: _enum([
|
|
16049
|
+
"ok",
|
|
16050
|
+
"error",
|
|
16051
|
+
"not-run"
|
|
16052
|
+
]),
|
|
16053
|
+
message: string$2().optional()
|
|
16054
|
+
});
|
|
16055
|
+
var AutomationRunTraceSchema = object({
|
|
16056
|
+
runId: string$2().min(1),
|
|
16057
|
+
startedAt: number(),
|
|
16058
|
+
endedAt: number(),
|
|
16059
|
+
trigger: AutomationTriggerTraceSchema,
|
|
16060
|
+
conditions: array(AutomationConditionTraceNodeSchema),
|
|
16061
|
+
verdict: AutomationRunVerdictSchema.optional(),
|
|
16062
|
+
steps: array(AutomationStepTraceSchema),
|
|
16063
|
+
outcome: AutomationRunOutcomeSchema,
|
|
16064
|
+
/** How many identical consecutive skips this trace stands for (>= 1). */
|
|
16065
|
+
repeat: number().int().min(1),
|
|
16066
|
+
/** When the latest of those repeats happened. */
|
|
16067
|
+
lastAt: number()
|
|
16068
|
+
});
|
|
16069
|
+
/**
|
|
16070
|
+
* What the hub's `runs` ledger holds for one owner (`automation:<blockId>` or
|
|
16071
|
+
* `action:<actionId>`): the cooldown stamp and the trace ring.
|
|
16072
|
+
*/
|
|
16073
|
+
var AutomationStateDocSchema = object({
|
|
16074
|
+
version: literal(1),
|
|
16075
|
+
lastRunStartedAt: number().nullable(),
|
|
16076
|
+
traces: array(AutomationRunTraceSchema)
|
|
16077
|
+
});
|
|
16078
|
+
/**
|
|
16079
|
+
* Actions — named, runnable things an operator (or an automation) can start,
|
|
16080
|
+
* declared as LIST ENTRIES, never as devices.
|
|
16081
|
+
*
|
|
16082
|
+
* An entry is either **internal** (a short list of cap/wait steps kept in the
|
|
16083
|
+
* hub's `actions` record kind) or declared by an addon through the
|
|
16084
|
+
* `action-source` collection (a Home Assistant script, a scene …). The one
|
|
16085
|
+
* surface the UI and automations talk to is the `actions` cap; it keeps no
|
|
16086
|
+
* catalog of its own (D3) — every `list` asks the providers live.
|
|
16087
|
+
*
|
|
16088
|
+
* This file is the data half: the entry schema, the variables an entry may
|
|
16089
|
+
* declare, and the pure resolution of the values a run was given. It holds no
|
|
16090
|
+
* runtime.
|
|
16091
|
+
*/
|
|
16092
|
+
/** What a variable's value is. `select` takes one of `options`. */
|
|
16093
|
+
var ActionVariableTypeSchema = _enum([
|
|
16094
|
+
"string",
|
|
16095
|
+
"number",
|
|
16096
|
+
"boolean",
|
|
16097
|
+
"select"
|
|
16098
|
+
]);
|
|
16099
|
+
var ACTION_VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
16100
|
+
var ActionVariableValueSchema = union([
|
|
16101
|
+
string$2(),
|
|
16102
|
+
number(),
|
|
16103
|
+
boolean()
|
|
16104
|
+
]);
|
|
16105
|
+
/**
|
|
16106
|
+
* One declared variable. Steps reference it as `{ $var: 'name' }` (the whole
|
|
16107
|
+
* value) or `{{vars.name}}` (inside a string) in their args.
|
|
16108
|
+
*/
|
|
16109
|
+
var ActionVariableSchema = object({
|
|
16110
|
+
name: string$2().regex(ACTION_VARIABLE_NAME_RE),
|
|
16111
|
+
label: string$2().min(1).max(120).optional(),
|
|
16112
|
+
type: ActionVariableTypeSchema,
|
|
16113
|
+
required: boolean().optional(),
|
|
16114
|
+
default: ActionVariableValueSchema.optional(),
|
|
16115
|
+
/** The allowed values of a `select`. */
|
|
16116
|
+
options: array(string$2().min(1)).min(1).optional()
|
|
16117
|
+
}).superRefine((v, ctx) => {
|
|
16118
|
+
if (v.type === "select" && v.options === void 0) ctx.addIssue({
|
|
16119
|
+
code: "custom",
|
|
16120
|
+
message: "a select variable needs options",
|
|
16121
|
+
path: ["options"]
|
|
16122
|
+
});
|
|
16123
|
+
if (v.default !== void 0 && variableValueError(v, v.default) !== null) ctx.addIssue({
|
|
16124
|
+
code: "custom",
|
|
16125
|
+
message: variableValueError(v, v.default) ?? "invalid default",
|
|
16126
|
+
path: ["default"]
|
|
16127
|
+
});
|
|
16128
|
+
});
|
|
16129
|
+
/** Why `value` is not a legal value for the variable, or null when it is. */
|
|
16130
|
+
function variableValueError(variable, value) {
|
|
16131
|
+
switch (variable.type) {
|
|
16132
|
+
case "string": return typeof value === "string" ? null : `variable "${variable.name}" must be a string`;
|
|
16133
|
+
case "number": return typeof value === "number" && Number.isFinite(value) ? null : `variable "${variable.name}" must be a number`;
|
|
16134
|
+
case "boolean": return typeof value === "boolean" ? null : `variable "${variable.name}" must be true or false`;
|
|
16135
|
+
case "select": return typeof value === "string" && (variable.options ?? []).includes(value) ? null : `variable "${variable.name}" must be one of ${(variable.options ?? []).join(", ")}`;
|
|
16136
|
+
}
|
|
16137
|
+
}
|
|
16138
|
+
var ActionSourceKindSchema = _enum(["internal", "addon"]);
|
|
16139
|
+
function isInternalActionId(actionId) {
|
|
16140
|
+
return actionId.startsWith("internal:") && actionId.length > 9;
|
|
16141
|
+
}
|
|
16142
|
+
/** The addon behind a `<addonId>:<localId>` id, or null for anything else (internal included). */
|
|
16143
|
+
function addonIdOfActionId(actionId) {
|
|
16144
|
+
if (isInternalActionId(actionId)) return null;
|
|
16145
|
+
const at = actionId.indexOf(":");
|
|
16146
|
+
return at > 0 && at < actionId.length - 1 ? actionId.slice(0, at) : null;
|
|
16147
|
+
}
|
|
16148
|
+
var ActionStatusSchema = object({
|
|
16149
|
+
isRunning: boolean(),
|
|
16150
|
+
/** Ms epoch the latest run STARTED; null when it never ran. */
|
|
16151
|
+
lastRunAt: number().nullable(),
|
|
16152
|
+
/** The latest run's failure; null on success or when it never ran. */
|
|
16153
|
+
lastError: string$2().nullable()
|
|
16154
|
+
});
|
|
16155
|
+
var ActionEntrySchema = object({
|
|
16156
|
+
/** `internal:<uuid>` or `<addonId>:<localId>`. */
|
|
16157
|
+
id: string$2().min(3),
|
|
16158
|
+
name: string$2().min(1).max(120),
|
|
16159
|
+
/** The addon that declared it; `core-blocks` for an internal one. */
|
|
16160
|
+
addonId: string$2().min(1),
|
|
16161
|
+
source: ActionSourceKindSchema,
|
|
16162
|
+
/** An addon-declared entry is edited where it was declared. */
|
|
16163
|
+
readOnly: boolean(),
|
|
16164
|
+
/** Internal only: cap and wait steps. No action-in-action, so no cycles. */
|
|
16165
|
+
steps: array(ActionStepSchema).min(1).max(50).optional(),
|
|
16166
|
+
variables: array(ActionVariableSchema).max(20).optional(),
|
|
16167
|
+
status: ActionStatusSchema
|
|
16168
|
+
}).superRefine((entry, ctx) => {
|
|
16169
|
+
if (entry.source === "internal") {
|
|
16170
|
+
if (!isInternalActionId(entry.id)) ctx.addIssue({
|
|
16171
|
+
code: "custom",
|
|
16172
|
+
message: "an internal action id is internal:<uuid>",
|
|
16173
|
+
path: ["id"]
|
|
16174
|
+
});
|
|
16175
|
+
return;
|
|
16176
|
+
}
|
|
16177
|
+
if (entry.steps !== void 0) ctx.addIssue({
|
|
16178
|
+
code: "custom",
|
|
16179
|
+
message: "only an internal action carries steps",
|
|
16180
|
+
path: ["steps"]
|
|
16181
|
+
});
|
|
16182
|
+
if (!entry.readOnly) ctx.addIssue({
|
|
16183
|
+
code: "custom",
|
|
16184
|
+
message: "an addon action is read-only here",
|
|
16185
|
+
path: ["readOnly"]
|
|
16186
|
+
});
|
|
16187
|
+
if (addonIdOfActionId(entry.id) === null) ctx.addIssue({
|
|
16188
|
+
code: "custom",
|
|
16189
|
+
message: "an addon action id is <addonId>:<localId>",
|
|
16190
|
+
path: ["id"]
|
|
16191
|
+
});
|
|
16192
|
+
});
|
|
16193
|
+
/** What an operator writes for an internal action. */
|
|
16194
|
+
var ActionInputSchema = object({
|
|
16195
|
+
name: string$2().min(1).max(120),
|
|
16196
|
+
steps: array(ActionStepSchema).min(1).max(50),
|
|
16197
|
+
variables: array(ActionVariableSchema).max(20).optional()
|
|
16198
|
+
});
|
|
16199
|
+
/** Who started a run. Absent = the operator pressed it. */
|
|
16200
|
+
var ActionRunOriginSchema = object({
|
|
16201
|
+
/** The automation block whose step started it. */
|
|
16202
|
+
blockId: string$2().min(1).optional() });
|
|
16203
|
+
/** `started` = the caller did not ask to wait. */
|
|
16204
|
+
var ActionRunOutcomeSchema = union([AutomationRunOutcomeSchema, literal("started")]);
|
|
16205
|
+
var ActionRunResultSchema = object({
|
|
16206
|
+
runId: string$2().min(1),
|
|
16207
|
+
outcome: ActionRunOutcomeSchema,
|
|
16208
|
+
/** Why it did not run, or what failed. */
|
|
16209
|
+
message: string$2().optional()
|
|
16210
|
+
});
|
|
16211
|
+
_enum([
|
|
16212
|
+
"not-found",
|
|
16213
|
+
"read-only",
|
|
16214
|
+
"invalid-variables",
|
|
16215
|
+
"invalid-steps",
|
|
16216
|
+
"not-linked",
|
|
16217
|
+
"busy"
|
|
16218
|
+
]);
|
|
16219
|
+
object({
|
|
16220
|
+
actionId: string$2(),
|
|
16221
|
+
runId: string$2(),
|
|
16222
|
+
isRunning: boolean(),
|
|
16223
|
+
/** Set when the run ended. */
|
|
16224
|
+
outcome: AutomationRunOutcomeSchema.optional(),
|
|
16225
|
+
lastRunAt: number().nullable(),
|
|
16226
|
+
lastError: string$2().nullable()
|
|
15603
16227
|
});
|
|
15604
16228
|
/**
|
|
15605
16229
|
* `addon-pages` — system-scoped singleton aggregator cap. Public-facing
|
|
@@ -17194,7 +17818,22 @@ var BrokerProviderInfoSchema = object({
|
|
|
17194
17818
|
*/
|
|
17195
17819
|
interactiveConnectPath: string$2().min(1).optional(),
|
|
17196
17820
|
/** Relative hub path to disconnect the linked account (same popup pattern). */
|
|
17197
|
-
interactiveDisconnectPath: string$2().min(1).optional()
|
|
17821
|
+
interactiveDisconnectPath: string$2().min(1).optional(),
|
|
17822
|
+
/**
|
|
17823
|
+
* The kind signs in through a server-held interactive session instead of
|
|
17824
|
+
* (or before) typed credentials. `basePath` is a hub-relative route set
|
|
17825
|
+
* served by the owning addon (`/addon/<id>/...`) speaking the sign-in
|
|
17826
|
+
* contract: `POST {basePath}/start` (body: the form's settings) answers
|
|
17827
|
+
* `{ sessionId, loginUrl }`; `GET {basePath}/status?sessionId=` answers
|
|
17828
|
+
* `{ phase: 'starting' | 'awaiting-login' | 'captured' | 'error' |
|
|
17829
|
+
* 'cancelled', loginUrl, error }`; `POST {basePath}/commit`
|
|
17830
|
+
* (`{ sessionId, name, brokerId? }`) stores the credentials on a new or
|
|
17831
|
+
* existing broker and answers `{ brokerId }`; `POST {basePath}/cancel`
|
|
17832
|
+
* (`{ sessionId }`) drops the session. The dialog that edits a broker
|
|
17833
|
+
* offers it as the default path; the fields the session fills are
|
|
17834
|
+
* marked `filledBySignIn` in the kind's settings schema.
|
|
17835
|
+
*/
|
|
17836
|
+
signIn: object({ basePath: string$2().min(1) }).optional()
|
|
17198
17837
|
}))
|
|
17199
17838
|
});
|
|
17200
17839
|
var ListInputSchema = object({
|
|
@@ -18320,7 +18959,7 @@ var CompositionEventPlanSchema = object({
|
|
|
18320
18959
|
stateField: string$2(),
|
|
18321
18960
|
fieldKey: string$2()
|
|
18322
18961
|
});
|
|
18323
|
-
/** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.
|
|
18962
|
+
/** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.temperature`. */
|
|
18324
18963
|
var CompositionProblemSchema = object({
|
|
18325
18964
|
code: _enum([
|
|
18326
18965
|
"reserved-variant",
|
|
@@ -18348,7 +18987,8 @@ var CompositionProblemSchema = object({
|
|
|
18348
18987
|
"native-capability-replace",
|
|
18349
18988
|
"mixed-quantity",
|
|
18350
18989
|
"helper-mismatch",
|
|
18351
|
-
"member-constraint"
|
|
18990
|
+
"member-constraint",
|
|
18991
|
+
"retention"
|
|
18352
18992
|
]),
|
|
18353
18993
|
path: string$2(),
|
|
18354
18994
|
message: string$2()
|
|
@@ -18582,13 +19222,17 @@ Object.freeze({
|
|
|
18582
19222
|
latchedAt: {
|
|
18583
19223
|
kind: "fixed",
|
|
18584
19224
|
result: ["number", "null"]
|
|
19225
|
+
},
|
|
19226
|
+
schmitt: {
|
|
19227
|
+
kind: "fixed",
|
|
19228
|
+
result: ["boolean", "null"]
|
|
18585
19229
|
}
|
|
18586
19230
|
});
|
|
18587
19231
|
/**
|
|
18588
19232
|
* One catalog row per base reading a group helper can build (D944 §3).
|
|
18589
19233
|
* Data, not a form default written into the block.
|
|
18590
19234
|
*/
|
|
18591
|
-
var NOW = {
|
|
19235
|
+
var NOW$2 = {
|
|
18592
19236
|
fieldPath: "lastFetchedAt",
|
|
18593
19237
|
expr: "now"
|
|
18594
19238
|
};
|
|
@@ -18602,6 +19246,8 @@ function numeric(input) {
|
|
|
18602
19246
|
family: "numeric",
|
|
18603
19247
|
deviceType: DeviceType.Sensor,
|
|
18604
19248
|
memberDeviceType: DeviceType.Sensor,
|
|
19249
|
+
memberDeviceTypes: [DeviceType.Sensor],
|
|
19250
|
+
activeWhen: void 0,
|
|
18605
19251
|
commandCaps: [],
|
|
18606
19252
|
baseUnit: baseUnitOf(input.quantity),
|
|
18607
19253
|
aggregates: NUMERIC_AGGREGATES,
|
|
@@ -18617,6 +19263,8 @@ function binary(input) {
|
|
|
18617
19263
|
family: "binary",
|
|
18618
19264
|
deviceType: DeviceType.Sensor,
|
|
18619
19265
|
memberDeviceType: DeviceType.Sensor,
|
|
19266
|
+
memberDeviceTypes: [DeviceType.Sensor],
|
|
19267
|
+
activeWhen: void 0,
|
|
18620
19268
|
commandCaps: [],
|
|
18621
19269
|
quantity: "none",
|
|
18622
19270
|
baseUnit: void 0,
|
|
@@ -18630,73 +19278,73 @@ var GROUP_TEMPLATES = [
|
|
|
18630
19278
|
summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
|
|
18631
19279
|
role: DeviceRole.TemperatureSensor,
|
|
18632
19280
|
outputCap: "temperature-sensor",
|
|
18633
|
-
outputField: "
|
|
19281
|
+
outputField: "temperature",
|
|
18634
19282
|
quantity: "temperature",
|
|
18635
19283
|
defaultAggregate: "mean",
|
|
18636
19284
|
empty: "null",
|
|
18637
|
-
companions: [NOW]
|
|
19285
|
+
companions: [NOW$2]
|
|
18638
19286
|
}),
|
|
18639
19287
|
numeric({
|
|
18640
19288
|
id: "humidity",
|
|
18641
19289
|
summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
|
|
18642
19290
|
role: DeviceRole.HumiditySensor,
|
|
18643
19291
|
outputCap: "humidity-sensor",
|
|
18644
|
-
outputField: "
|
|
19292
|
+
outputField: "humidity",
|
|
18645
19293
|
quantity: "humidity",
|
|
18646
19294
|
defaultAggregate: "mean",
|
|
18647
19295
|
empty: "null",
|
|
18648
|
-
companions: [NOW]
|
|
19296
|
+
companions: [NOW$2]
|
|
18649
19297
|
}),
|
|
18650
19298
|
numeric({
|
|
18651
19299
|
id: "pressure",
|
|
18652
19300
|
summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
|
|
18653
19301
|
role: DeviceRole.PressureSensor,
|
|
18654
19302
|
outputCap: "pressure-sensor",
|
|
18655
|
-
outputField: "
|
|
19303
|
+
outputField: "pressure",
|
|
18656
19304
|
quantity: "pressure",
|
|
18657
19305
|
defaultAggregate: "mean",
|
|
18658
19306
|
empty: "null",
|
|
18659
|
-
companions: [NOW]
|
|
19307
|
+
companions: [NOW$2]
|
|
18660
19308
|
}),
|
|
18661
19309
|
numeric({
|
|
18662
19310
|
id: "illuminance",
|
|
18663
19311
|
summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
|
|
18664
19312
|
role: DeviceRole.AmbientLightSensor,
|
|
18665
19313
|
outputCap: "ambient-light-sensor",
|
|
18666
|
-
outputField: "
|
|
19314
|
+
outputField: "illuminance",
|
|
18667
19315
|
quantity: "illuminance",
|
|
18668
19316
|
defaultAggregate: "mean",
|
|
18669
19317
|
empty: "null",
|
|
18670
|
-
companions: [NOW]
|
|
19318
|
+
companions: [NOW$2]
|
|
18671
19319
|
}),
|
|
18672
19320
|
numeric({
|
|
18673
19321
|
id: "power",
|
|
18674
19322
|
summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
|
|
18675
19323
|
role: DeviceRole.PowerSensor,
|
|
18676
19324
|
outputCap: "power-meter",
|
|
18677
|
-
outputField: "
|
|
19325
|
+
outputField: "power",
|
|
18678
19326
|
quantity: "power",
|
|
18679
19327
|
defaultAggregate: "sum",
|
|
18680
19328
|
empty: "omit",
|
|
18681
|
-
companions: [NOW]
|
|
19329
|
+
companions: [NOW$2]
|
|
18682
19330
|
}),
|
|
18683
19331
|
numeric({
|
|
18684
19332
|
id: "energy",
|
|
18685
19333
|
summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
|
|
18686
19334
|
role: DeviceRole.EnergySensor,
|
|
18687
19335
|
outputCap: "power-meter",
|
|
18688
|
-
outputField: "
|
|
19336
|
+
outputField: "energy",
|
|
18689
19337
|
quantity: "energy",
|
|
18690
19338
|
defaultAggregate: "sum",
|
|
18691
19339
|
empty: "omit",
|
|
18692
|
-
companions: [NOW]
|
|
19340
|
+
companions: [NOW$2]
|
|
18693
19341
|
}),
|
|
18694
19342
|
numeric({
|
|
18695
19343
|
id: "battery",
|
|
18696
19344
|
summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
|
|
18697
19345
|
role: DeviceRole.BatterySensor,
|
|
18698
19346
|
outputCap: "battery",
|
|
18699
|
-
outputField: "
|
|
19347
|
+
outputField: "level",
|
|
18700
19348
|
quantity: "percentage",
|
|
18701
19349
|
defaultAggregate: "mean",
|
|
18702
19350
|
empty: "null",
|
|
@@ -18724,7 +19372,7 @@ var GROUP_TEMPLATES = [
|
|
|
18724
19372
|
quantity: "device",
|
|
18725
19373
|
defaultAggregate: "mean",
|
|
18726
19374
|
empty: "null",
|
|
18727
|
-
companions: [NOW]
|
|
19375
|
+
companions: [NOW$2]
|
|
18728
19376
|
}),
|
|
18729
19377
|
binary({
|
|
18730
19378
|
id: "motion",
|
|
@@ -18818,6 +19466,11 @@ var GROUP_TEMPLATES = [
|
|
|
18818
19466
|
summary: "On when at least one switch is on, or when all of them are. Offline and unknown members are left out of that decision, never read as off, and still receive the command.",
|
|
18819
19467
|
deviceType: DeviceType.Switch,
|
|
18820
19468
|
outputCap: "switch",
|
|
19469
|
+
outputField: "on",
|
|
19470
|
+
member: {
|
|
19471
|
+
cap: "switch",
|
|
19472
|
+
fieldPath: "on"
|
|
19473
|
+
},
|
|
18821
19474
|
commandCaps: ["switch"]
|
|
18822
19475
|
}),
|
|
18823
19476
|
actuator({
|
|
@@ -18825,34 +19478,136 @@ var GROUP_TEMPLATES = [
|
|
|
18825
19478
|
summary: "On when at least one light is on, or when all of them are. Brightness and colour are offered only when every member has them. A command is not sent to a member that lacks it.",
|
|
18826
19479
|
deviceType: DeviceType.Light,
|
|
18827
19480
|
outputCap: "switch",
|
|
19481
|
+
outputField: "on",
|
|
19482
|
+
member: {
|
|
19483
|
+
cap: "switch",
|
|
19484
|
+
fieldPath: "on"
|
|
19485
|
+
},
|
|
18828
19486
|
commandCaps: [
|
|
18829
19487
|
"switch",
|
|
18830
19488
|
"brightness",
|
|
18831
19489
|
"color"
|
|
18832
19490
|
]
|
|
19491
|
+
}),
|
|
19492
|
+
actuator({
|
|
19493
|
+
id: "fan",
|
|
19494
|
+
summary: "On when at least one fan is on, or when all of them are. Speed, preset, direction and oscillation are offered only when every fan has them. A command is not sent to a fan that lacks it.",
|
|
19495
|
+
deviceType: DeviceType.Fan,
|
|
19496
|
+
outputCap: "switch",
|
|
19497
|
+
outputField: "on",
|
|
19498
|
+
member: {
|
|
19499
|
+
cap: "switch",
|
|
19500
|
+
fieldPath: "on"
|
|
19501
|
+
},
|
|
19502
|
+
commandCaps: ["switch", "fan-control"]
|
|
19503
|
+
}),
|
|
19504
|
+
actuator({
|
|
19505
|
+
id: "cover",
|
|
19506
|
+
summary: "Open when at least one cover is open or part-open, or only when all of them are. Position and tilt are offered only when every cover has them. A cover that reports nothing is left out, never read as closed.",
|
|
19507
|
+
deviceType: DeviceType.Cover,
|
|
19508
|
+
outputCap: "group-active",
|
|
19509
|
+
outputField: "active",
|
|
19510
|
+
member: {
|
|
19511
|
+
cap: "cover",
|
|
19512
|
+
fieldPath: "state"
|
|
19513
|
+
},
|
|
19514
|
+
activeWhen: [
|
|
19515
|
+
"open",
|
|
19516
|
+
"opening",
|
|
19517
|
+
"stopped"
|
|
19518
|
+
],
|
|
19519
|
+
commandCaps: ["cover"]
|
|
19520
|
+
}),
|
|
19521
|
+
actuator({
|
|
19522
|
+
id: "valve",
|
|
19523
|
+
summary: "Open when at least one valve is open or part-open, or only when all of them are. Position is offered only when every valve has it. A valve that reports nothing is left out, never read as closed.",
|
|
19524
|
+
deviceType: DeviceType.Valve,
|
|
19525
|
+
outputCap: "group-active",
|
|
19526
|
+
outputField: "active",
|
|
19527
|
+
member: {
|
|
19528
|
+
cap: "valve",
|
|
19529
|
+
fieldPath: "state"
|
|
19530
|
+
},
|
|
19531
|
+
activeWhen: [
|
|
19532
|
+
"open",
|
|
19533
|
+
"opening",
|
|
19534
|
+
"stopped"
|
|
19535
|
+
],
|
|
19536
|
+
commandCaps: ["valve"]
|
|
19537
|
+
}),
|
|
19538
|
+
actuator({
|
|
19539
|
+
id: "lock",
|
|
19540
|
+
summary: "Locked when at least one lock is locked, or only when all of them are. A jammed or moving lock is not locked. Locking is one tap; unlocking the group needs a confirmation every time, and nothing is sent without it.",
|
|
19541
|
+
deviceType: DeviceType.Lock,
|
|
19542
|
+
outputCap: "group-active",
|
|
19543
|
+
outputField: "active",
|
|
19544
|
+
member: {
|
|
19545
|
+
cap: "lock-control",
|
|
19546
|
+
fieldPath: "state"
|
|
19547
|
+
},
|
|
19548
|
+
activeWhen: ["locked"],
|
|
19549
|
+
commandCaps: ["lock-control"]
|
|
19550
|
+
}),
|
|
19551
|
+
actuator({
|
|
19552
|
+
id: "media-player",
|
|
19553
|
+
summary: "Playing when at least one player is playing, or only when all of them are. Volume is offered only when every player has it. A player that reports nothing is left out, never read as stopped.",
|
|
19554
|
+
deviceType: DeviceType.MediaPlayer,
|
|
19555
|
+
outputCap: "group-active",
|
|
19556
|
+
outputField: "active",
|
|
19557
|
+
member: {
|
|
19558
|
+
cap: "media-player",
|
|
19559
|
+
fieldPath: "state"
|
|
19560
|
+
},
|
|
19561
|
+
activeWhen: ["playing", "buffering"],
|
|
19562
|
+
commandCaps: ["media-player"]
|
|
19563
|
+
}),
|
|
19564
|
+
actuator({
|
|
19565
|
+
id: "climate",
|
|
19566
|
+
summary: "Running when at least one thermostat or air conditioner is not off, or only when all of them are. Target temperature and fan are offered only when every device has them. A device that reports nothing is left out, never read as off.",
|
|
19567
|
+
deviceType: DeviceType.Thermostat,
|
|
19568
|
+
memberDeviceTypes: [DeviceType.Thermostat, DeviceType.Climate],
|
|
19569
|
+
outputCap: "group-active",
|
|
19570
|
+
outputField: "active",
|
|
19571
|
+
member: {
|
|
19572
|
+
cap: "climate-control",
|
|
19573
|
+
fieldPath: "mode"
|
|
19574
|
+
},
|
|
19575
|
+
activeWhen: [
|
|
19576
|
+
"heat",
|
|
19577
|
+
"cool",
|
|
19578
|
+
"auto",
|
|
19579
|
+
"heat_cool",
|
|
19580
|
+
"fan_only",
|
|
19581
|
+
"dry"
|
|
19582
|
+
],
|
|
19583
|
+
commandCaps: ["climate-control"]
|
|
18833
19584
|
})
|
|
18834
19585
|
];
|
|
18835
19586
|
function actuator(input) {
|
|
18836
19587
|
return {
|
|
18837
|
-
|
|
19588
|
+
id: input.id,
|
|
19589
|
+
summary: input.summary,
|
|
19590
|
+
deviceType: input.deviceType,
|
|
19591
|
+
outputCap: input.outputCap,
|
|
19592
|
+
outputField: input.outputField,
|
|
19593
|
+
commandCaps: input.commandCaps,
|
|
18838
19594
|
family: "actuator",
|
|
18839
19595
|
role: void 0,
|
|
18840
19596
|
memberDeviceType: input.deviceType,
|
|
18841
|
-
|
|
19597
|
+
memberDeviceTypes: input.memberDeviceTypes ?? [input.deviceType],
|
|
19598
|
+
activeWhen: input.activeWhen,
|
|
18842
19599
|
quantity: "none",
|
|
18843
19600
|
baseUnit: void 0,
|
|
18844
19601
|
aggregates: BINARY_AGGREGATES,
|
|
18845
19602
|
defaultAggregate: "any",
|
|
18846
19603
|
empty: "offline",
|
|
18847
|
-
accept: [
|
|
18848
|
-
cap: "switch",
|
|
18849
|
-
fieldPath: "on"
|
|
18850
|
-
}],
|
|
19604
|
+
accept: [input.member],
|
|
18851
19605
|
companions: [CHANGED]
|
|
18852
19606
|
};
|
|
18853
19607
|
}
|
|
18854
19608
|
new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
|
|
18855
19609
|
DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
|
|
19610
|
+
DeviceType.Camera;
|
|
18856
19611
|
`${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
|
|
18857
19612
|
/**
|
|
18858
19613
|
* What a composition may BUILD on a device type (G2, D676): the caps a new
|
|
@@ -19167,7 +19922,18 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
|
|
|
19167
19922
|
kind: "mutation",
|
|
19168
19923
|
auth: "admin",
|
|
19169
19924
|
access: "view"
|
|
19170
|
-
})
|
|
19925
|
+
}), method(object({
|
|
19926
|
+
blockId: string$2().min(1),
|
|
19927
|
+
trace: AutomationRunTraceSchema.optional(),
|
|
19928
|
+
runStartedAt: number().optional(),
|
|
19929
|
+
keepTraces: number().int().min(1).max(100).optional()
|
|
19930
|
+
}), _void(), {
|
|
19931
|
+
kind: "mutation",
|
|
19932
|
+
auth: "admin"
|
|
19933
|
+
}), method(object({ blockId: string$2().min(1) }), AutomationStateDocSchema, { auth: "admin" }), method(object({ blockId: string$2().min(1) }), object({
|
|
19934
|
+
traces: array(AutomationRunTraceSchema),
|
|
19935
|
+
lastError: string$2().nullable()
|
|
19936
|
+
}), { auth: "admin" });
|
|
19171
19937
|
/**
|
|
19172
19938
|
* `custom-model-registry` — collection cap exposing operator-registered
|
|
19173
19939
|
* custom detection models. Each provider (today: `addon-model-studio`)
|
|
@@ -44076,7 +44842,7 @@ var NetworkAccessStatusSchema = object({
|
|
|
44076
44842
|
});
|
|
44077
44843
|
/**
|
|
44078
44844
|
* Optional, richer endpoint shape returned by providers that expose
|
|
44079
|
-
* MORE than one ingress concurrently (Tailscale
|
|
44845
|
+
* MORE than one ingress concurrently (Tailscale Funnel with mixed
|
|
44080
44846
|
* serve+funnel rules, future ngrok multi-tunnel, …). Each entry carries
|
|
44081
44847
|
* the originating provider config (mode + sourcePort) so the
|
|
44082
44848
|
* orchestrator UI can label rows distinctly. Providers that expose only
|
|
@@ -44469,51 +45235,6 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
|
|
|
44469
45235
|
kind: "mutation",
|
|
44470
45236
|
auth: "admin"
|
|
44471
45237
|
});
|
|
44472
|
-
new Set([
|
|
44473
|
-
{
|
|
44474
|
-
id: "person",
|
|
44475
|
-
name: "Person"
|
|
44476
|
-
},
|
|
44477
|
-
{
|
|
44478
|
-
id: "vehicle",
|
|
44479
|
-
name: "Vehicle"
|
|
44480
|
-
},
|
|
44481
|
-
{
|
|
44482
|
-
id: "animal",
|
|
44483
|
-
name: "Animal"
|
|
44484
|
-
},
|
|
44485
|
-
{
|
|
44486
|
-
id: "package",
|
|
44487
|
-
name: "Package"
|
|
44488
|
-
}
|
|
44489
|
-
].map((l) => l.id));
|
|
44490
|
-
var COCO_TO_MACRO = {
|
|
44491
|
-
mapping: {
|
|
44492
|
-
person: "person",
|
|
44493
|
-
bicycle: "vehicle",
|
|
44494
|
-
car: "vehicle",
|
|
44495
|
-
motorcycle: "vehicle",
|
|
44496
|
-
airplane: "vehicle",
|
|
44497
|
-
bus: "vehicle",
|
|
44498
|
-
train: "vehicle",
|
|
44499
|
-
truck: "vehicle",
|
|
44500
|
-
boat: "vehicle",
|
|
44501
|
-
bird: "animal",
|
|
44502
|
-
cat: "animal",
|
|
44503
|
-
dog: "animal",
|
|
44504
|
-
horse: "animal",
|
|
44505
|
-
sheep: "animal",
|
|
44506
|
-
cow: "animal",
|
|
44507
|
-
elephant: "animal",
|
|
44508
|
-
bear: "animal",
|
|
44509
|
-
zebra: "animal",
|
|
44510
|
-
giraffe: "animal",
|
|
44511
|
-
suitcase: "package",
|
|
44512
|
-
backpack: "package",
|
|
44513
|
-
handbag: "package"
|
|
44514
|
-
},
|
|
44515
|
-
preserveOriginal: false
|
|
44516
|
-
};
|
|
44517
45238
|
var AUDIO_MACRO_LABELS = [
|
|
44518
45239
|
{
|
|
44519
45240
|
id: "speech",
|
|
@@ -46763,7 +47484,20 @@ var NcRuleInputSchema = object({
|
|
|
46763
47484
|
* save by the hub's grouping guard). `.optional()`, never `.default()`: the addon cap
|
|
46764
47485
|
* path does not run Zod defaults.
|
|
46765
47486
|
*/
|
|
46766
|
-
groupingSec: number().int().min(5).max(60).optional()
|
|
47487
|
+
groupingSec: number().int().min(5).max(60).optional(),
|
|
47488
|
+
/**
|
|
47489
|
+
* ASK ME TO CONFIRM DETECTIONS (#243): a DETECTION notification of this rule
|
|
47490
|
+
* carries "Correct" / "Wrong" buttons (and, where the channel has room, a
|
|
47491
|
+
* "Wrong, it is a <class>" choice). A tap marks the track for retraining, the
|
|
47492
|
+
* same mark the Library writes, and records whose notification it was and
|
|
47493
|
+
* when. A channel with no buttons gets the same two choices as links.
|
|
47494
|
+
*
|
|
47495
|
+
* `.optional()`, never `.default()` (a Zod default does not run on the addon
|
|
47496
|
+
* cap path): absent and `false` both mean off, which is what every rule
|
|
47497
|
+
* authored before this field meant. An editor turns it off by writing
|
|
47498
|
+
* `false`, so it needs no `clear` key.
|
|
47499
|
+
*/
|
|
47500
|
+
askVerdict: boolean().optional()
|
|
46767
47501
|
});
|
|
46768
47502
|
/**
|
|
46769
47503
|
* Partial patch for `updateRule` — any subset of the input fields, plus the
|
|
@@ -57067,9 +57801,9 @@ method(object({ nodeId: string$2().optional() }).optional(), array(AddonListItem
|
|
|
57067
57801
|
*/
|
|
57068
57802
|
var AirQualitySensorStatusSchema = object({
|
|
57069
57803
|
/** Carbon dioxide concentration in ppm. */
|
|
57070
|
-
|
|
57804
|
+
co2: number().min(0).optional().meta(readingMeta("concentration-volume")),
|
|
57071
57805
|
/** Total volatile organic compounds in ppb. */
|
|
57072
|
-
|
|
57806
|
+
voc: number().min(0).optional().meta(readingMeta("concentration-ppb")),
|
|
57073
57807
|
/** Particulate matter ≤ 2.5 μm in µg/m³. */
|
|
57074
57808
|
pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
|
|
57075
57809
|
/** Particulate matter ≤ 10 μm in µg/m³. */
|
|
@@ -57102,12 +57836,12 @@ var airQualitySensorCapability = {
|
|
|
57102
57836
|
* History (D850): recorded series.
|
|
57103
57837
|
*/
|
|
57104
57838
|
history: { fields: {
|
|
57105
|
-
|
|
57839
|
+
co2: {
|
|
57106
57840
|
kind: "gauge",
|
|
57107
57841
|
unit: "ppm",
|
|
57108
57842
|
deadband: 5
|
|
57109
57843
|
},
|
|
57110
|
-
|
|
57844
|
+
voc: {
|
|
57111
57845
|
kind: "gauge",
|
|
57112
57846
|
unit: "ppb",
|
|
57113
57847
|
deadband: 5
|
|
@@ -57138,7 +57872,7 @@ var airQualitySensorCapability = {
|
|
|
57138
57872
|
*/
|
|
57139
57873
|
var AmbientLightSensorStatusSchema = object({
|
|
57140
57874
|
/** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
|
|
57141
|
-
|
|
57875
|
+
illuminance: number().min(0).nullable().meta(readingMeta("illuminance", {
|
|
57142
57876
|
precision: 0,
|
|
57143
57877
|
label: "Illuminance"
|
|
57144
57878
|
})),
|
|
@@ -57167,7 +57901,7 @@ var ambientLightSensorCapability = {
|
|
|
57167
57901
|
/**
|
|
57168
57902
|
* History (D850): recorded series. Wide range, so a RELATIVE deadband (10 %).
|
|
57169
57903
|
*/
|
|
57170
|
-
history: { fields: {
|
|
57904
|
+
history: { fields: { illuminance: {
|
|
57171
57905
|
kind: "gauge",
|
|
57172
57906
|
unit: "lx",
|
|
57173
57907
|
deadbandPct: 10,
|
|
@@ -57306,6 +58040,84 @@ var audioMetricsCapability = {
|
|
|
57306
58040
|
*/
|
|
57307
58041
|
history: "none"
|
|
57308
58042
|
};
|
|
58043
|
+
method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
|
|
58044
|
+
actionId: string$2().min(1),
|
|
58045
|
+
variables: record(string$2(), ActionVariableValueSchema).optional()
|
|
58046
|
+
}), _void(), {
|
|
58047
|
+
kind: "mutation",
|
|
58048
|
+
auth: "admin"
|
|
58049
|
+
}), method(object({ actionId: string$2().min(1) }), _void(), {
|
|
58050
|
+
kind: "mutation",
|
|
58051
|
+
auth: "admin"
|
|
58052
|
+
});
|
|
58053
|
+
/**
|
|
58054
|
+
* actions — the ONE surface for runnable actions, for the UI and for
|
|
58055
|
+
* automations. Provided by the core-blocks builtin on the hub.
|
|
58056
|
+
*
|
|
58057
|
+
* An action is a LIST ENTRY, not a device (D883): either internal (cap and wait
|
|
58058
|
+
* steps, kept in the hub's `actions` record kind) or declared by an addon over
|
|
58059
|
+
* `action-source`. `list` asks every `action-source` provider LIVE and overlays
|
|
58060
|
+
* the run status from the hub's `runs` ledger; there is no catalog here to
|
|
58061
|
+
* drift (D3).
|
|
58062
|
+
*
|
|
58063
|
+
* **Permissions follow the camera, as linked devices do.** An action is linked
|
|
58064
|
+
* to cameras by the operator (`setLinks`, D356: nothing links itself). A
|
|
58065
|
+
* non-admin may `get` / `run` an action only through a camera they may see that
|
|
58066
|
+
* links it: they name the camera (`deviceId`, a top-level field so the scope
|
|
58067
|
+
* gate sees it) and the provider refuses an action that camera does not link.
|
|
58068
|
+
* An admin, and an in-process caller (an automation block), may run any action.
|
|
58069
|
+
* Every refusal is typed (`[not-linked] …`) and logged.
|
|
58070
|
+
*
|
|
58071
|
+
* A second `run` of an action that is already running is REFUSED — traced and
|
|
58072
|
+
* logged — never queued.
|
|
58073
|
+
*/
|
|
58074
|
+
var ActionIdInput = object({ actionId: string$2().min(1) });
|
|
58075
|
+
var ActionLinksSchema = object({
|
|
58076
|
+
deviceId: number().int().nonnegative(),
|
|
58077
|
+
actionIds: array(string$2().min(1)).max(100)
|
|
58078
|
+
});
|
|
58079
|
+
method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
|
|
58080
|
+
actionId: string$2().min(1),
|
|
58081
|
+
deviceId: number().int().nonnegative().optional()
|
|
58082
|
+
}), object({ action: ActionEntrySchema.nullable() }), {
|
|
58083
|
+
role: "viewer",
|
|
58084
|
+
auth: "protected",
|
|
58085
|
+
kind: "query",
|
|
58086
|
+
caller: "required"
|
|
58087
|
+
}), method(object({
|
|
58088
|
+
actionId: string$2().min(1),
|
|
58089
|
+
variables: record(string$2(), ActionVariableValueSchema).optional(),
|
|
58090
|
+
wait: boolean().optional(),
|
|
58091
|
+
origin: ActionRunOriginSchema.optional(),
|
|
58092
|
+
deviceId: number().int().nonnegative().optional()
|
|
58093
|
+
}), ActionRunResultSchema, {
|
|
58094
|
+
role: "viewer",
|
|
58095
|
+
auth: "protected",
|
|
58096
|
+
kind: "mutation",
|
|
58097
|
+
caller: "required"
|
|
58098
|
+
}), method(ActionIdInput, _void(), {
|
|
58099
|
+
kind: "mutation",
|
|
58100
|
+
auth: "admin"
|
|
58101
|
+
}), method(ActionIdInput, object({ traces: array(AutomationRunTraceSchema) }), { auth: "admin" }), method(object({ action: ActionInputSchema }), object({ action: ActionEntrySchema }), {
|
|
58102
|
+
kind: "mutation",
|
|
58103
|
+
auth: "admin"
|
|
58104
|
+
}), method(object({
|
|
58105
|
+
actionId: string$2().min(1),
|
|
58106
|
+
action: ActionInputSchema.partial()
|
|
58107
|
+
}), object({ action: ActionEntrySchema }), {
|
|
58108
|
+
kind: "mutation",
|
|
58109
|
+
auth: "admin"
|
|
58110
|
+
}), method(ActionIdInput, _void(), {
|
|
58111
|
+
kind: "mutation",
|
|
58112
|
+
auth: "admin"
|
|
58113
|
+
}), method(object({ deviceId: number().int().nonnegative() }), array(ActionEntrySchema), {
|
|
58114
|
+
role: "viewer",
|
|
58115
|
+
auth: "protected",
|
|
58116
|
+
kind: "query"
|
|
58117
|
+
}), method(object({ deviceId: number().int().nonnegative() }), ActionLinksSchema, { auth: "admin" }), method(ActionLinksSchema, ActionLinksSchema, {
|
|
58118
|
+
kind: "mutation",
|
|
58119
|
+
auth: "admin"
|
|
58120
|
+
});
|
|
57309
58121
|
/**
|
|
57310
58122
|
* Automation-control cap. Models HA `automation.*` entities on
|
|
57311
58123
|
* `DeviceType.Automation`. An automation is a trigger+condition+
|
|
@@ -57402,7 +58214,7 @@ var BatteryStatusSchema = object({
|
|
|
57402
58214
|
* Consumers must SKIP a null rather than coerce it — `battery-band` already
|
|
57403
58215
|
* declines to band a non-finite reading, which is the correct shape.
|
|
57404
58216
|
*/
|
|
57405
|
-
|
|
58217
|
+
level: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
|
|
57406
58218
|
/**
|
|
57407
58219
|
* Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
|
|
57408
58220
|
* Reolink-specific for the Solar Panel 2 accessory (will become
|
|
@@ -57412,7 +58224,7 @@ var BatteryStatusSchema = object({
|
|
|
57412
58224
|
* `'unknown'` is the absence of an answer: the provider has not read the
|
|
57413
58225
|
* power fields yet, or the firmware reported neither. Before it existed,
|
|
57414
58226
|
* two unreadable fields produced `'none'`, and a camera nobody had reached
|
|
57415
|
-
* was drawn "on battery alone" — the same D315 defect `
|
|
58227
|
+
* was drawn "on battery alone" — the same D315 defect `level` closed
|
|
57416
58228
|
* by going nullable. Consumers SKIP it (no charger state published, no
|
|
57417
58229
|
* glyph, no HomeKit ChargingState) rather than coerce it either way.
|
|
57418
58230
|
*/
|
|
@@ -57436,7 +58248,7 @@ var BatteryStatusSchema = object({
|
|
|
57436
58248
|
* that only report charge. A second reading on this cap, not a second
|
|
57437
58249
|
* `numeric-sensor` on the same device.
|
|
57438
58250
|
*/
|
|
57439
|
-
|
|
58251
|
+
voltage: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
|
|
57440
58252
|
/**
|
|
57441
58253
|
* Ms epoch of the last time the device PROVED it was reachable — a
|
|
57442
58254
|
* completed firmware round-trip, an observed wake, or an inbound push
|
|
@@ -57470,7 +58282,7 @@ var BatteryStatusSchema = object({
|
|
|
57470
58282
|
/**
|
|
57471
58283
|
* True when the source is a BINARY low-battery indicator (HA
|
|
57472
58284
|
* `binary_sensor` device_class=battery / `LOW_BAT`) that has no real
|
|
57473
|
-
* charge level — `
|
|
58285
|
+
* charge level — `level` is then a coarse stand-in (100 = normal,
|
|
57474
58286
|
* sub-threshold = low). UI MUST render "Normal"/"Low" instead of a
|
|
57475
58287
|
* misleading exact percentage. Absent/false → genuine 0–100 % reading.
|
|
57476
58288
|
*/
|
|
@@ -57483,21 +58295,22 @@ var BATTERY_DEVICE_TYPES = [
|
|
|
57483
58295
|
DeviceType.Switch,
|
|
57484
58296
|
DeviceType.PetFeeder,
|
|
57485
58297
|
DeviceType.Container,
|
|
57486
|
-
DeviceType.Thermostat
|
|
58298
|
+
DeviceType.Thermostat,
|
|
58299
|
+
DeviceType.EventEmitter
|
|
57487
58300
|
];
|
|
57488
58301
|
/** Generic Customize target for the percentage reading (D936). */
|
|
57489
58302
|
var batteryCustomize = {
|
|
57490
58303
|
version: 1,
|
|
57491
58304
|
targets: [{
|
|
57492
|
-
id: "
|
|
58305
|
+
id: "level",
|
|
57493
58306
|
cardinality: "scalar",
|
|
57494
58307
|
vocabulary: {
|
|
57495
58308
|
cap: "battery",
|
|
57496
|
-
stateField: "
|
|
58309
|
+
stateField: "level"
|
|
57497
58310
|
},
|
|
57498
58311
|
deviceTypes: BATTERY_DEVICE_TYPES,
|
|
57499
58312
|
apply: "add",
|
|
57500
|
-
path: "
|
|
58313
|
+
path: "level",
|
|
57501
58314
|
sources: [{
|
|
57502
58315
|
kind: "field",
|
|
57503
58316
|
valueKinds: ["number", "string"],
|
|
@@ -57591,7 +58404,7 @@ onStatusChanged: { data: object({
|
|
|
57591
58404
|
schema: BatteryStatusSchema,
|
|
57592
58405
|
kind: "push",
|
|
57593
58406
|
empty: {
|
|
57594
|
-
|
|
58407
|
+
level: null,
|
|
57595
58408
|
charging: "unknown",
|
|
57596
58409
|
sleeping: false,
|
|
57597
58410
|
lastUpdated: 0
|
|
@@ -57618,11 +58431,11 @@ onStatusChanged: { data: object({
|
|
|
57618
58431
|
* History (D850): recorded series. The generic capture replaces the `battery` series of `pipeline-analytics:stats-5m`.
|
|
57619
58432
|
*/
|
|
57620
58433
|
history: { fields: {
|
|
57621
|
-
|
|
58434
|
+
level: {
|
|
57622
58435
|
kind: "gauge",
|
|
57623
58436
|
unit: "%"
|
|
57624
58437
|
},
|
|
57625
|
-
|
|
58438
|
+
voltage: {
|
|
57626
58439
|
kind: "gauge",
|
|
57627
58440
|
unit: "V"
|
|
57628
58441
|
}
|
|
@@ -57682,14 +58495,14 @@ var binaryCapability = {
|
|
|
57682
58495
|
* controls to this single-method surface.
|
|
57683
58496
|
*
|
|
57684
58497
|
* The cap is intentionally minimal: a single `setBrightness({deviceId,
|
|
57685
|
-
*
|
|
58498
|
+
* level})` mutation plus the auto-injected `getStatus`. Drivers
|
|
57686
58499
|
* that expose richer controls (color temperature, scenes, schedules)
|
|
57687
58500
|
* should surface those via the device's `getSettingsUISchema()`
|
|
57688
58501
|
* instead of bloating this cap.
|
|
57689
58502
|
*/
|
|
57690
58503
|
var BrightnessStatusSchema = object({
|
|
57691
58504
|
/** Current level as 0..100 inclusive. Firmware-reported. */
|
|
57692
|
-
|
|
58505
|
+
level: number().min(0).max(100).meta(readingMeta("percentage")),
|
|
57693
58506
|
/** Ms epoch of the last operator-driven change. Useful for UI freshness. */
|
|
57694
58507
|
lastChangedAt: number()
|
|
57695
58508
|
});
|
|
@@ -57701,7 +58514,7 @@ var brightnessCapability = {
|
|
|
57701
58514
|
deviceTypes: [DeviceType.Light],
|
|
57702
58515
|
methods: { setBrightness: method(object({
|
|
57703
58516
|
deviceId: number().int().nonnegative(),
|
|
57704
|
-
|
|
58517
|
+
level: number().min(0).max(100)
|
|
57705
58518
|
}), _void(), {
|
|
57706
58519
|
kind: "mutation",
|
|
57707
58520
|
auth: "protected"
|
|
@@ -57714,7 +58527,7 @@ var brightnessCapability = {
|
|
|
57714
58527
|
*/
|
|
57715
58528
|
onBrightnessChanged: { data: object({
|
|
57716
58529
|
deviceId: number(),
|
|
57717
|
-
|
|
58530
|
+
level: number().min(0).max(100),
|
|
57718
58531
|
lastChangedAt: number()
|
|
57719
58532
|
}) } },
|
|
57720
58533
|
status: {
|
|
@@ -57737,7 +58550,7 @@ onBrightnessChanged: { data: object({
|
|
|
57737
58550
|
/**
|
|
57738
58551
|
* History (D850): recorded series. Actuator tier: level keeps every change.
|
|
57739
58552
|
*/
|
|
57740
|
-
history: { fields: {
|
|
58553
|
+
history: { fields: { level: {
|
|
57741
58554
|
kind: "gauge",
|
|
57742
58555
|
unit: "%",
|
|
57743
58556
|
minIntervalMs: 0
|
|
@@ -58351,11 +59164,11 @@ var ColorInputSchema = discriminatedUnion("mode", [
|
|
|
58351
59164
|
}),
|
|
58352
59165
|
object({
|
|
58353
59166
|
mode: literal("mired"),
|
|
58354
|
-
|
|
59167
|
+
colorTemperature: number().int().min(50).max(1e3)
|
|
58355
59168
|
})
|
|
58356
59169
|
]);
|
|
58357
59170
|
var ColorStatusSchema = object({
|
|
58358
|
-
/** Active color mode — which of `rgb` / `hsv` / `
|
|
59171
|
+
/** Active color mode — which of `rgb` / `hsv` / `colorTemperature` reflects
|
|
58359
59172
|
* the bulb's current state. */
|
|
58360
59173
|
mode: _enum([
|
|
58361
59174
|
"rgb",
|
|
@@ -58367,7 +59180,7 @@ var ColorStatusSchema = object({
|
|
|
58367
59180
|
/** Populated when `mode === 'hsv'`. */
|
|
58368
59181
|
hsv: HsvTripletSchema.optional(),
|
|
58369
59182
|
/** Populated when `mode === 'mired'`. */
|
|
58370
|
-
|
|
59183
|
+
colorTemperature: number().int().optional().meta(readingMeta("colour-temperature")),
|
|
58371
59184
|
/** Ms epoch of the last operator-driven change. */
|
|
58372
59185
|
lastChangedAt: number()
|
|
58373
59186
|
});
|
|
@@ -58399,7 +59212,7 @@ onColorChanged: { data: object({
|
|
|
58399
59212
|
]),
|
|
58400
59213
|
rgb: RgbTripletSchema.optional(),
|
|
58401
59214
|
hsv: HsvTripletSchema.optional(),
|
|
58402
|
-
|
|
59215
|
+
colorTemperature: number().int().optional(),
|
|
58403
59216
|
lastChangedAt: number()
|
|
58404
59217
|
}) } },
|
|
58405
59218
|
status: {
|
|
@@ -59276,11 +60089,11 @@ var DishwasherStatusSchema = object({
|
|
|
59276
60089
|
/** Feature keys of the programs the appliance currently offers — drives the UI selector. */
|
|
59277
60090
|
availablePrograms: array(string$2()),
|
|
59278
60091
|
/** Seconds remaining on the active cycle, or null when not running / not known. */
|
|
59279
|
-
|
|
60092
|
+
remaining: number().int().nonnegative().nullable().meta(readingMeta("duration")),
|
|
59280
60093
|
/** Seconds the delayed start is set to (time until the cycle starts), or null. */
|
|
59281
|
-
|
|
60094
|
+
delayedStart: number().int().nonnegative().nullable().meta(readingMeta("duration")),
|
|
59282
60095
|
/** Cycle completion 0..100, or null when not applicable. */
|
|
59283
|
-
|
|
60096
|
+
progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
59284
60097
|
/** True when the door is reported open; null when the door state is not known. */
|
|
59285
60098
|
doorOpen: boolean().nullable(),
|
|
59286
60099
|
/** True when the door is latched (locked) during a cycle; null when not reported. */
|
|
@@ -59499,14 +60312,14 @@ var dishwasherCapability = {
|
|
|
59499
60312
|
runtimeState: DishwasherStatusSchema,
|
|
59500
60313
|
durability: "session",
|
|
59501
60314
|
history: { fields: {
|
|
59502
|
-
|
|
60315
|
+
progress: {
|
|
59503
60316
|
kind: "gauge",
|
|
59504
60317
|
unit: "%",
|
|
59505
60318
|
deadband: 2
|
|
59506
60319
|
},
|
|
59507
60320
|
state: { kind: "enum" },
|
|
59508
60321
|
operationState: { kind: "enum" },
|
|
59509
|
-
|
|
60322
|
+
remaining: {
|
|
59510
60323
|
kind: "gauge",
|
|
59511
60324
|
unit: "s",
|
|
59512
60325
|
minIntervalMs: 6e4
|
|
@@ -60097,7 +60910,7 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
|
|
|
60097
60910
|
var FanControlStatusSchema = object({
|
|
60098
60911
|
/** Active speed as 0..100 inclusive. Null when the device has no
|
|
60099
60912
|
* speed surface (single-speed fan). */
|
|
60100
|
-
|
|
60913
|
+
speed: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
60101
60914
|
/** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
|
|
60102
60915
|
* a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
|
|
60103
60916
|
percentageStep: number().positive().optional().meta(readingMeta("percentage")),
|
|
@@ -60124,7 +60937,7 @@ var fanControlCapability = {
|
|
|
60124
60937
|
methods: {
|
|
60125
60938
|
setPercentage: method(object({
|
|
60126
60939
|
deviceId: number().int().nonnegative(),
|
|
60127
|
-
|
|
60940
|
+
speed: number().min(0).max(100)
|
|
60128
60941
|
}), _void(), {
|
|
60129
60942
|
kind: "mutation",
|
|
60130
60943
|
auth: "protected"
|
|
@@ -60157,7 +60970,7 @@ var fanControlCapability = {
|
|
|
60157
60970
|
},
|
|
60158
60971
|
/**
|
|
60159
60972
|
* Runtime-state slice — mirrored by the kernel. UI fan speed
|
|
60160
|
-
* sliders read `
|
|
60973
|
+
* sliders read `speed` for live updates.
|
|
60161
60974
|
*/
|
|
60162
60975
|
runtimeState: FanControlStatusSchema,
|
|
60163
60976
|
/**
|
|
@@ -60171,7 +60984,7 @@ var fanControlCapability = {
|
|
|
60171
60984
|
* History (D850): recorded series. Actuator tier: speed keeps every change.
|
|
60172
60985
|
*/
|
|
60173
60986
|
history: { fields: {
|
|
60174
|
-
|
|
60987
|
+
speed: {
|
|
60175
60988
|
kind: "gauge",
|
|
60176
60989
|
unit: "%",
|
|
60177
60990
|
minIntervalMs: 0
|
|
@@ -60360,7 +61173,17 @@ var groupCommandsCapability = {
|
|
|
60360
61173
|
scope: "device",
|
|
60361
61174
|
deviceNative: true,
|
|
60362
61175
|
mode: "singleton",
|
|
60363
|
-
deviceTypes: [
|
|
61176
|
+
deviceTypes: [
|
|
61177
|
+
DeviceType.Switch,
|
|
61178
|
+
DeviceType.Light,
|
|
61179
|
+
DeviceType.Fan,
|
|
61180
|
+
DeviceType.Cover,
|
|
61181
|
+
DeviceType.Valve,
|
|
61182
|
+
DeviceType.Lock,
|
|
61183
|
+
DeviceType.MediaPlayer,
|
|
61184
|
+
DeviceType.Thermostat,
|
|
61185
|
+
DeviceType.Climate
|
|
61186
|
+
],
|
|
60364
61187
|
methods: {},
|
|
60365
61188
|
status: {
|
|
60366
61189
|
schema: GroupCommandsSchema,
|
|
@@ -60378,6 +61201,49 @@ var groupCommandsCapability = {
|
|
|
60378
61201
|
history: "none"
|
|
60379
61202
|
};
|
|
60380
61203
|
/**
|
|
61204
|
+
* Whether a helper actuator group counts as active (D944): at least one
|
|
61205
|
+
* included member is, or every included member is. What "active" means is the
|
|
61206
|
+
* type's: a cover is open, a lock is locked, a media player is playing, a
|
|
61207
|
+
* climate device is not off. A group of lights, switches and fans reports
|
|
61208
|
+
* `switch.on` instead; this cap is for the types whose own state is not a
|
|
61209
|
+
* yes/no. Unknown members are left out (`group-status` counts them) and an
|
|
61210
|
+
* empty included set takes the device offline, never `active: false`.
|
|
61211
|
+
*/
|
|
61212
|
+
var GroupActiveSchema = object({
|
|
61213
|
+
active: boolean().meta({ label: "Active" }),
|
|
61214
|
+
/** Ms epoch when the slice was last updated. */
|
|
61215
|
+
lastChangedAt: number()
|
|
61216
|
+
});
|
|
61217
|
+
var groupActiveCapability = {
|
|
61218
|
+
name: "group-active",
|
|
61219
|
+
scope: "device",
|
|
61220
|
+
deviceNative: true,
|
|
61221
|
+
mode: "singleton",
|
|
61222
|
+
deviceTypes: [
|
|
61223
|
+
DeviceType.Cover,
|
|
61224
|
+
DeviceType.Valve,
|
|
61225
|
+
DeviceType.Lock,
|
|
61226
|
+
DeviceType.MediaPlayer,
|
|
61227
|
+
DeviceType.Climate,
|
|
61228
|
+
DeviceType.Thermostat
|
|
61229
|
+
],
|
|
61230
|
+
methods: {},
|
|
61231
|
+
status: {
|
|
61232
|
+
schema: GroupActiveSchema,
|
|
61233
|
+
kind: "push"
|
|
61234
|
+
},
|
|
61235
|
+
runtimeState: GroupActiveSchema,
|
|
61236
|
+
/**
|
|
61237
|
+
* Runtime-state durability: **session** — recomputed from the members on the
|
|
61238
|
+
* next source event; a restored value would outlive the members it named.
|
|
61239
|
+
*
|
|
61240
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
61241
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
61242
|
+
*/
|
|
61243
|
+
durability: "session",
|
|
61244
|
+
history: "none"
|
|
61245
|
+
};
|
|
61246
|
+
/**
|
|
60381
61247
|
* How many members of a helper group answered (D944). Counts are integers,
|
|
60382
61248
|
* not measurements: zero included is a real count, never a stand-in reading.
|
|
60383
61249
|
* The reading itself stays on the output capability. History is none — the
|
|
@@ -60408,7 +61274,14 @@ var groupStatusCapability = {
|
|
|
60408
61274
|
deviceTypes: [
|
|
60409
61275
|
DeviceType.Sensor,
|
|
60410
61276
|
DeviceType.Switch,
|
|
60411
|
-
DeviceType.Light
|
|
61277
|
+
DeviceType.Light,
|
|
61278
|
+
DeviceType.Fan,
|
|
61279
|
+
DeviceType.Cover,
|
|
61280
|
+
DeviceType.Valve,
|
|
61281
|
+
DeviceType.Lock,
|
|
61282
|
+
DeviceType.MediaPlayer,
|
|
61283
|
+
DeviceType.Thermostat,
|
|
61284
|
+
DeviceType.Climate
|
|
60412
61285
|
],
|
|
60413
61286
|
methods: {},
|
|
60414
61287
|
status: {
|
|
@@ -60556,7 +61429,7 @@ var humidifierCapability = {
|
|
|
60556
61429
|
*/
|
|
60557
61430
|
var HumiditySensorStatusSchema = object({
|
|
60558
61431
|
/** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
|
|
60559
|
-
|
|
61432
|
+
humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
|
|
60560
61433
|
precision: 0,
|
|
60561
61434
|
label: "Humidity"
|
|
60562
61435
|
})),
|
|
@@ -60585,7 +61458,7 @@ var humiditySensorCapability = {
|
|
|
60585
61458
|
/**
|
|
60586
61459
|
* History (D850): recorded series.
|
|
60587
61460
|
*/
|
|
60588
|
-
history: { fields: {
|
|
61461
|
+
history: { fields: { humidity: {
|
|
60589
61462
|
kind: "gauge",
|
|
60590
61463
|
unit: "%",
|
|
60591
61464
|
deadband: .5
|
|
@@ -61225,7 +62098,7 @@ var LawnMowerControlStatusSchema = object({
|
|
|
61225
62098
|
batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
61226
62099
|
/** 0..100 mowing-completion percentage of the current task, or null when no
|
|
61227
62100
|
* task is active / progress is unavailable. */
|
|
61228
|
-
|
|
62101
|
+
progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
61229
62102
|
/** Current device/event code (dynamic — mostly status, sometimes an error),
|
|
61230
62103
|
* or null when unknown. */
|
|
61231
62104
|
currentCode: number().nullable().meta(readingMeta("none")),
|
|
@@ -61681,7 +62554,14 @@ var lockControlCapability = {
|
|
|
61681
62554
|
}),
|
|
61682
62555
|
unlock: method(object({
|
|
61683
62556
|
deviceId: number().int().nonnegative(),
|
|
61684
|
-
code: string$2().min(1).optional()
|
|
62557
|
+
code: string$2().min(1).optional(),
|
|
62558
|
+
/**
|
|
62559
|
+
* Honoured by a helper lock GROUP only (D944): unlocking several doors
|
|
62560
|
+
* with one call is refused unless the caller says it was confirmed. A
|
|
62561
|
+
* single lock ignores it. The refusal lives in the callee, so no
|
|
62562
|
+
* automation or API caller reaches the doors by skipping a dialog.
|
|
62563
|
+
*/
|
|
62564
|
+
confirm: boolean().optional()
|
|
61685
62565
|
}), _void(), {
|
|
61686
62566
|
kind: "mutation",
|
|
61687
62567
|
auth: "protected"
|
|
@@ -61718,6 +62598,44 @@ var lockControlCapability = {
|
|
|
61718
62598
|
volatileStateFields: ["lastChangedAt"]
|
|
61719
62599
|
};
|
|
61720
62600
|
/**
|
|
62601
|
+
* A monotonic total between resets (D944). Energy uses `power-meter.energy`
|
|
62602
|
+
* so dashboards keep one series; every other instantaneous reading lands here.
|
|
62603
|
+
* `value` is the time integral in the source quantity's accumulated base
|
|
62604
|
+
* (base-unit seconds, or kWh on the power meter). `null` is a gap, never zero.
|
|
62605
|
+
*/
|
|
62606
|
+
var MeterStatusSchema = object({
|
|
62607
|
+
value: number().nullable().meta(readingMeta("device", { label: "Total" })),
|
|
62608
|
+
lastFetchedAt: number()
|
|
62609
|
+
});
|
|
62610
|
+
var meterCapability = {
|
|
62611
|
+
name: "meter",
|
|
62612
|
+
scope: "device",
|
|
62613
|
+
deviceNative: true,
|
|
62614
|
+
mode: "singleton",
|
|
62615
|
+
deviceTypes: [DeviceType.Sensor],
|
|
62616
|
+
methods: {},
|
|
62617
|
+
status: {
|
|
62618
|
+
schema: MeterStatusSchema,
|
|
62619
|
+
kind: "push"
|
|
62620
|
+
},
|
|
62621
|
+
runtimeState: MeterStatusSchema,
|
|
62622
|
+
/**
|
|
62623
|
+
* Runtime-state durability: **restored** — the total is a cache of the
|
|
62624
|
+
* history integral. A restart shows it only until the history pass
|
|
62625
|
+
* recomputes the open period; it is not an in-memory buffer.
|
|
62626
|
+
*
|
|
62627
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
62628
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
62629
|
+
*/
|
|
62630
|
+
durability: "restored",
|
|
62631
|
+
history: { fields: { value: {
|
|
62632
|
+
kind: "counter",
|
|
62633
|
+
deadband: .001,
|
|
62634
|
+
minIntervalMs: 6e4
|
|
62635
|
+
} } },
|
|
62636
|
+
volatileStateFields: ["lastFetchedAt"]
|
|
62637
|
+
};
|
|
62638
|
+
/**
|
|
61721
62639
|
* Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
|
|
61722
62640
|
* Apple TV, Spotify, Roku, generic OTT receivers, …) on
|
|
61723
62641
|
* `DeviceType.MediaPlayer`. Disjoint from the camera streaming
|
|
@@ -61774,10 +62692,10 @@ var MediaPlayerStatusSchema = object({
|
|
|
61774
62692
|
currentMedia: MediaInfoSchema.nullable(),
|
|
61775
62693
|
/** Current playback position in ms. Null when not seekable or
|
|
61776
62694
|
* nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
|
|
61777
|
-
|
|
62695
|
+
position: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
|
|
61778
62696
|
/** Total duration of the current media in ms. Null when unknown
|
|
61779
62697
|
* (live stream) or nothing is playing. */
|
|
61780
|
-
|
|
62698
|
+
duration: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
|
|
61781
62699
|
/** Shuffle toggle. Null when no shuffle surface.
|
|
61782
62700
|
* Pair with `DeviceFeature.MediaPlayerShuffle`. */
|
|
61783
62701
|
shuffle: boolean().nullable(),
|
|
@@ -61816,7 +62734,7 @@ var mediaPlayerCapability = {
|
|
|
61816
62734
|
}),
|
|
61817
62735
|
seek: method(object({
|
|
61818
62736
|
deviceId: number().int().nonnegative(),
|
|
61819
|
-
|
|
62737
|
+
position: number().int().nonnegative()
|
|
61820
62738
|
}), _void(), {
|
|
61821
62739
|
kind: "mutation",
|
|
61822
62740
|
auth: "protected"
|
|
@@ -61888,7 +62806,7 @@ var mediaPlayerCapability = {
|
|
|
61888
62806
|
/**
|
|
61889
62807
|
* History (D850): volume (a level an operator or an automation sets; the
|
|
61890
62808
|
* default 10 s floor folds a slider drag into its end value) and mute.
|
|
61891
|
-
* `
|
|
62809
|
+
* `position` is not recorded: it moves continuously while playing.
|
|
61892
62810
|
*/
|
|
61893
62811
|
history: { fields: {
|
|
61894
62812
|
volumeLevel: {
|
|
@@ -62850,9 +63768,9 @@ var NetworkLinkStatusSchema = object({
|
|
|
62850
63768
|
* one whose reading has not landed must not be drawn at 0 %. Consumers
|
|
62851
63769
|
* SKIP a null rather than coerce it.
|
|
62852
63770
|
*/
|
|
62853
|
-
|
|
63771
|
+
signal: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
62854
63772
|
/** Raw received signal strength in dBm, when the firmware reports one. */
|
|
62855
|
-
|
|
63773
|
+
rssi: number().optional().meta(readingMeta("signal-level")),
|
|
62856
63774
|
/** Network name of a wireless link, when the firmware reports it. */
|
|
62857
63775
|
ssid: string$2().optional(),
|
|
62858
63776
|
/** Ms epoch of the last observation. Lets consumers reason about freshness. */
|
|
@@ -62888,7 +63806,7 @@ onStatusChanged: { data: object({
|
|
|
62888
63806
|
kind: "push",
|
|
62889
63807
|
empty: {
|
|
62890
63808
|
type: "unknown",
|
|
62891
|
-
|
|
63809
|
+
signal: null,
|
|
62892
63810
|
lastUpdated: 0
|
|
62893
63811
|
}
|
|
62894
63812
|
},
|
|
@@ -62911,13 +63829,13 @@ onStatusChanged: { data: object({
|
|
|
62911
63829
|
* History (D850): recorded series.
|
|
62912
63830
|
*/
|
|
62913
63831
|
history: { fields: {
|
|
62914
|
-
|
|
63832
|
+
signal: {
|
|
62915
63833
|
kind: "gauge",
|
|
62916
63834
|
unit: "%",
|
|
62917
63835
|
deadband: 3,
|
|
62918
63836
|
minIntervalMs: 6e4
|
|
62919
63837
|
},
|
|
62920
|
-
|
|
63838
|
+
rssi: {
|
|
62921
63839
|
kind: "gauge",
|
|
62922
63840
|
unit: "dBm",
|
|
62923
63841
|
deadband: 2,
|
|
@@ -63662,10 +64580,20 @@ var PetFeederDeviceStatusSchema = _enum([
|
|
|
63662
64580
|
]);
|
|
63663
64581
|
var gramsPortion = number().int().min(4).max(200);
|
|
63664
64582
|
var PetFeederStatusSchema = object({
|
|
63665
|
-
/** Food
|
|
63666
|
-
*
|
|
63667
|
-
* bowl
|
|
64583
|
+
/** Food in the TANK (hopper), as the feeder's own sensor reports it
|
|
64584
|
+
* (`state.food`). A coarse level code, not grams — `0` is empty. The
|
|
64585
|
+
* amount in the bowl is `bowlFood`. On dual-hopper models this is the
|
|
64586
|
+
* combined reading; per-hopper levels live in `food1`/`food2`. Null when
|
|
64587
|
+
* the device has not reported yet. */
|
|
63668
64588
|
foodLevel: number().nullable().meta(readingMeta("none")),
|
|
64589
|
+
/** Food in the BOWL as the feeder reports it (`state.bowl`), in the
|
|
64590
|
+
* feeder's own units. Null when the model reports none. */
|
|
64591
|
+
bowlFood: number().nullable().meta(readingMeta("none")),
|
|
64592
|
+
/** True while the pet is eating (`state.eating`). */
|
|
64593
|
+
eating: boolean(),
|
|
64594
|
+
/** Whether `markFoodReplenished` does anything on this model. When false
|
|
64595
|
+
* the client must not offer it. */
|
|
64596
|
+
supportsMarkRefilled: boolean(),
|
|
63669
64597
|
/** Hopper-1 food level (grams) on dual-hopper feeders; null on
|
|
63670
64598
|
* single-hopper models. */
|
|
63671
64599
|
food1: number().nullable().meta(readingMeta("none")),
|
|
@@ -63673,8 +64601,8 @@ var PetFeederStatusSchema = object({
|
|
|
63673
64601
|
* single-hopper models. */
|
|
63674
64602
|
food2: number().nullable().meta(readingMeta("none")),
|
|
63675
64603
|
/** Derived low-food flag — mirrors the HA `food_level` binary_sensor
|
|
63676
|
-
* (`device_class: problem`, on = low). True when the
|
|
63677
|
-
*
|
|
64604
|
+
* (`device_class: problem`, on = low). True when the tank reports empty,
|
|
64605
|
+
* or, on a dual-hopper model, when EITHER hopper is empty. */
|
|
63678
64606
|
lowFood: boolean(),
|
|
63679
64607
|
/** Battery charge 0..100 (%). Null on mains-powered models or when the
|
|
63680
64608
|
* device has no battery reading. */
|
|
@@ -63742,7 +64670,9 @@ var petFeederCapability = {
|
|
|
63742
64670
|
kind: "mutation",
|
|
63743
64671
|
auth: "protected"
|
|
63744
64672
|
}),
|
|
63745
|
-
/**
|
|
64673
|
+
/** Tell the PetKit cloud a hopper was refilled so it stops its low-food
|
|
64674
|
+
* reminders (D4H/D4S/D4SH only — see `supportsMarkRefilled`). It does not
|
|
64675
|
+
* change any sensor reading. */
|
|
63746
64676
|
markFoodReplenished: method(object({ deviceId: number().int().nonnegative() }), _void(), {
|
|
63747
64677
|
kind: "mutation",
|
|
63748
64678
|
auth: "protected"
|
|
@@ -63815,6 +64745,7 @@ var petFeederCapability = {
|
|
|
63815
64745
|
*/
|
|
63816
64746
|
history: { fields: {
|
|
63817
64747
|
foodLevel: { kind: "gauge" },
|
|
64748
|
+
bowlFood: { kind: "gauge" },
|
|
63818
64749
|
food1: { kind: "gauge" },
|
|
63819
64750
|
food2: { kind: "gauge" },
|
|
63820
64751
|
batteryPower: {
|
|
@@ -64224,27 +65155,89 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
|
|
|
64224
65155
|
* in real-world devices.
|
|
64225
65156
|
*/
|
|
64226
65157
|
var PowerMeterStatusSchema = object({
|
|
64227
|
-
/** Instantaneous power draw
|
|
64228
|
-
|
|
65158
|
+
/** Instantaneous power draw (watts). */
|
|
65159
|
+
power: number().optional().meta(readingMeta("power", { label: "Power" })),
|
|
64229
65160
|
/** Cumulative energy in kilowatt-hours since the meter was reset. */
|
|
64230
|
-
|
|
64231
|
-
/** Voltage
|
|
64232
|
-
|
|
65161
|
+
energy: number().optional().meta(readingMeta("energy", { label: "Energy" })),
|
|
65162
|
+
/** Voltage (volts). */
|
|
65163
|
+
voltage: number().optional().meta(readingMeta("voltage", { label: "Voltage" })),
|
|
64233
65164
|
/** Current in amperes. */
|
|
64234
|
-
|
|
65165
|
+
current: number().optional().meta(readingMeta("current", { label: "Current" })),
|
|
64235
65166
|
/** Apparent power in volt-amperes. */
|
|
64236
|
-
|
|
65167
|
+
apparentPower: number().optional().meta(readingMeta("apparent-power")),
|
|
64237
65168
|
/** Power factor (0..1) if reported. */
|
|
64238
65169
|
powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
|
|
64239
65170
|
/** Ms epoch when the slice was last updated. */
|
|
64240
65171
|
lastFetchedAt: number()
|
|
64241
65172
|
});
|
|
65173
|
+
/**
|
|
65174
|
+
* Loads that draw metered mains power (D951). The reading is a feature of the
|
|
65175
|
+
* device itself — a plug, a lamp, a fan, an air conditioner, a boiler, a
|
|
65176
|
+
* vacuum dock — not a separate sensor beside it. A `Sensor` stays: a clamp
|
|
65177
|
+
* meter or a standalone energy monitor IS the device. Cover, lock, valve,
|
|
65178
|
+
* camera and the like are left out: their draw is not what anyone meters.
|
|
65179
|
+
*/
|
|
65180
|
+
var POWER_METER_DEVICE_TYPES = [
|
|
65181
|
+
DeviceType.Sensor,
|
|
65182
|
+
DeviceType.Switch,
|
|
65183
|
+
DeviceType.Light,
|
|
65184
|
+
DeviceType.Fan,
|
|
65185
|
+
DeviceType.Climate,
|
|
65186
|
+
DeviceType.Thermostat,
|
|
65187
|
+
DeviceType.Humidifier,
|
|
65188
|
+
DeviceType.WaterHeater,
|
|
65189
|
+
DeviceType.MediaPlayer,
|
|
65190
|
+
DeviceType.Washer,
|
|
65191
|
+
DeviceType.Dryer,
|
|
65192
|
+
DeviceType.Dishwasher,
|
|
65193
|
+
DeviceType.Vacuum,
|
|
65194
|
+
DeviceType.LawnMower,
|
|
65195
|
+
DeviceType.PetFeeder
|
|
65196
|
+
];
|
|
65197
|
+
/** Everything but a bare `Sensor`: a sensor that takes its reading from another sensor adds nothing. */
|
|
65198
|
+
var POWER_METER_CUSTOMIZE_DEVICE_TYPES = POWER_METER_DEVICE_TYPES.filter((type) => type !== DeviceType.Sensor);
|
|
65199
|
+
/** One scalar target per reading; each is sourced from a device of its own (D951). */
|
|
65200
|
+
function powerTarget(id, unit) {
|
|
65201
|
+
return {
|
|
65202
|
+
id,
|
|
65203
|
+
cardinality: "scalar",
|
|
65204
|
+
vocabulary: {
|
|
65205
|
+
cap: "power-meter",
|
|
65206
|
+
stateField: id
|
|
65207
|
+
},
|
|
65208
|
+
deviceTypes: POWER_METER_CUSTOMIZE_DEVICE_TYPES,
|
|
65209
|
+
apply: "add",
|
|
65210
|
+
path: id,
|
|
65211
|
+
sources: [{
|
|
65212
|
+
kind: "numeric",
|
|
65213
|
+
requiredUnit: unit,
|
|
65214
|
+
transforms: [{ kind: "identity" }]
|
|
65215
|
+
}],
|
|
65216
|
+
companions: [{
|
|
65217
|
+
kind: "clock",
|
|
65218
|
+
path: "lastFetchedAt",
|
|
65219
|
+
when: "always"
|
|
65220
|
+
}],
|
|
65221
|
+
sourceReport: { status: "assignment-status" },
|
|
65222
|
+
advancedExpression: true
|
|
65223
|
+
};
|
|
65224
|
+
}
|
|
65225
|
+
/** "Power and energy" extension: power, energy, voltage and current from other devices (D951). */
|
|
65226
|
+
var powerMeterCustomize = {
|
|
65227
|
+
version: 1,
|
|
65228
|
+
targets: [
|
|
65229
|
+
powerTarget("power", "W"),
|
|
65230
|
+
powerTarget("energy", "kWh"),
|
|
65231
|
+
powerTarget("voltage", "V"),
|
|
65232
|
+
powerTarget("current", "A")
|
|
65233
|
+
]
|
|
65234
|
+
};
|
|
64242
65235
|
var powerMeterCapability = {
|
|
64243
65236
|
name: "power-meter",
|
|
64244
65237
|
scope: "device",
|
|
64245
65238
|
deviceNative: true,
|
|
64246
65239
|
mode: "singleton",
|
|
64247
|
-
deviceTypes:
|
|
65240
|
+
deviceTypes: POWER_METER_DEVICE_TYPES,
|
|
64248
65241
|
methods: {},
|
|
64249
65242
|
status: {
|
|
64250
65243
|
schema: PowerMeterStatusSchema,
|
|
@@ -64252,41 +65245,41 @@ var powerMeterCapability = {
|
|
|
64252
65245
|
},
|
|
64253
65246
|
runtimeState: PowerMeterStatusSchema,
|
|
64254
65247
|
/**
|
|
64255
|
-
* Runtime-state durability: **restored** — as `numeric-sensor`; `
|
|
65248
|
+
* Runtime-state durability: **restored** — as `numeric-sensor`; `energy` is an accumulator whose restored value is the baseline.
|
|
64256
65249
|
*
|
|
64257
65250
|
* See `RuntimeStateDurability`. Enforced by
|
|
64258
65251
|
* `scripts/check-runtime-state-durability.ts`.
|
|
64259
65252
|
*/
|
|
64260
65253
|
durability: "restored",
|
|
64261
65254
|
/**
|
|
64262
|
-
* History (D850): recorded series. Gauges are floored at 30 s; `
|
|
65255
|
+
* History (D850): recorded series. Gauges are floored at 30 s; `energy` is the one monotonic meter (counter).
|
|
64263
65256
|
*/
|
|
64264
65257
|
history: { fields: {
|
|
64265
|
-
|
|
65258
|
+
power: {
|
|
64266
65259
|
kind: "gauge",
|
|
64267
65260
|
unit: "W",
|
|
64268
65261
|
deadband: 1,
|
|
64269
65262
|
minIntervalMs: 3e4
|
|
64270
65263
|
},
|
|
64271
|
-
|
|
65264
|
+
energy: {
|
|
64272
65265
|
kind: "counter",
|
|
64273
65266
|
unit: "kWh",
|
|
64274
65267
|
deadband: .01,
|
|
64275
65268
|
minIntervalMs: 6e4
|
|
64276
65269
|
},
|
|
64277
|
-
|
|
65270
|
+
voltage: {
|
|
64278
65271
|
kind: "gauge",
|
|
64279
65272
|
unit: "V",
|
|
64280
65273
|
deadband: 1,
|
|
64281
65274
|
minIntervalMs: 3e4
|
|
64282
65275
|
},
|
|
64283
|
-
|
|
65276
|
+
current: {
|
|
64284
65277
|
kind: "gauge",
|
|
64285
65278
|
unit: "A",
|
|
64286
65279
|
deadband: .05,
|
|
64287
65280
|
minIntervalMs: 3e4
|
|
64288
65281
|
},
|
|
64289
|
-
|
|
65282
|
+
apparentPower: {
|
|
64290
65283
|
kind: "gauge",
|
|
64291
65284
|
unit: "VA",
|
|
64292
65285
|
deadband: 1,
|
|
@@ -64301,7 +65294,8 @@ var powerMeterCapability = {
|
|
|
64301
65294
|
} },
|
|
64302
65295
|
/** Clock fields: written, but excluded from the compare that decides
|
|
64303
65296
|
* whether persisting is worth a SQLite commit. */
|
|
64304
|
-
volatileStateFields: ["lastFetchedAt"]
|
|
65297
|
+
volatileStateFields: ["lastFetchedAt"],
|
|
65298
|
+
customize: powerMeterCustomize
|
|
64305
65299
|
};
|
|
64306
65300
|
/**
|
|
64307
65301
|
* Presence cap. Models HA `person.*` and `device_tracker.*` entities
|
|
@@ -64335,9 +65329,6 @@ var PresenceStatusSchema = object({
|
|
|
64335
65329
|
location: string$2().nullable(),
|
|
64336
65330
|
/** GPS coordinates when available. Null otherwise. */
|
|
64337
65331
|
gps: GpsLocationSchema.nullable(),
|
|
64338
|
-
/** Optional battery level of the tracking device (0..100). Null
|
|
64339
|
-
* when not reported. */
|
|
64340
|
-
batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
64341
65332
|
/** Ms epoch when the slice was last updated. */
|
|
64342
65333
|
lastChangedAt: number()
|
|
64343
65334
|
});
|
|
@@ -64366,14 +65357,7 @@ var presenceCapability = {
|
|
|
64366
65357
|
* `scripts/check-runtime-state-durability.ts`.
|
|
64367
65358
|
*/
|
|
64368
65359
|
durability: "restored",
|
|
64369
|
-
|
|
64370
|
-
* History (D850): recorded series.
|
|
64371
|
-
*/
|
|
64372
|
-
history: { fields: { batteryPercent: {
|
|
64373
|
-
kind: "gauge",
|
|
64374
|
-
unit: "%",
|
|
64375
|
-
deadband: 1
|
|
64376
|
-
} } },
|
|
65360
|
+
history: "none",
|
|
64377
65361
|
/** Clock fields: written, but excluded from the compare that decides
|
|
64378
65362
|
* whether persisting is worth a SQLite commit. */
|
|
64379
65363
|
volatileStateFields: ["lastChangedAt"]
|
|
@@ -64388,7 +65372,7 @@ var presenceCapability = {
|
|
|
64388
65372
|
*/
|
|
64389
65373
|
var PressureSensorStatusSchema = object({
|
|
64390
65374
|
/** Current pressure in hPa. `null` when the sample was refused (D393). */
|
|
64391
|
-
|
|
65375
|
+
pressure: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
|
|
64392
65376
|
/** Ms epoch when the slice was last updated. */
|
|
64393
65377
|
lastFetchedAt: number()
|
|
64394
65378
|
});
|
|
@@ -64414,7 +65398,7 @@ var pressureSensorCapability = {
|
|
|
64414
65398
|
/**
|
|
64415
65399
|
* History (D850): recorded series.
|
|
64416
65400
|
*/
|
|
64417
|
-
history: { fields: {
|
|
65401
|
+
history: { fields: { pressure: {
|
|
64418
65402
|
kind: "gauge",
|
|
64419
65403
|
unit: "hPa",
|
|
64420
65404
|
deadband: .1
|
|
@@ -64982,6 +65966,8 @@ var runningCustomize = {
|
|
|
64982
65966
|
stateField: "running"
|
|
64983
65967
|
},
|
|
64984
65968
|
deviceTypes: [
|
|
65969
|
+
DeviceType.Washer,
|
|
65970
|
+
DeviceType.Dryer,
|
|
64985
65971
|
DeviceType.Dishwasher,
|
|
64986
65972
|
DeviceType.Vacuum,
|
|
64987
65973
|
DeviceType.LawnMower,
|
|
@@ -67471,7 +68457,8 @@ var switchCapability = {
|
|
|
67471
68457
|
deviceTypes: [
|
|
67472
68458
|
DeviceType.Switch,
|
|
67473
68459
|
DeviceType.Siren,
|
|
67474
|
-
DeviceType.Light
|
|
68460
|
+
DeviceType.Light,
|
|
68461
|
+
DeviceType.Fan
|
|
67475
68462
|
],
|
|
67476
68463
|
methods: { setState: method(object({
|
|
67477
68464
|
deviceId: number(),
|
|
@@ -68110,7 +69097,7 @@ var tamperCapability = {
|
|
|
68110
69097
|
*/
|
|
68111
69098
|
var TemperatureSensorStatusSchema = object({
|
|
68112
69099
|
/** Current temperature in Celsius. `null` when the sample was refused (D393). */
|
|
68113
|
-
|
|
69100
|
+
temperature: number().nullable().meta(readingMeta("temperature", {
|
|
68114
69101
|
precision: 1,
|
|
68115
69102
|
label: "Temperature"
|
|
68116
69103
|
})),
|
|
@@ -68139,7 +69126,7 @@ var temperatureSensorCapability = {
|
|
|
68139
69126
|
/**
|
|
68140
69127
|
* History (D850): recorded series.
|
|
68141
69128
|
*/
|
|
68142
|
-
history: { fields: {
|
|
69129
|
+
history: { fields: { temperature: {
|
|
68143
69130
|
kind: "gauge",
|
|
68144
69131
|
unit: "°C",
|
|
68145
69132
|
deadband: .1
|
|
@@ -68436,7 +69423,7 @@ var VacuumControlStatusSchema = object({
|
|
|
68436
69423
|
/** Speed tokens the hardware accepts — drives the UI selector. */
|
|
68437
69424
|
availableFanSpeeds: array(string$2()),
|
|
68438
69425
|
/** 0..100 cleaning-completion percentage of the current task, or null. */
|
|
68439
|
-
|
|
69426
|
+
progress: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
68440
69427
|
/** Current error code (0 / null = no error). */
|
|
68441
69428
|
errorCode: number().nullable().meta(readingMeta("none")),
|
|
68442
69429
|
/** Human label for {@link errorCode}, or null when none / undecodable. */
|
|
@@ -69684,8 +70671,9 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
|
|
|
69684
70671
|
kind: "mutation",
|
|
69685
70672
|
auth: "admin"
|
|
69686
70673
|
});
|
|
70674
|
+
DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
|
|
69687
70675
|
SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
|
|
69688
|
-
DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Camera, DeviceType.Camera, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
|
|
70676
|
+
DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
|
|
69689
70677
|
String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
|
|
69690
70678
|
DeviceRole.BinarySensor, DeviceRole.MotionSensor;
|
|
69691
70679
|
new Set(CAP_NAMES);
|
|
@@ -70014,6 +71002,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
|
|
|
70014
71002
|
featureProbe: featureProbeCapability,
|
|
70015
71003
|
flood: floodCapability,
|
|
70016
71004
|
gas: gasCapability,
|
|
71005
|
+
groupActive: groupActiveCapability,
|
|
70017
71006
|
groupCommands: groupCommandsCapability,
|
|
70018
71007
|
groupStatus: groupStatusCapability,
|
|
70019
71008
|
heatingValve: heatingValveCapability,
|
|
@@ -70025,6 +71014,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
|
|
|
70025
71014
|
lawnMowerControl: lawnMowerControlCapability,
|
|
70026
71015
|
lockControl: lockControlCapability,
|
|
70027
71016
|
mediaPlayer: mediaPlayerCapability,
|
|
71017
|
+
meter: meterCapability,
|
|
70028
71018
|
motion: motionCapability,
|
|
70029
71019
|
motionTrigger: motionTriggerCapability,
|
|
70030
71020
|
motionZones: motionZonesCapability,
|
|
@@ -70087,7 +71077,7 @@ var BaseDevice = class {
|
|
|
70087
71077
|
* `device-local-state.ts`. Drivers access via:
|
|
70088
71078
|
*
|
|
70089
71079
|
* `this.state.battery.sleeping = true` // patches the battery slice
|
|
70090
|
-
* `const pct = this.state.battery.
|
|
71080
|
+
* `const pct = this.state.battery.level` // reads the battery slice
|
|
70091
71081
|
* `this.state.deviceStatus.online = true` // mirrors `markOnline(true)`
|
|
70092
71082
|
*
|
|
70093
71083
|
* Adding a new cap with `runtimeState:` automatically extends this
|
|
@@ -70127,7 +71117,7 @@ var BaseDevice = class {
|
|
|
70127
71117
|
* shape; drivers don't redeclare it, they just write through.
|
|
70128
71118
|
*
|
|
70129
71119
|
* Read: `this.runtimeState.getCapState('battery')` →
|
|
70130
|
-
* `{
|
|
71120
|
+
* `{level, charging, sleeping, lastUpdated}` for any
|
|
70131
71121
|
* provider that registers `batteryCapability`.
|
|
70132
71122
|
* Write: `this.runtimeState.setCapState('battery', { … })`.
|
|
70133
71123
|
*
|
|
@@ -71284,7 +72274,7 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
71284
72274
|
return toDeviceSummary(device, this.addonId);
|
|
71285
72275
|
}
|
|
71286
72276
|
};
|
|
71287
|
-
DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher;
|
|
72277
|
+
DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
|
|
71288
72278
|
new Set(Object.values(DeviceType));
|
|
71289
72279
|
Object.freeze({
|
|
71290
72280
|
"accessories.getStatus": {
|
|
@@ -71301,6 +72291,104 @@ Object.freeze({
|
|
|
71301
72291
|
access: "create",
|
|
71302
72292
|
role: "admin"
|
|
71303
72293
|
},
|
|
72294
|
+
"actions.create": {
|
|
72295
|
+
capName: "actions",
|
|
72296
|
+
capScope: "system",
|
|
72297
|
+
addonId: null,
|
|
72298
|
+
access: "create",
|
|
72299
|
+
role: "admin"
|
|
72300
|
+
},
|
|
72301
|
+
"actions.delete": {
|
|
72302
|
+
capName: "actions",
|
|
72303
|
+
capScope: "system",
|
|
72304
|
+
addonId: null,
|
|
72305
|
+
access: "delete",
|
|
72306
|
+
role: "admin"
|
|
72307
|
+
},
|
|
72308
|
+
"actions.get": {
|
|
72309
|
+
capName: "actions",
|
|
72310
|
+
capScope: "system",
|
|
72311
|
+
addonId: null,
|
|
72312
|
+
access: "view",
|
|
72313
|
+
role: "viewer"
|
|
72314
|
+
},
|
|
72315
|
+
"actions.getLinks": {
|
|
72316
|
+
capName: "actions",
|
|
72317
|
+
capScope: "system",
|
|
72318
|
+
addonId: null,
|
|
72319
|
+
access: "view",
|
|
72320
|
+
role: "admin"
|
|
72321
|
+
},
|
|
72322
|
+
"actions.getTraces": {
|
|
72323
|
+
capName: "actions",
|
|
72324
|
+
capScope: "system",
|
|
72325
|
+
addonId: null,
|
|
72326
|
+
access: "view",
|
|
72327
|
+
role: "admin"
|
|
72328
|
+
},
|
|
72329
|
+
"actions.list": {
|
|
72330
|
+
capName: "actions",
|
|
72331
|
+
capScope: "system",
|
|
72332
|
+
addonId: null,
|
|
72333
|
+
access: "view",
|
|
72334
|
+
role: "admin"
|
|
72335
|
+
},
|
|
72336
|
+
"actions.listForCamera": {
|
|
72337
|
+
capName: "actions",
|
|
72338
|
+
capScope: "system",
|
|
72339
|
+
addonId: null,
|
|
72340
|
+
access: "view",
|
|
72341
|
+
role: "viewer"
|
|
72342
|
+
},
|
|
72343
|
+
"actions.run": {
|
|
72344
|
+
capName: "actions",
|
|
72345
|
+
capScope: "system",
|
|
72346
|
+
addonId: null,
|
|
72347
|
+
access: "create",
|
|
72348
|
+
role: "viewer"
|
|
72349
|
+
},
|
|
72350
|
+
"actions.setLinks": {
|
|
72351
|
+
capName: "actions",
|
|
72352
|
+
capScope: "system",
|
|
72353
|
+
addonId: null,
|
|
72354
|
+
access: "create",
|
|
72355
|
+
role: "admin"
|
|
72356
|
+
},
|
|
72357
|
+
"actions.stop": {
|
|
72358
|
+
capName: "actions",
|
|
72359
|
+
capScope: "system",
|
|
72360
|
+
addonId: null,
|
|
72361
|
+
access: "create",
|
|
72362
|
+
role: "admin"
|
|
72363
|
+
},
|
|
72364
|
+
"actions.update": {
|
|
72365
|
+
capName: "actions",
|
|
72366
|
+
capScope: "system",
|
|
72367
|
+
addonId: null,
|
|
72368
|
+
access: "create",
|
|
72369
|
+
role: "admin"
|
|
72370
|
+
},
|
|
72371
|
+
"actionSource.listActions": {
|
|
72372
|
+
capName: "action-source",
|
|
72373
|
+
capScope: "system",
|
|
72374
|
+
addonId: null,
|
|
72375
|
+
access: "view",
|
|
72376
|
+
role: "admin"
|
|
72377
|
+
},
|
|
72378
|
+
"actionSource.runAction": {
|
|
72379
|
+
capName: "action-source",
|
|
72380
|
+
capScope: "system",
|
|
72381
|
+
addonId: null,
|
|
72382
|
+
access: "create",
|
|
72383
|
+
role: "admin"
|
|
72384
|
+
},
|
|
72385
|
+
"actionSource.stopAction": {
|
|
72386
|
+
capName: "action-source",
|
|
72387
|
+
capScope: "system",
|
|
72388
|
+
addonId: null,
|
|
72389
|
+
access: "create",
|
|
72390
|
+
role: "admin"
|
|
72391
|
+
},
|
|
71304
72392
|
"addonPages.listPages": {
|
|
71305
72393
|
capName: "addon-pages",
|
|
71306
72394
|
capScope: "system",
|
|
@@ -72582,6 +73670,13 @@ Object.freeze({
|
|
|
72582
73670
|
access: "view",
|
|
72583
73671
|
role: "admin"
|
|
72584
73672
|
},
|
|
73673
|
+
"coreBlocks.getAutomationRunState": {
|
|
73674
|
+
capName: "core-blocks",
|
|
73675
|
+
capScope: "system",
|
|
73676
|
+
addonId: null,
|
|
73677
|
+
access: "view",
|
|
73678
|
+
role: "admin"
|
|
73679
|
+
},
|
|
72585
73680
|
"coreBlocks.getBlockSettings": {
|
|
72586
73681
|
capName: "core-blocks",
|
|
72587
73682
|
capScope: "system",
|
|
@@ -72603,6 +73698,13 @@ Object.freeze({
|
|
|
72603
73698
|
access: "view",
|
|
72604
73699
|
role: "admin"
|
|
72605
73700
|
},
|
|
73701
|
+
"coreBlocks.getTraces": {
|
|
73702
|
+
capName: "core-blocks",
|
|
73703
|
+
capScope: "system",
|
|
73704
|
+
addonId: null,
|
|
73705
|
+
access: "view",
|
|
73706
|
+
role: "admin"
|
|
73707
|
+
},
|
|
72606
73708
|
"coreBlocks.getTypeDefs": {
|
|
72607
73709
|
capName: "core-blocks",
|
|
72608
73710
|
capScope: "system",
|
|
@@ -72638,6 +73740,13 @@ Object.freeze({
|
|
|
72638
73740
|
access: "create",
|
|
72639
73741
|
role: "admin"
|
|
72640
73742
|
},
|
|
73743
|
+
"coreBlocks.reportAutomationRun": {
|
|
73744
|
+
capName: "core-blocks",
|
|
73745
|
+
capScope: "system",
|
|
73746
|
+
addonId: null,
|
|
73747
|
+
access: "create",
|
|
73748
|
+
role: "admin"
|
|
73749
|
+
},
|
|
72641
73750
|
"coreBlocks.restart": {
|
|
72642
73751
|
capName: "core-blocks",
|
|
72643
73752
|
capScope: "system",
|
|
@@ -74507,6 +75616,13 @@ Object.freeze({
|
|
|
74507
75616
|
access: "view",
|
|
74508
75617
|
role: "viewer"
|
|
74509
75618
|
},
|
|
75619
|
+
"groupActive.getStatus": {
|
|
75620
|
+
capName: "group-active",
|
|
75621
|
+
capScope: "device",
|
|
75622
|
+
addonId: null,
|
|
75623
|
+
access: "view",
|
|
75624
|
+
role: "viewer"
|
|
75625
|
+
},
|
|
74510
75626
|
"groupCommands.getStatus": {
|
|
74511
75627
|
capName: "group-commands",
|
|
74512
75628
|
capScope: "device",
|
|
@@ -75473,6 +76589,13 @@ Object.freeze({
|
|
|
75473
76589
|
access: "create",
|
|
75474
76590
|
role: "admin"
|
|
75475
76591
|
},
|
|
76592
|
+
"meter.getStatus": {
|
|
76593
|
+
capName: "meter",
|
|
76594
|
+
capScope: "device",
|
|
76595
|
+
addonId: null,
|
|
76596
|
+
access: "view",
|
|
76597
|
+
role: "viewer"
|
|
76598
|
+
},
|
|
75476
76599
|
"metricsProvider.collectSnapshot": {
|
|
75477
76600
|
capName: "metrics-provider",
|
|
75478
76601
|
capScope: "system",
|
|
@@ -81197,7 +82320,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
81197
82320
|
label: t("Full brightness", "Luminosità massima"),
|
|
81198
82321
|
phrase: t("Set {device} to full brightness", "Porta {device} alla massima luminosità"),
|
|
81199
82322
|
icon: "sun",
|
|
81200
|
-
args: {
|
|
82323
|
+
args: { level: 100 }
|
|
81201
82324
|
},
|
|
81202
82325
|
{
|
|
81203
82326
|
id: "brightness.half",
|
|
@@ -81205,7 +82328,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
81205
82328
|
label: t("Half brightness", "Luminosità a metà"),
|
|
81206
82329
|
phrase: t("Dim {device} to half", "Abbassa {device} a metà luminosità"),
|
|
81207
82330
|
icon: "sun-dim",
|
|
81208
|
-
args: {
|
|
82331
|
+
args: { level: 50 }
|
|
81209
82332
|
},
|
|
81210
82333
|
{
|
|
81211
82334
|
id: "brightness.off",
|
|
@@ -81213,16 +82336,16 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
81213
82336
|
label: t("Brightness off", "Luminosità a zero"),
|
|
81214
82337
|
phrase: t("Dim {device} to zero", "Abbassa {device} a zero"),
|
|
81215
82338
|
icon: "lightbulb-off",
|
|
81216
|
-
args: {
|
|
82339
|
+
args: { level: 0 }
|
|
81217
82340
|
},
|
|
81218
82341
|
{
|
|
81219
82342
|
id: "brightness.set",
|
|
81220
82343
|
method: "setBrightness",
|
|
81221
82344
|
label: t("Set brightness", "Imposta la luminosità"),
|
|
81222
|
-
phrase: t("Set the brightness of {device} to {
|
|
82345
|
+
phrase: t("Set the brightness of {device} to {level}", "Imposta la luminosità di {device} a {level}"),
|
|
81223
82346
|
icon: "sun-medium",
|
|
81224
82347
|
openArgs: [{
|
|
81225
|
-
key: "
|
|
82348
|
+
key: "level",
|
|
81226
82349
|
label: PERCENT,
|
|
81227
82350
|
offer: {
|
|
81228
82351
|
kind: "number",
|
|
@@ -81236,13 +82359,13 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
81236
82359
|
],
|
|
81237
82360
|
state: [{
|
|
81238
82361
|
kind: "numeric",
|
|
81239
|
-
field: "
|
|
82362
|
+
field: "level",
|
|
81240
82363
|
quantity: t("the brightness", "la luminosità"),
|
|
81241
82364
|
label: t("Brightness", "Luminosità"),
|
|
81242
82365
|
unit: "%",
|
|
81243
82366
|
icon: "sun"
|
|
81244
82367
|
}],
|
|
81245
|
-
events: { onBrightnessChanged: { notOperatorFacing: "the `
|
|
82368
|
+
events: { onBrightnessChanged: { notOperatorFacing: "the `level` state trigger reads the same change" } }
|
|
81246
82369
|
},
|
|
81247
82370
|
color: {
|
|
81248
82371
|
scope: "device",
|
|
@@ -81256,7 +82379,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
81256
82379
|
icon: "sun",
|
|
81257
82380
|
args: { color: {
|
|
81258
82381
|
mode: "mired",
|
|
81259
|
-
|
|
82382
|
+
colorTemperature: 370
|
|
81260
82383
|
} }
|
|
81261
82384
|
},
|
|
81262
82385
|
{
|
|
@@ -81267,7 +82390,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
81267
82390
|
icon: "snowflake",
|
|
81268
82391
|
args: { color: {
|
|
81269
82392
|
mode: "mired",
|
|
81270
|
-
|
|
82393
|
+
colorTemperature: 200
|
|
81271
82394
|
} }
|
|
81272
82395
|
},
|
|
81273
82396
|
{
|
|
@@ -81881,7 +83004,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
81881
83004
|
label: t("Fan off", "Ventilatore spento"),
|
|
81882
83005
|
phrase: t("Stop {device}", "Ferma {device}"),
|
|
81883
83006
|
icon: "power-off",
|
|
81884
|
-
args: {
|
|
83007
|
+
args: { speed: 0 }
|
|
81885
83008
|
},
|
|
81886
83009
|
{
|
|
81887
83010
|
id: "fan.full",
|
|
@@ -81889,16 +83012,16 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
81889
83012
|
label: t("Fan full", "Ventilatore al massimo"),
|
|
81890
83013
|
phrase: t("Run {device} at full speed", "Porta {device} alla massima velocità"),
|
|
81891
83014
|
icon: "fan",
|
|
81892
|
-
args: {
|
|
83015
|
+
args: { speed: 100 }
|
|
81893
83016
|
},
|
|
81894
83017
|
{
|
|
81895
83018
|
id: "fan.speed",
|
|
81896
83019
|
method: "setPercentage",
|
|
81897
83020
|
label: t("Set speed", "Imposta la velocità"),
|
|
81898
|
-
phrase: t("Set the speed of {device} to {
|
|
83021
|
+
phrase: t("Set the speed of {device} to {speed}", "Imposta la velocità di {device} al {speed}"),
|
|
81899
83022
|
icon: "fan",
|
|
81900
83023
|
openArgs: [{
|
|
81901
|
-
key: "
|
|
83024
|
+
key: "speed",
|
|
81902
83025
|
label: t("Speed", "Velocità"),
|
|
81903
83026
|
offer: {
|
|
81904
83027
|
kind: "number",
|
|
@@ -81960,7 +83083,7 @@ var ACTUATOR_CAP_VOCABULARY = {
|
|
|
81960
83083
|
],
|
|
81961
83084
|
state: [{
|
|
81962
83085
|
kind: "numeric",
|
|
81963
|
-
field: "
|
|
83086
|
+
field: "speed",
|
|
81964
83087
|
quantity: t("the speed", "la velocità"),
|
|
81965
83088
|
label: t("Speed", "Velocità"),
|
|
81966
83089
|
unit: "%",
|
|
@@ -82758,7 +83881,7 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
82758
83881
|
},
|
|
82759
83882
|
{
|
|
82760
83883
|
kind: "numeric",
|
|
82761
|
-
field: "
|
|
83884
|
+
field: "progress",
|
|
82762
83885
|
quantity: t("the cycle progress", "l’avanzamento del lavaggio"),
|
|
82763
83886
|
label: t("Progress", "Avanzamento"),
|
|
82764
83887
|
unit: "%",
|
|
@@ -83116,10 +84239,10 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
83116
84239
|
id: "media.seek",
|
|
83117
84240
|
method: "seek",
|
|
83118
84241
|
label: t("Seek", "Vai al punto"),
|
|
83119
|
-
phrase: t("Move {device} to {
|
|
84242
|
+
phrase: t("Move {device} to {position}", "Porta {device} al punto {position}"),
|
|
83120
84243
|
icon: "fast-forward",
|
|
83121
84244
|
openArgs: [{
|
|
83122
|
-
key: "
|
|
84245
|
+
key: "position",
|
|
83123
84246
|
label: t("Position", "Posizione"),
|
|
83124
84247
|
offer: {
|
|
83125
84248
|
kind: "number",
|
|
@@ -83571,7 +84694,39 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
83571
84694
|
phrase: t("When {device} finishes", "Quando {device} finisce"),
|
|
83572
84695
|
icon: "square"
|
|
83573
84696
|
}
|
|
83574
|
-
}
|
|
84697
|
+
},
|
|
84698
|
+
overrides: { byType: {
|
|
84699
|
+
[DeviceType.Washer]: {
|
|
84700
|
+
state: { running: {
|
|
84701
|
+
whenTrue: t("When the washing machine starts", "Quando la lavatrice inizia"),
|
|
84702
|
+
whenFalse: t("When the washing machine has finished", "Quando la lavatrice ha finito")
|
|
84703
|
+
} },
|
|
84704
|
+
events: {
|
|
84705
|
+
onStarted: { phrase: t("When the washing machine starts", "Quando la lavatrice inizia") },
|
|
84706
|
+
onFinished: { phrase: t("When the washing machine has finished", "Quando la lavatrice ha finito") }
|
|
84707
|
+
}
|
|
84708
|
+
},
|
|
84709
|
+
[DeviceType.Dryer]: {
|
|
84710
|
+
state: { running: {
|
|
84711
|
+
whenTrue: t("When the dryer starts", "Quando l'asciugatrice inizia"),
|
|
84712
|
+
whenFalse: t("When the dryer has finished", "Quando l'asciugatrice ha finito")
|
|
84713
|
+
} },
|
|
84714
|
+
events: {
|
|
84715
|
+
onStarted: { phrase: t("When the dryer starts", "Quando l'asciugatrice inizia") },
|
|
84716
|
+
onFinished: { phrase: t("When the dryer has finished", "Quando l'asciugatrice ha finito") }
|
|
84717
|
+
}
|
|
84718
|
+
},
|
|
84719
|
+
[DeviceType.Dishwasher]: {
|
|
84720
|
+
state: { running: {
|
|
84721
|
+
whenTrue: t("When the dishwasher starts", "Quando la lavastoviglie inizia"),
|
|
84722
|
+
whenFalse: t("When the dishwasher has finished", "Quando la lavastoviglie ha finito")
|
|
84723
|
+
} },
|
|
84724
|
+
events: {
|
|
84725
|
+
onStarted: { phrase: t("When the dishwasher starts", "Quando la lavastoviglie inizia") },
|
|
84726
|
+
onFinished: { phrase: t("When the dishwasher has finished", "Quando la lavastoviglie ha finito") }
|
|
84727
|
+
}
|
|
84728
|
+
}
|
|
84729
|
+
} }
|
|
83575
84730
|
},
|
|
83576
84731
|
update: {
|
|
83577
84732
|
scope: "device",
|
|
@@ -84136,7 +85291,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
84136
85291
|
state: [
|
|
84137
85292
|
{
|
|
84138
85293
|
kind: "numeric",
|
|
84139
|
-
field: "
|
|
85294
|
+
field: "co2",
|
|
84140
85295
|
quantity: t("the CO₂", "la CO₂"),
|
|
84141
85296
|
label: t("CO₂", "CO₂"),
|
|
84142
85297
|
unit: "ppm",
|
|
@@ -84144,7 +85299,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
84144
85299
|
},
|
|
84145
85300
|
{
|
|
84146
85301
|
kind: "numeric",
|
|
84147
|
-
field: "
|
|
85302
|
+
field: "voc",
|
|
84148
85303
|
quantity: t("the VOC level", "il livello di COV"),
|
|
84149
85304
|
label: t("VOC", "COV"),
|
|
84150
85305
|
unit: "ppb",
|
|
@@ -84180,7 +85335,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
84180
85335
|
noun: t("Light level", "Luminosità ambiente"),
|
|
84181
85336
|
state: [{
|
|
84182
85337
|
kind: "numeric",
|
|
84183
|
-
field: "
|
|
85338
|
+
field: "illuminance",
|
|
84184
85339
|
quantity: t("the light level", "la luminosità"),
|
|
84185
85340
|
label: t("Illuminance", "Illuminamento"),
|
|
84186
85341
|
unit: "lx",
|
|
@@ -84343,6 +85498,10 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
84343
85498
|
icon: "flame"
|
|
84344
85499
|
}]
|
|
84345
85500
|
},
|
|
85501
|
+
"group-active": {
|
|
85502
|
+
scope: "device",
|
|
85503
|
+
noun: t("Group state", "Stato del gruppo")
|
|
85504
|
+
},
|
|
84346
85505
|
"group-commands": {
|
|
84347
85506
|
scope: "device",
|
|
84348
85507
|
noun: t("Group command", "Comando di gruppo")
|
|
@@ -84379,7 +85538,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
84379
85538
|
noun: t("Humidity", "Umidità"),
|
|
84380
85539
|
state: [{
|
|
84381
85540
|
kind: "numeric",
|
|
84382
|
-
field: "
|
|
85541
|
+
field: "humidity",
|
|
84383
85542
|
quantity: t("the humidity", "l’umidità"),
|
|
84384
85543
|
label: t("Humidity", "Umidità"),
|
|
84385
85544
|
unit: "%",
|
|
@@ -84399,6 +85558,17 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
84399
85558
|
}],
|
|
84400
85559
|
events: { onMotionChanged: { notOperatorFacing: "the `detected` state trigger phrases both edges, from the mirror that survives a dropped event (D49)" } }
|
|
84401
85560
|
},
|
|
85561
|
+
meter: {
|
|
85562
|
+
scope: "device",
|
|
85563
|
+
noun: t("Meter", "Contatore"),
|
|
85564
|
+
state: [{
|
|
85565
|
+
kind: "numeric",
|
|
85566
|
+
field: "value",
|
|
85567
|
+
quantity: t("the total", "il totale"),
|
|
85568
|
+
label: t("Total", "Totale"),
|
|
85569
|
+
icon: "gauge"
|
|
85570
|
+
}]
|
|
85571
|
+
},
|
|
84402
85572
|
"numeric-sensor": {
|
|
84403
85573
|
scope: "device",
|
|
84404
85574
|
noun: t("Sensor", "Sensore"),
|
|
@@ -84416,7 +85586,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
84416
85586
|
state: [
|
|
84417
85587
|
{
|
|
84418
85588
|
kind: "numeric",
|
|
84419
|
-
field: "
|
|
85589
|
+
field: "power",
|
|
84420
85590
|
quantity: t("the power draw", "il consumo"),
|
|
84421
85591
|
label: t("Power", "Potenza"),
|
|
84422
85592
|
unit: "W",
|
|
@@ -84424,7 +85594,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
84424
85594
|
},
|
|
84425
85595
|
{
|
|
84426
85596
|
kind: "numeric",
|
|
84427
|
-
field: "
|
|
85597
|
+
field: "energy",
|
|
84428
85598
|
quantity: t("the energy used", "l’energia consumata"),
|
|
84429
85599
|
label: t("Energy", "Energia"),
|
|
84430
85600
|
unit: "kWh",
|
|
@@ -84432,7 +85602,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
84432
85602
|
},
|
|
84433
85603
|
{
|
|
84434
85604
|
kind: "numeric",
|
|
84435
|
-
field: "
|
|
85605
|
+
field: "voltage",
|
|
84436
85606
|
quantity: t("the voltage", "la tensione"),
|
|
84437
85607
|
label: t("Voltage", "Tensione"),
|
|
84438
85608
|
unit: "V",
|
|
@@ -84440,7 +85610,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
84440
85610
|
},
|
|
84441
85611
|
{
|
|
84442
85612
|
kind: "numeric",
|
|
84443
|
-
field: "
|
|
85613
|
+
field: "current",
|
|
84444
85614
|
quantity: t("the current", "la corrente"),
|
|
84445
85615
|
label: t("Current", "Corrente"),
|
|
84446
85616
|
unit: "A",
|
|
@@ -84457,13 +85627,6 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
84457
85627
|
label: t("Where", "Posizione"),
|
|
84458
85628
|
phrase: t("When {device} is at {value}", "Quando {device} è in {value}"),
|
|
84459
85629
|
icon: "map-pin"
|
|
84460
|
-
}, {
|
|
84461
|
-
kind: "numeric",
|
|
84462
|
-
field: "batteryPercent",
|
|
84463
|
-
quantity: t("the phone battery", "la batteria del telefono"),
|
|
84464
|
-
label: t("Battery", "Batteria"),
|
|
84465
|
-
unit: "%",
|
|
84466
|
-
icon: "battery"
|
|
84467
85630
|
}]
|
|
84468
85631
|
},
|
|
84469
85632
|
"pressure-sensor": {
|
|
@@ -84471,7 +85634,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
84471
85634
|
noun: t("Pressure", "Pressione"),
|
|
84472
85635
|
state: [{
|
|
84473
85636
|
kind: "numeric",
|
|
84474
|
-
field: "
|
|
85637
|
+
field: "pressure",
|
|
84475
85638
|
quantity: t("the pressure", "la pressione"),
|
|
84476
85639
|
label: t("Pressure", "Pressione"),
|
|
84477
85640
|
unit: "hPa",
|
|
@@ -84507,7 +85670,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
84507
85670
|
noun: t("Temperature", "Temperatura"),
|
|
84508
85671
|
state: [{
|
|
84509
85672
|
kind: "numeric",
|
|
84510
|
-
field: "
|
|
85673
|
+
field: "temperature",
|
|
84511
85674
|
quantity: t("the temperature", "la temperatura"),
|
|
84512
85675
|
label: t("Temperature", "Temperatura"),
|
|
84513
85676
|
unit: "°C",
|
|
@@ -84578,7 +85741,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
84578
85741
|
state: [
|
|
84579
85742
|
{
|
|
84580
85743
|
kind: "numeric",
|
|
84581
|
-
field: "
|
|
85744
|
+
field: "level",
|
|
84582
85745
|
quantity: t("the battery level", "il livello della batteria"),
|
|
84583
85746
|
label: t("Battery", "Batteria"),
|
|
84584
85747
|
unit: "%",
|
|
@@ -84620,7 +85783,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
84620
85783
|
icon: "battery-charging"
|
|
84621
85784
|
}
|
|
84622
85785
|
],
|
|
84623
|
-
events: { onStatusChanged: { notOperatorFacing: "the
|
|
85786
|
+
events: { onStatusChanged: { notOperatorFacing: "the level / sleeping / charging state triggers each read one field of this change" } },
|
|
84624
85787
|
extraEvents: [{
|
|
84625
85788
|
category: EventCategory.DeviceAwake,
|
|
84626
85789
|
label: t("Woke up", "Risvegliato"),
|
|
@@ -84638,7 +85801,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
84638
85801
|
noun: t("Network link", "Collegamento di rete"),
|
|
84639
85802
|
state: [{
|
|
84640
85803
|
kind: "numeric",
|
|
84641
|
-
field: "
|
|
85804
|
+
field: "signal",
|
|
84642
85805
|
quantity: t("the signal strength", "l’intensità del segnale"),
|
|
84643
85806
|
label: t("Signal", "Segnale"),
|
|
84644
85807
|
unit: "%",
|
|
@@ -84731,6 +85894,8 @@ var ML = "model and training machinery — no device action";
|
|
|
84731
85894
|
var INTERNAL$1 = "internal plumbing between addons — never operator-facing";
|
|
84732
85895
|
/** Every system-scoped cap, with why it is not a device's vocabulary. */
|
|
84733
85896
|
var SYSTEM_CAP_VOCABULARY = {
|
|
85897
|
+
"action-source": system(INTERNAL$1),
|
|
85898
|
+
actions: system("declared actions — run from the Actions page and from the cameras that link them"),
|
|
84734
85899
|
"addon-pages": system(UI_PLUMBING),
|
|
84735
85900
|
"addon-pages-source": system(UI_PLUMBING),
|
|
84736
85901
|
"addon-routes": system(UI_PLUMBING),
|
|
@@ -84871,8 +86036,47 @@ function projectAction(cap, action) {
|
|
|
84871
86036
|
};
|
|
84872
86037
|
}
|
|
84873
86038
|
Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
|
|
84874
|
-
DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher;
|
|
86039
|
+
DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
|
|
84875
86040
|
DeviceFeature.BatteryOperated;
|
|
86041
|
+
DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
|
|
86042
|
+
var HA_AS = "a power sensor (device_class power). Home Assistant has no laundry domain. running-state stays unexported until a null reading can be published as unavailable.";
|
|
86043
|
+
var HOMEKIT_REASON = "HomeKit has no laundry-appliance service. The export tab would pair an accessory with nothing on it: running-state is withheld until a null reading can be unavailable, and power is not a HomeKit service this export honours.";
|
|
86044
|
+
var ALEXA_REASON = "Alexa has no laundry category in this export, and neither power nor running has an Alexa interface.";
|
|
86045
|
+
var GOOGLE_REASON = "Google WASHER, DRYER and DISHWASHER require RunCycle. A power threshold is not that trait, and this export does not publish RunCycle.";
|
|
86046
|
+
var MATTER_REASON = "No Matter export is built. The closest device types are Laundry Washer (0x0073) and Laundry Dryer (0x0074); a power threshold is not the Laundry Washer Mode cluster.";
|
|
86047
|
+
function withheld(reason) {
|
|
86048
|
+
return {
|
|
86049
|
+
[DeviceType.Washer]: {
|
|
86050
|
+
exported: false,
|
|
86051
|
+
reason
|
|
86052
|
+
},
|
|
86053
|
+
[DeviceType.Dryer]: {
|
|
86054
|
+
exported: false,
|
|
86055
|
+
reason
|
|
86056
|
+
},
|
|
86057
|
+
[DeviceType.Dishwasher]: {
|
|
86058
|
+
exported: false,
|
|
86059
|
+
reason
|
|
86060
|
+
}
|
|
86061
|
+
};
|
|
86062
|
+
}
|
|
86063
|
+
function offered(received) {
|
|
86064
|
+
return {
|
|
86065
|
+
[DeviceType.Washer]: {
|
|
86066
|
+
exported: true,
|
|
86067
|
+
as: received
|
|
86068
|
+
},
|
|
86069
|
+
[DeviceType.Dryer]: {
|
|
86070
|
+
exported: true,
|
|
86071
|
+
as: received
|
|
86072
|
+
},
|
|
86073
|
+
[DeviceType.Dishwasher]: {
|
|
86074
|
+
exported: true,
|
|
86075
|
+
as: received
|
|
86076
|
+
}
|
|
86077
|
+
};
|
|
86078
|
+
}
|
|
86079
|
+
offered(HA_AS), withheld(HOMEKIT_REASON), withheld(ALEXA_REASON), withheld(GOOGLE_REASON), withheld(MATTER_REASON);
|
|
84876
86080
|
Object.freeze({
|
|
84877
86081
|
"accessories.getStatus": [{
|
|
84878
86082
|
name: "deviceId",
|
|
@@ -84888,6 +86092,31 @@ Object.freeze({
|
|
|
84888
86092
|
form: "single",
|
|
84889
86093
|
optional: false
|
|
84890
86094
|
}],
|
|
86095
|
+
"actions.get": [{
|
|
86096
|
+
name: "deviceId",
|
|
86097
|
+
form: "single",
|
|
86098
|
+
optional: true
|
|
86099
|
+
}],
|
|
86100
|
+
"actions.getLinks": [{
|
|
86101
|
+
name: "deviceId",
|
|
86102
|
+
form: "single",
|
|
86103
|
+
optional: false
|
|
86104
|
+
}],
|
|
86105
|
+
"actions.listForCamera": [{
|
|
86106
|
+
name: "deviceId",
|
|
86107
|
+
form: "single",
|
|
86108
|
+
optional: false
|
|
86109
|
+
}],
|
|
86110
|
+
"actions.run": [{
|
|
86111
|
+
name: "deviceId",
|
|
86112
|
+
form: "single",
|
|
86113
|
+
optional: true
|
|
86114
|
+
}],
|
|
86115
|
+
"actions.setLinks": [{
|
|
86116
|
+
name: "deviceId",
|
|
86117
|
+
form: "single",
|
|
86118
|
+
optional: false
|
|
86119
|
+
}],
|
|
84891
86120
|
"addonSettings.getDeviceSettings": [{
|
|
84892
86121
|
name: "deviceId",
|
|
84893
86122
|
form: "single",
|
|
@@ -85839,6 +87068,11 @@ Object.freeze({
|
|
|
85839
87068
|
form: "single",
|
|
85840
87069
|
optional: false
|
|
85841
87070
|
}],
|
|
87071
|
+
"groupActive.getStatus": [{
|
|
87072
|
+
name: "deviceId",
|
|
87073
|
+
form: "single",
|
|
87074
|
+
optional: false
|
|
87075
|
+
}],
|
|
85842
87076
|
"groupCommands.getStatus": [{
|
|
85843
87077
|
name: "deviceId",
|
|
85844
87078
|
form: "single",
|
|
@@ -86049,6 +87283,11 @@ Object.freeze({
|
|
|
86049
87283
|
form: "single",
|
|
86050
87284
|
optional: false
|
|
86051
87285
|
}],
|
|
87286
|
+
"meter.getStatus": [{
|
|
87287
|
+
name: "deviceId",
|
|
87288
|
+
form: "single",
|
|
87289
|
+
optional: false
|
|
87290
|
+
}],
|
|
86052
87291
|
"motion.getStatus": [{
|
|
86053
87292
|
name: "deviceId",
|
|
86054
87293
|
form: "single",
|
|
@@ -87656,7 +88895,6 @@ Object.freeze({
|
|
|
87656
88895
|
"network-access": "ingress",
|
|
87657
88896
|
"smtp-provider": "email"
|
|
87658
88897
|
});
|
|
87659
|
-
DeviceType.Camera;
|
|
87660
88898
|
/**
|
|
87661
88899
|
* A saved Customize assignment (D936). The schema is the type: callers use
|
|
87662
88900
|
* `z.infer`, they do not restate the shape.
|
|
@@ -88960,6 +90198,7 @@ DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
|
|
|
88960
90198
|
DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
|
|
88961
90199
|
DEFAULT_NATIVE_LEASE_SETTINGS.sceneBudgetMb;
|
|
88962
90200
|
DEFAULT_NATIVE_LEASE_SETTINGS.admission;
|
|
90201
|
+
String.raw`(?<=^|["'(\s>,\[{])`;
|
|
88963
90202
|
var MB = 1024 * 1024;
|
|
88964
90203
|
1024 * MB, 3072 * MB;
|
|
88965
90204
|
//#endregion
|
|
@@ -197743,9 +198982,9 @@ function decodeMatterAttribute(input) {
|
|
|
197743
198982
|
case MatterClusterId.LevelControl: return decodeLevel(attributeId, value);
|
|
197744
198983
|
case MatterClusterId.BooleanState: return decodeContact(attributeId, value);
|
|
197745
198984
|
case MatterClusterId.OccupancySensing: return decodeOccupancy(attributeId, value);
|
|
197746
|
-
case MatterClusterId.TemperatureMeasurement: return decodeScaledMeasurement(attributeId, value, "temperature-sensor", "
|
|
197747
|
-
case MatterClusterId.RelativeHumidityMeasurement: return decodeScaledMeasurement(attributeId, value, "humidity-sensor", "
|
|
197748
|
-
case MatterClusterId.PressureMeasurement: return decodeScaledMeasurement(attributeId, value, "pressure-sensor", "
|
|
198985
|
+
case MatterClusterId.TemperatureMeasurement: return decodeScaledMeasurement(attributeId, value, "temperature-sensor", "temperature", .01);
|
|
198986
|
+
case MatterClusterId.RelativeHumidityMeasurement: return decodeScaledMeasurement(attributeId, value, "humidity-sensor", "humidity", .01);
|
|
198987
|
+
case MatterClusterId.PressureMeasurement: return decodeScaledMeasurement(attributeId, value, "pressure-sensor", "pressure", 1);
|
|
197749
198988
|
case MatterClusterId.IlluminanceMeasurement: return decodeIlluminance(attributeId, value);
|
|
197750
198989
|
case MatterClusterId.PowerSource: return decodeBattery(attributeId, value);
|
|
197751
198990
|
case MatterClusterId.Thermostat: return decodeThermostat(attributeId, value);
|
|
@@ -197840,7 +199079,7 @@ function decodeLevel(attributeId, value) {
|
|
|
197840
199079
|
return {
|
|
197841
199080
|
cap: "brightness",
|
|
197842
199081
|
patch: {
|
|
197843
|
-
|
|
199082
|
+
level: Math.round(Math.max(0, Math.min(254, level)) / 254 * 100),
|
|
197844
199083
|
lastChangedAt: Date.now()
|
|
197845
199084
|
}
|
|
197846
199085
|
};
|
|
@@ -197889,7 +199128,7 @@ function decodeIlluminance(attributeId, value) {
|
|
|
197889
199128
|
return {
|
|
197890
199129
|
cap: "ambient-light-sensor",
|
|
197891
199130
|
patch: {
|
|
197892
|
-
|
|
199131
|
+
illuminance: Math.round(10 ** ((raw - 1) / 1e4)),
|
|
197893
199132
|
lastFetchedAt: Date.now()
|
|
197894
199133
|
}
|
|
197895
199134
|
};
|
|
@@ -197901,7 +199140,7 @@ function decodeBattery(attributeId, value) {
|
|
|
197901
199140
|
return {
|
|
197902
199141
|
cap: "battery",
|
|
197903
199142
|
patch: {
|
|
197904
|
-
|
|
199143
|
+
level: Math.max(0, Math.min(100, Math.round(half / 2))),
|
|
197905
199144
|
lastUpdated: Date.now()
|
|
197906
199145
|
}
|
|
197907
199146
|
};
|
|
@@ -197964,7 +199203,7 @@ function decodeElectricalPower(attributeId, value) {
|
|
|
197964
199203
|
return mw === null ? null : {
|
|
197965
199204
|
cap: "power-meter",
|
|
197966
199205
|
patch: {
|
|
197967
|
-
|
|
199206
|
+
power: mw / 1e3,
|
|
197968
199207
|
lastFetchedAt: Date.now()
|
|
197969
199208
|
}
|
|
197970
199209
|
};
|
|
@@ -197974,7 +199213,7 @@ function decodeElectricalPower(attributeId, value) {
|
|
|
197974
199213
|
return mv === null ? null : {
|
|
197975
199214
|
cap: "power-meter",
|
|
197976
199215
|
patch: {
|
|
197977
|
-
|
|
199216
|
+
voltage: mv / 1e3,
|
|
197978
199217
|
lastFetchedAt: Date.now()
|
|
197979
199218
|
}
|
|
197980
199219
|
};
|
|
@@ -197984,7 +199223,7 @@ function decodeElectricalPower(attributeId, value) {
|
|
|
197984
199223
|
return ma === null ? null : {
|
|
197985
199224
|
cap: "power-meter",
|
|
197986
199225
|
patch: {
|
|
197987
|
-
|
|
199226
|
+
current: ma / 1e3,
|
|
197988
199227
|
lastFetchedAt: Date.now()
|
|
197989
199228
|
}
|
|
197990
199229
|
};
|
|
@@ -197999,7 +199238,7 @@ function decodeElectricalEnergy(attributeId, value) {
|
|
|
197999
199238
|
return {
|
|
198000
199239
|
cap: "power-meter",
|
|
198001
199240
|
patch: {
|
|
198002
|
-
|
|
199241
|
+
energy: mwh / 1e6,
|
|
198003
199242
|
lastFetchedAt: Date.now()
|
|
198004
199243
|
}
|
|
198005
199244
|
};
|
|
@@ -200223,16 +201462,16 @@ var MatterEndpointDevice = class extends BaseDevice {
|
|
|
200223
201462
|
}
|
|
200224
201463
|
registerBrightness() {
|
|
200225
201464
|
const COLD = {
|
|
200226
|
-
|
|
201465
|
+
level: 0,
|
|
200227
201466
|
lastChangedAt: 0
|
|
200228
201467
|
};
|
|
200229
201468
|
this.ctx.registerNativeCap(brightnessCapability, {
|
|
200230
201469
|
getStatus: async () => this.runtimeState.getCapState("brightness") ?? COLD,
|
|
200231
|
-
setBrightness: async ({ percentage }) => {
|
|
201470
|
+
setBrightness: async ({ level: percentage }) => {
|
|
200232
201471
|
const level = Math.round(Math.max(0, Math.min(100, percentage)) / 100 * 254);
|
|
200233
201472
|
await this.invoke(MatterClusterId.LevelControl, "moveToLevel", { level });
|
|
200234
201473
|
this.runtimeState.setCapState("brightness", {
|
|
200235
|
-
percentage,
|
|
201474
|
+
level: percentage,
|
|
200236
201475
|
lastChangedAt: Date.now()
|
|
200237
201476
|
});
|
|
200238
201477
|
}
|
|
@@ -200443,7 +201682,7 @@ var MatterEndpointDevice = class extends BaseDevice {
|
|
|
200443
201682
|
}
|
|
200444
201683
|
registerTemperatureSensor() {
|
|
200445
201684
|
const COLD = {
|
|
200446
|
-
|
|
201685
|
+
temperature: 0,
|
|
200447
201686
|
lastFetchedAt: 0
|
|
200448
201687
|
};
|
|
200449
201688
|
this.ctx.registerNativeCap(temperatureSensorCapability, { getStatus: async () => this.runtimeState.getCapState("temperature-sensor") ?? COLD });
|
|
@@ -200451,7 +201690,7 @@ var MatterEndpointDevice = class extends BaseDevice {
|
|
|
200451
201690
|
}
|
|
200452
201691
|
registerHumiditySensor() {
|
|
200453
201692
|
const COLD = {
|
|
200454
|
-
|
|
201693
|
+
humidity: 0,
|
|
200455
201694
|
lastFetchedAt: 0
|
|
200456
201695
|
};
|
|
200457
201696
|
this.ctx.registerNativeCap(humiditySensorCapability, { getStatus: async () => this.runtimeState.getCapState("humidity-sensor") ?? COLD });
|
|
@@ -200459,7 +201698,7 @@ var MatterEndpointDevice = class extends BaseDevice {
|
|
|
200459
201698
|
}
|
|
200460
201699
|
registerPressureSensor() {
|
|
200461
201700
|
const COLD = {
|
|
200462
|
-
|
|
201701
|
+
pressure: 0,
|
|
200463
201702
|
lastFetchedAt: 0
|
|
200464
201703
|
};
|
|
200465
201704
|
this.ctx.registerNativeCap(pressureSensorCapability, { getStatus: async () => this.runtimeState.getCapState("pressure-sensor") ?? COLD });
|
|
@@ -200467,7 +201706,7 @@ var MatterEndpointDevice = class extends BaseDevice {
|
|
|
200467
201706
|
}
|
|
200468
201707
|
registerAmbientLightSensor() {
|
|
200469
201708
|
const COLD = {
|
|
200470
|
-
|
|
201709
|
+
illuminance: 0,
|
|
200471
201710
|
lastFetchedAt: 0
|
|
200472
201711
|
};
|
|
200473
201712
|
this.ctx.registerNativeCap(ambientLightSensorCapability, { getStatus: async () => this.runtimeState.getCapState("ambient-light-sensor") ?? COLD });
|
|
@@ -200475,7 +201714,7 @@ var MatterEndpointDevice = class extends BaseDevice {
|
|
|
200475
201714
|
}
|
|
200476
201715
|
registerBattery() {
|
|
200477
201716
|
const COLD = {
|
|
200478
|
-
|
|
201717
|
+
level: 0,
|
|
200479
201718
|
charging: "unknown",
|
|
200480
201719
|
sleeping: false,
|
|
200481
201720
|
lastUpdated: 0
|