@camstack/addon-provider-ecowitt 0.2.146 → 0.2.148
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +18 -0
- package/dist/addon.js +1594 -557
- package/dist/addon.mjs +1594 -557
- package/package.json +4 -2
package/dist/addon.js
CHANGED
|
@@ -2,7 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
let http = require("http");
|
|
3
3
|
let events = require("events");
|
|
4
4
|
let dgram = require("dgram");
|
|
5
|
-
//#region ../types/dist/event-category-
|
|
5
|
+
//#region ../types/dist/event-category-CT-hz5Rb.mjs
|
|
6
6
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
7
7
|
EventCategory["SystemBoot"] = "system.boot";
|
|
8
8
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -328,6 +328,14 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
328
328
|
/** The recovery edge of {@link PipelineMotionNoisy}: the motion fell back, or a detection arrived. */
|
|
329
329
|
EventCategory["PipelineMotionCalm"] = "pipeline.motion-calm";
|
|
330
330
|
/**
|
|
331
|
+
* A camera's package LLM confirm has failed closed three times in a row
|
|
332
|
+
* (D931, 2026-10-26). One edge per camera until
|
|
333
|
+
* {@link PipelinePackageLlmAnswered}.
|
|
334
|
+
*/
|
|
335
|
+
EventCategory["PipelinePackageLlmSilent"] = "pipeline.package-llm-silent";
|
|
336
|
+
/** The recovery edge of {@link PipelinePackageLlmSilent}: a confirm returned yes or no. */
|
|
337
|
+
EventCategory["PipelinePackageLlmAnswered"] = "pipeline.package-llm-answered";
|
|
338
|
+
/**
|
|
331
339
|
* Per-camera pipeline config was mutated by the orchestrator
|
|
332
340
|
* (a settings change via `setAgentInferenceDevices` /
|
|
333
341
|
* `setCameraStepToggle` / `setCameraStepSettings`). Orchestrator
|
|
@@ -5437,7 +5445,7 @@ var ZodIssueCode$1 = {
|
|
|
5437
5445
|
var ZodFirstPartyTypeKind$1;
|
|
5438
5446
|
ZodFirstPartyTypeKind$1 || (ZodFirstPartyTypeKind$1 = {});
|
|
5439
5447
|
//#endregion
|
|
5440
|
-
//#region ../types/dist/sleep-
|
|
5448
|
+
//#region ../types/dist/sleep-bykKrTgu.mjs
|
|
5441
5449
|
/**
|
|
5442
5450
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5443
5451
|
* window to float samples (D455).
|
|
@@ -7991,6 +7999,99 @@ var RecordKindDeclarationSchema = object({
|
|
|
7991
7999
|
}).strict();
|
|
7992
8000
|
/** The kind names: lower-case, dash separated, short enough for an index name. */
|
|
7993
8001
|
var RecordKindNameSchema = string().regex(/^[a-z][a-z0-9-]{0,47}$/);
|
|
8002
|
+
var READING_QUANTITIES = [
|
|
8003
|
+
"temperature",
|
|
8004
|
+
"pressure",
|
|
8005
|
+
"speed",
|
|
8006
|
+
"precipitation",
|
|
8007
|
+
"precipitation-rate",
|
|
8008
|
+
"length",
|
|
8009
|
+
"area",
|
|
8010
|
+
"illuminance",
|
|
8011
|
+
"irradiance",
|
|
8012
|
+
"power",
|
|
8013
|
+
"energy",
|
|
8014
|
+
"voltage",
|
|
8015
|
+
"current",
|
|
8016
|
+
"apparent-power",
|
|
8017
|
+
"humidity",
|
|
8018
|
+
"percentage",
|
|
8019
|
+
"concentration-volume",
|
|
8020
|
+
"concentration-mass",
|
|
8021
|
+
"concentration-ppb",
|
|
8022
|
+
"volume",
|
|
8023
|
+
"duration",
|
|
8024
|
+
"duration-ms",
|
|
8025
|
+
"information",
|
|
8026
|
+
"sound-level",
|
|
8027
|
+
"signal-level",
|
|
8028
|
+
"colour-temperature",
|
|
8029
|
+
"angle",
|
|
8030
|
+
"bitrate",
|
|
8031
|
+
"rate",
|
|
8032
|
+
"mass",
|
|
8033
|
+
"index",
|
|
8034
|
+
"ratio",
|
|
8035
|
+
"count",
|
|
8036
|
+
"none",
|
|
8037
|
+
"device"
|
|
8038
|
+
];
|
|
8039
|
+
var ReadingQuantitySchema = _enum(READING_QUANTITIES);
|
|
8040
|
+
new Set(READING_QUANTITIES);
|
|
8041
|
+
/**
|
|
8042
|
+
* Stored unit symbol for a quantity. `none` means the number is dimensionless
|
|
8043
|
+
* (or an index / ratio / count). `device` means the cap has no single unit.
|
|
8044
|
+
*/
|
|
8045
|
+
var QUANTITY_BASE = {
|
|
8046
|
+
temperature: "°C",
|
|
8047
|
+
pressure: "hPa",
|
|
8048
|
+
speed: "m/s",
|
|
8049
|
+
precipitation: "mm",
|
|
8050
|
+
"precipitation-rate": "mm/h",
|
|
8051
|
+
length: "m",
|
|
8052
|
+
area: "m²",
|
|
8053
|
+
illuminance: "lx",
|
|
8054
|
+
irradiance: "W/m²",
|
|
8055
|
+
power: "W",
|
|
8056
|
+
energy: "kWh",
|
|
8057
|
+
voltage: "V",
|
|
8058
|
+
current: "A",
|
|
8059
|
+
"apparent-power": "VA",
|
|
8060
|
+
humidity: "%",
|
|
8061
|
+
percentage: "%",
|
|
8062
|
+
"concentration-volume": "ppm",
|
|
8063
|
+
"concentration-mass": "µg/m³",
|
|
8064
|
+
"concentration-ppb": "ppb",
|
|
8065
|
+
volume: "L",
|
|
8066
|
+
duration: "s",
|
|
8067
|
+
"duration-ms": "ms",
|
|
8068
|
+
information: "B",
|
|
8069
|
+
"sound-level": "dBFS",
|
|
8070
|
+
"signal-level": "dBm",
|
|
8071
|
+
"colour-temperature": "mired",
|
|
8072
|
+
angle: "°",
|
|
8073
|
+
bitrate: "kbps",
|
|
8074
|
+
rate: "fps",
|
|
8075
|
+
mass: "kg",
|
|
8076
|
+
index: "none",
|
|
8077
|
+
ratio: "none",
|
|
8078
|
+
count: "none",
|
|
8079
|
+
none: "none",
|
|
8080
|
+
device: "device"
|
|
8081
|
+
};
|
|
8082
|
+
/** The stored unit, or `undefined` when the quantity has no unit symbol. */
|
|
8083
|
+
function baseUnitOf(quantity) {
|
|
8084
|
+
const symbol = QUANTITY_BASE[quantity];
|
|
8085
|
+
if (symbol === "none" || symbol === "device") return void 0;
|
|
8086
|
+
return symbol;
|
|
8087
|
+
}
|
|
8088
|
+
function readingMeta(quantity, extras) {
|
|
8089
|
+
return {
|
|
8090
|
+
quantity,
|
|
8091
|
+
...extras?.precision !== void 0 ? { precision: extras.precision } : {},
|
|
8092
|
+
...extras?.label !== void 0 ? { label: extras.label } : {}
|
|
8093
|
+
};
|
|
8094
|
+
}
|
|
7994
8095
|
/** Shared Zod schemas used across streaming capabilities. */
|
|
7995
8096
|
var CamProfileSchema = _enum([
|
|
7996
8097
|
"high",
|
|
@@ -8018,8 +8119,8 @@ var CamStreamKindSchema = _enum([
|
|
|
8018
8119
|
"derived"
|
|
8019
8120
|
]);
|
|
8020
8121
|
var CamStreamResolutionSchema = object({
|
|
8021
|
-
width: number().int().positive(),
|
|
8022
|
-
height: number().int().positive()
|
|
8122
|
+
width: number().int().positive().meta(readingMeta("none")),
|
|
8123
|
+
height: number().int().positive().meta(readingMeta("none"))
|
|
8023
8124
|
});
|
|
8024
8125
|
var CameraStreamSchema = object({
|
|
8025
8126
|
/** Stable, provider-assigned id unique within the (deviceId) scope. */
|
|
@@ -8537,6 +8638,8 @@ var CAP_NAMES = Object.freeze([
|
|
|
8537
8638
|
"filesystem-browse",
|
|
8538
8639
|
"flood",
|
|
8539
8640
|
"gas",
|
|
8641
|
+
"group-commands",
|
|
8642
|
+
"group-status",
|
|
8540
8643
|
"heating-valve",
|
|
8541
8644
|
"humidifier",
|
|
8542
8645
|
"humidity-sensor",
|
|
@@ -8666,14 +8769,6 @@ Object.freeze({
|
|
|
8666
8769
|
{
|
|
8667
8770
|
"path": "aqi",
|
|
8668
8771
|
"valueType": "number"
|
|
8669
|
-
},
|
|
8670
|
-
{
|
|
8671
|
-
"path": "unit",
|
|
8672
|
-
"valueType": "string"
|
|
8673
|
-
},
|
|
8674
|
-
{
|
|
8675
|
-
"path": "precision",
|
|
8676
|
-
"valueType": "number"
|
|
8677
8772
|
}
|
|
8678
8773
|
],
|
|
8679
8774
|
"alarm-panel": [
|
|
@@ -8709,20 +8804,10 @@ Object.freeze({
|
|
|
8709
8804
|
"valueType": "boolean"
|
|
8710
8805
|
}
|
|
8711
8806
|
],
|
|
8712
|
-
"ambient-light-sensor": [
|
|
8713
|
-
|
|
8714
|
-
|
|
8715
|
-
|
|
8716
|
-
},
|
|
8717
|
-
{
|
|
8718
|
-
"path": "unit",
|
|
8719
|
-
"valueType": "string"
|
|
8720
|
-
},
|
|
8721
|
-
{
|
|
8722
|
-
"path": "precision",
|
|
8723
|
-
"valueType": "number"
|
|
8724
|
-
}
|
|
8725
|
-
],
|
|
8807
|
+
"ambient-light-sensor": [{
|
|
8808
|
+
"path": "lux",
|
|
8809
|
+
"valueType": "number"
|
|
8810
|
+
}],
|
|
8726
8811
|
"audio-metrics": [
|
|
8727
8812
|
{
|
|
8728
8813
|
"path": "ts",
|
|
@@ -8802,6 +8887,10 @@ Object.freeze({
|
|
|
8802
8887
|
"path": "lastUpdated",
|
|
8803
8888
|
"valueType": "number"
|
|
8804
8889
|
},
|
|
8890
|
+
{
|
|
8891
|
+
"path": "volts",
|
|
8892
|
+
"valueType": "number"
|
|
8893
|
+
},
|
|
8805
8894
|
{
|
|
8806
8895
|
"path": "lastContactAt",
|
|
8807
8896
|
"valueType": "number"
|
|
@@ -9044,10 +9133,6 @@ Object.freeze({
|
|
|
9044
9133
|
"datetime"
|
|
9045
9134
|
]
|
|
9046
9135
|
},
|
|
9047
|
-
{
|
|
9048
|
-
"path": "unit",
|
|
9049
|
-
"valueType": "string"
|
|
9050
|
-
},
|
|
9051
9136
|
{
|
|
9052
9137
|
"path": "min",
|
|
9053
9138
|
"valueType": "number"
|
|
@@ -9059,19 +9144,6 @@ Object.freeze({
|
|
|
9059
9144
|
{
|
|
9060
9145
|
"path": "step",
|
|
9061
9146
|
"valueType": "number"
|
|
9062
|
-
},
|
|
9063
|
-
{
|
|
9064
|
-
"path": "precision",
|
|
9065
|
-
"valueType": "number"
|
|
9066
|
-
},
|
|
9067
|
-
{
|
|
9068
|
-
"path": "format",
|
|
9069
|
-
"valueType": "enum",
|
|
9070
|
-
"enumValues": [
|
|
9071
|
-
"date",
|
|
9072
|
-
"time",
|
|
9073
|
-
"datetime"
|
|
9074
|
-
]
|
|
9075
9147
|
}
|
|
9076
9148
|
],
|
|
9077
9149
|
"cover": [
|
|
@@ -9312,13 +9384,8 @@ Object.freeze({
|
|
|
9312
9384
|
"path": "value",
|
|
9313
9385
|
"valueType": "string"
|
|
9314
9386
|
}, {
|
|
9315
|
-
"path": "
|
|
9316
|
-
"valueType": "
|
|
9317
|
-
"enumValues": [
|
|
9318
|
-
"date",
|
|
9319
|
-
"time",
|
|
9320
|
-
"datetime"
|
|
9321
|
-
]
|
|
9387
|
+
"path": "timestamp",
|
|
9388
|
+
"valueType": "boolean"
|
|
9322
9389
|
}],
|
|
9323
9390
|
"event-emitter": [
|
|
9324
9391
|
{
|
|
@@ -9391,6 +9458,20 @@ Object.freeze({
|
|
|
9391
9458
|
"path": "detected",
|
|
9392
9459
|
"valueType": "boolean"
|
|
9393
9460
|
}],
|
|
9461
|
+
"group-status": [
|
|
9462
|
+
{
|
|
9463
|
+
"path": "included",
|
|
9464
|
+
"valueType": "number"
|
|
9465
|
+
},
|
|
9466
|
+
{
|
|
9467
|
+
"path": "excluded",
|
|
9468
|
+
"valueType": "number"
|
|
9469
|
+
},
|
|
9470
|
+
{
|
|
9471
|
+
"path": "total",
|
|
9472
|
+
"valueType": "number"
|
|
9473
|
+
}
|
|
9474
|
+
],
|
|
9394
9475
|
"heating-valve": [{
|
|
9395
9476
|
"path": "position",
|
|
9396
9477
|
"valueType": "number"
|
|
@@ -9425,20 +9506,10 @@ Object.freeze({
|
|
|
9425
9506
|
"valueType": "number"
|
|
9426
9507
|
}
|
|
9427
9508
|
],
|
|
9428
|
-
"humidity-sensor": [
|
|
9429
|
-
|
|
9430
|
-
|
|
9431
|
-
|
|
9432
|
-
},
|
|
9433
|
-
{
|
|
9434
|
-
"path": "unit",
|
|
9435
|
-
"valueType": "string"
|
|
9436
|
-
},
|
|
9437
|
-
{
|
|
9438
|
-
"path": "precision",
|
|
9439
|
-
"valueType": "number"
|
|
9440
|
-
}
|
|
9441
|
-
],
|
|
9509
|
+
"humidity-sensor": [{
|
|
9510
|
+
"path": "percent",
|
|
9511
|
+
"valueType": "number"
|
|
9512
|
+
}],
|
|
9442
9513
|
"image": [{
|
|
9443
9514
|
"path": "url",
|
|
9444
9515
|
"valueType": "string"
|
|
@@ -9777,20 +9848,10 @@ Object.freeze({
|
|
|
9777
9848
|
"valueType": "boolean"
|
|
9778
9849
|
}
|
|
9779
9850
|
],
|
|
9780
|
-
"numeric-sensor": [
|
|
9781
|
-
|
|
9782
|
-
|
|
9783
|
-
|
|
9784
|
-
},
|
|
9785
|
-
{
|
|
9786
|
-
"path": "unit",
|
|
9787
|
-
"valueType": "string"
|
|
9788
|
-
},
|
|
9789
|
-
{
|
|
9790
|
-
"path": "precision",
|
|
9791
|
-
"valueType": "number"
|
|
9792
|
-
}
|
|
9793
|
-
],
|
|
9851
|
+
"numeric-sensor": [{
|
|
9852
|
+
"path": "value",
|
|
9853
|
+
"valueType": "number"
|
|
9854
|
+
}],
|
|
9794
9855
|
"pet-feeder": [
|
|
9795
9856
|
{
|
|
9796
9857
|
"path": "foodLevel",
|
|
@@ -9882,14 +9943,6 @@ Object.freeze({
|
|
|
9882
9943
|
{
|
|
9883
9944
|
"path": "powerFactor",
|
|
9884
9945
|
"valueType": "number"
|
|
9885
|
-
},
|
|
9886
|
-
{
|
|
9887
|
-
"path": "unit",
|
|
9888
|
-
"valueType": "string"
|
|
9889
|
-
},
|
|
9890
|
-
{
|
|
9891
|
-
"path": "precision",
|
|
9892
|
-
"valueType": "number"
|
|
9893
9946
|
}
|
|
9894
9947
|
],
|
|
9895
9948
|
"presence": [
|
|
@@ -9918,20 +9971,10 @@ Object.freeze({
|
|
|
9918
9971
|
"valueType": "number"
|
|
9919
9972
|
}
|
|
9920
9973
|
],
|
|
9921
|
-
"pressure-sensor": [
|
|
9922
|
-
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
},
|
|
9926
|
-
{
|
|
9927
|
-
"path": "unit",
|
|
9928
|
-
"valueType": "string"
|
|
9929
|
-
},
|
|
9930
|
-
{
|
|
9931
|
-
"path": "precision",
|
|
9932
|
-
"valueType": "number"
|
|
9933
|
-
}
|
|
9934
|
-
],
|
|
9974
|
+
"pressure-sensor": [{
|
|
9975
|
+
"path": "hpa",
|
|
9976
|
+
"valueType": "number"
|
|
9977
|
+
}],
|
|
9935
9978
|
"privacy-mask": [{
|
|
9936
9979
|
"path": "enabled",
|
|
9937
9980
|
"valueType": "boolean"
|
|
@@ -10242,20 +10285,10 @@ Object.freeze({
|
|
|
10242
10285
|
"path": "tampered",
|
|
10243
10286
|
"valueType": "boolean"
|
|
10244
10287
|
}],
|
|
10245
|
-
"temperature-sensor": [
|
|
10246
|
-
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
},
|
|
10250
|
-
{
|
|
10251
|
-
"path": "unit",
|
|
10252
|
-
"valueType": "string"
|
|
10253
|
-
},
|
|
10254
|
-
{
|
|
10255
|
-
"path": "precision",
|
|
10256
|
-
"valueType": "number"
|
|
10257
|
-
}
|
|
10258
|
-
],
|
|
10288
|
+
"temperature-sensor": [{
|
|
10289
|
+
"path": "celsius",
|
|
10290
|
+
"valueType": "number"
|
|
10291
|
+
}],
|
|
10259
10292
|
"update": [
|
|
10260
10293
|
{
|
|
10261
10294
|
"path": "currentVersion",
|
|
@@ -10366,10 +10399,6 @@ Object.freeze({
|
|
|
10366
10399
|
"path": "temperature",
|
|
10367
10400
|
"valueType": "number"
|
|
10368
10401
|
},
|
|
10369
|
-
{
|
|
10370
|
-
"path": "temperatureUnit",
|
|
10371
|
-
"valueType": "string"
|
|
10372
|
-
},
|
|
10373
10402
|
{
|
|
10374
10403
|
"path": "humidity",
|
|
10375
10404
|
"valueType": "number"
|
|
@@ -10378,18 +10407,10 @@ Object.freeze({
|
|
|
10378
10407
|
"path": "pressure",
|
|
10379
10408
|
"valueType": "number"
|
|
10380
10409
|
},
|
|
10381
|
-
{
|
|
10382
|
-
"path": "pressureUnit",
|
|
10383
|
-
"valueType": "string"
|
|
10384
|
-
},
|
|
10385
10410
|
{
|
|
10386
10411
|
"path": "windSpeed",
|
|
10387
10412
|
"valueType": "number"
|
|
10388
10413
|
},
|
|
10389
|
-
{
|
|
10390
|
-
"path": "windSpeedUnit",
|
|
10391
|
-
"valueType": "string"
|
|
10392
|
-
},
|
|
10393
10414
|
{
|
|
10394
10415
|
"path": "windBearing",
|
|
10395
10416
|
"valueType": "number"
|
|
@@ -10806,7 +10827,7 @@ function errMsg(err) {
|
|
|
10806
10827
|
return String(err);
|
|
10807
10828
|
}
|
|
10808
10829
|
//#endregion
|
|
10809
|
-
//#region ../types/dist/composition-
|
|
10830
|
+
//#region ../types/dist/composition-OhpuespA.mjs
|
|
10810
10831
|
/**
|
|
10811
10832
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
10812
10833
|
*
|
|
@@ -11561,15 +11582,74 @@ function validateExpressionSource(src) {
|
|
|
11561
11582
|
}
|
|
11562
11583
|
return null;
|
|
11563
11584
|
}
|
|
11564
|
-
/**
|
|
11565
|
-
|
|
11566
|
-
|
|
11567
|
-
|
|
11585
|
+
/**
|
|
11586
|
+
* A composition source identity: `(addonId, stableId)`, never a numeric device
|
|
11587
|
+
* id. Split out so helper schemas can name it without a cycle through
|
|
11588
|
+
* `composition.ts` (D659).
|
|
11589
|
+
*/
|
|
11568
11590
|
var CompositionSourceRefSchema = object({
|
|
11569
11591
|
/** The owning addon; `stableId` is unique only within it. */
|
|
11570
11592
|
addonId: string().min(1),
|
|
11571
11593
|
stableId: string().min(1)
|
|
11572
11594
|
});
|
|
11595
|
+
var GROUP_TEMPLATE_IDS = [
|
|
11596
|
+
"temperature",
|
|
11597
|
+
"humidity",
|
|
11598
|
+
"pressure",
|
|
11599
|
+
"illuminance",
|
|
11600
|
+
"power",
|
|
11601
|
+
"energy",
|
|
11602
|
+
"battery",
|
|
11603
|
+
"numeric",
|
|
11604
|
+
"motion",
|
|
11605
|
+
"contact",
|
|
11606
|
+
"leak",
|
|
11607
|
+
"smoke",
|
|
11608
|
+
"occupancy",
|
|
11609
|
+
"binary",
|
|
11610
|
+
"switch",
|
|
11611
|
+
"light"
|
|
11612
|
+
];
|
|
11613
|
+
var NUMERIC_AGGREGATES = [
|
|
11614
|
+
"mean",
|
|
11615
|
+
"median",
|
|
11616
|
+
"min",
|
|
11617
|
+
"max",
|
|
11618
|
+
"sum"
|
|
11619
|
+
];
|
|
11620
|
+
var BINARY_AGGREGATES = ["any", "all"];
|
|
11621
|
+
/** How an actuator group decides it is on. Unknown members never count as off. */
|
|
11622
|
+
var ACTIVE_CRITERIA = ["at-least-one", "all"];
|
|
11623
|
+
var AGGREGATE_REDUCERS = [
|
|
11624
|
+
"mean",
|
|
11625
|
+
"median",
|
|
11626
|
+
"min",
|
|
11627
|
+
"max",
|
|
11628
|
+
"sum",
|
|
11629
|
+
"any",
|
|
11630
|
+
"all"
|
|
11631
|
+
];
|
|
11632
|
+
var GroupTemplateIdSchema = _enum(GROUP_TEMPLATE_IDS);
|
|
11633
|
+
var AggregateReducerSchema = _enum(AGGREGATE_REDUCERS);
|
|
11634
|
+
var ActiveCriterionSchema = _enum(ACTIVE_CRITERIA);
|
|
11635
|
+
/** One member. The field is stored so occupancy can name motion or binary. */
|
|
11636
|
+
var HelperMemberSchema = object({
|
|
11637
|
+
source: CompositionSourceRefSchema,
|
|
11638
|
+
cap: string().min(1),
|
|
11639
|
+
fieldPath: string().min(1)
|
|
11640
|
+
});
|
|
11641
|
+
var HelperSpecSchema = discriminatedUnion("kind", [object({
|
|
11642
|
+
kind: literal("group"),
|
|
11643
|
+
templateId: GroupTemplateIdSchema,
|
|
11644
|
+
aggregate: AggregateReducerSchema,
|
|
11645
|
+
members: array(HelperMemberSchema).min(1).max(64),
|
|
11646
|
+
/** Actuator groups only. Readings leave this unset. */
|
|
11647
|
+
active: ActiveCriterionSchema.optional()
|
|
11648
|
+
})]);
|
|
11649
|
+
/** A composed device's stableId is this prefix plus the block id. */
|
|
11650
|
+
var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
|
|
11651
|
+
/** Longest snippet a `code` source may carry (slice 4). */
|
|
11652
|
+
var MAX_COMPOSITION_CODE_LENGTH = 2e4;
|
|
11573
11653
|
/** One field of one capability of one source device. */
|
|
11574
11654
|
var CompositionFieldReadSchema = object({
|
|
11575
11655
|
source: CompositionSourceRefSchema,
|
|
@@ -11613,7 +11693,33 @@ var CompositionCodeSourceSchema = object({
|
|
|
11613
11693
|
var CompositionFieldSourceSchema = discriminatedUnion("kind", [
|
|
11614
11694
|
CompositionFromSourceSchema,
|
|
11615
11695
|
CompositionExpressionSourceSchema,
|
|
11616
|
-
CompositionCodeSourceSchema
|
|
11696
|
+
CompositionCodeSourceSchema,
|
|
11697
|
+
object({
|
|
11698
|
+
kind: literal("aggregate"),
|
|
11699
|
+
reducer: _enum([
|
|
11700
|
+
"mean",
|
|
11701
|
+
"median",
|
|
11702
|
+
"min",
|
|
11703
|
+
"max",
|
|
11704
|
+
"sum",
|
|
11705
|
+
"any",
|
|
11706
|
+
"all"
|
|
11707
|
+
]),
|
|
11708
|
+
reads: array(CompositionFieldReadSchema).min(1).max(64),
|
|
11709
|
+
/** `value` is the reading; the others are the member counts. */
|
|
11710
|
+
publish: _enum([
|
|
11711
|
+
"value",
|
|
11712
|
+
"included",
|
|
11713
|
+
"excluded",
|
|
11714
|
+
"total"
|
|
11715
|
+
]),
|
|
11716
|
+
/** What a `value` publish does when nobody is included. Counts ignore this. */
|
|
11717
|
+
empty: _enum([
|
|
11718
|
+
"null",
|
|
11719
|
+
"omit",
|
|
11720
|
+
"offline"
|
|
11721
|
+
])
|
|
11722
|
+
})
|
|
11617
11723
|
]);
|
|
11618
11724
|
var CompositionCommandTargetSchema = discriminatedUnion("kind", [object({
|
|
11619
11725
|
kind: literal("forward"),
|
|
@@ -11659,7 +11765,13 @@ var CompositionSchema = object({
|
|
|
11659
11765
|
kind: literal("existing"),
|
|
11660
11766
|
device: CompositionSourceRefSchema
|
|
11661
11767
|
})]),
|
|
11662
|
-
features: array(CompositionFeatureSchema).min(1).max(16)
|
|
11768
|
+
features: array(CompositionFeatureSchema).min(1).max(16),
|
|
11769
|
+
/**
|
|
11770
|
+
* Present when the block is a helper template. Absent on a free
|
|
11771
|
+
* composition (the room example, device 4603). The editor reads this,
|
|
11772
|
+
* never the compiled features.
|
|
11773
|
+
*/
|
|
11774
|
+
helper: HelperSpecSchema.optional()
|
|
11663
11775
|
}).superRefine((composition, ctx) => {
|
|
11664
11776
|
const seen = /* @__PURE__ */ new Set();
|
|
11665
11777
|
composition.features.forEach((feature, index) => {
|
|
@@ -17867,7 +17979,11 @@ var CompositionProblemSchema = object({
|
|
|
17867
17979
|
"item-field-reserved",
|
|
17868
17980
|
"target-missing",
|
|
17869
17981
|
"target-unreadable",
|
|
17870
|
-
"not-replaceable"
|
|
17982
|
+
"not-replaceable",
|
|
17983
|
+
"native-capability-replace",
|
|
17984
|
+
"mixed-quantity",
|
|
17985
|
+
"helper-mismatch",
|
|
17986
|
+
"member-constraint"
|
|
17871
17987
|
]),
|
|
17872
17988
|
path: string(),
|
|
17873
17989
|
message: string()
|
|
@@ -17888,7 +18004,8 @@ var CompositionUnavailableModeSchema = _enum([
|
|
|
17888
18004
|
"null",
|
|
17889
18005
|
"offline",
|
|
17890
18006
|
"unknown",
|
|
17891
|
-
"release"
|
|
18007
|
+
"release",
|
|
18008
|
+
"omit"
|
|
17892
18009
|
]);
|
|
17893
18010
|
/**
|
|
17894
18011
|
* `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
|
|
@@ -17925,9 +18042,11 @@ var CompositionFieldPlanSchema = object({
|
|
|
17925
18042
|
reads: array(CompositionFieldReadSchema)
|
|
17926
18043
|
});
|
|
17927
18044
|
/**
|
|
17928
|
-
* What a feature does to its target (D663): `new` builds a capability on a
|
|
17929
|
-
* device; on an existing device it `add`s a capability the device lacks
|
|
17930
|
-
*
|
|
18045
|
+
* What a feature does to its target (D663, D944): `new` builds a capability on a
|
|
18046
|
+
* new device; on an existing device it `add`s a capability the device lacks.
|
|
18047
|
+
* Replacing a capability the device already serves is refused
|
|
18048
|
+
* (`native-capability-replace`), not planned. `replace` remains on the claim
|
|
18049
|
+
* wire for a claim the hub already holds.
|
|
17931
18050
|
*/
|
|
17932
18051
|
var CompositionFeatureModeSchema = _enum([
|
|
17933
18052
|
"new",
|
|
@@ -18100,6 +18219,275 @@ Object.freeze({
|
|
|
18100
18219
|
result: ["number", "null"]
|
|
18101
18220
|
}
|
|
18102
18221
|
});
|
|
18222
|
+
/**
|
|
18223
|
+
* One catalog row per base reading a group helper can build (D944 §3).
|
|
18224
|
+
* Data, not a form default written into the block.
|
|
18225
|
+
*/
|
|
18226
|
+
var NOW = {
|
|
18227
|
+
fieldPath: "lastFetchedAt",
|
|
18228
|
+
expr: "now"
|
|
18229
|
+
};
|
|
18230
|
+
var CHANGED = {
|
|
18231
|
+
fieldPath: "lastChangedAt",
|
|
18232
|
+
expr: "now"
|
|
18233
|
+
};
|
|
18234
|
+
function numeric(input) {
|
|
18235
|
+
return {
|
|
18236
|
+
...input,
|
|
18237
|
+
family: "numeric",
|
|
18238
|
+
deviceType: DeviceType.Sensor,
|
|
18239
|
+
memberDeviceType: DeviceType.Sensor,
|
|
18240
|
+
commandCaps: [],
|
|
18241
|
+
baseUnit: baseUnitOf(input.quantity),
|
|
18242
|
+
aggregates: NUMERIC_AGGREGATES,
|
|
18243
|
+
accept: [{
|
|
18244
|
+
cap: input.outputCap,
|
|
18245
|
+
fieldPath: input.outputField
|
|
18246
|
+
}]
|
|
18247
|
+
};
|
|
18248
|
+
}
|
|
18249
|
+
function binary(input) {
|
|
18250
|
+
return {
|
|
18251
|
+
...input,
|
|
18252
|
+
family: "binary",
|
|
18253
|
+
deviceType: DeviceType.Sensor,
|
|
18254
|
+
memberDeviceType: DeviceType.Sensor,
|
|
18255
|
+
commandCaps: [],
|
|
18256
|
+
quantity: "none",
|
|
18257
|
+
baseUnit: void 0,
|
|
18258
|
+
aggregates: BINARY_AGGREGATES,
|
|
18259
|
+
empty: "offline"
|
|
18260
|
+
};
|
|
18261
|
+
}
|
|
18262
|
+
var GROUP_TEMPLATES = [
|
|
18263
|
+
numeric({
|
|
18264
|
+
id: "temperature",
|
|
18265
|
+
summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
|
|
18266
|
+
role: DeviceRole.TemperatureSensor,
|
|
18267
|
+
outputCap: "temperature-sensor",
|
|
18268
|
+
outputField: "celsius",
|
|
18269
|
+
quantity: "temperature",
|
|
18270
|
+
defaultAggregate: "mean",
|
|
18271
|
+
empty: "null",
|
|
18272
|
+
companions: [NOW]
|
|
18273
|
+
}),
|
|
18274
|
+
numeric({
|
|
18275
|
+
id: "humidity",
|
|
18276
|
+
summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
|
|
18277
|
+
role: DeviceRole.HumiditySensor,
|
|
18278
|
+
outputCap: "humidity-sensor",
|
|
18279
|
+
outputField: "percent",
|
|
18280
|
+
quantity: "humidity",
|
|
18281
|
+
defaultAggregate: "mean",
|
|
18282
|
+
empty: "null",
|
|
18283
|
+
companions: [NOW]
|
|
18284
|
+
}),
|
|
18285
|
+
numeric({
|
|
18286
|
+
id: "pressure",
|
|
18287
|
+
summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
|
|
18288
|
+
role: DeviceRole.PressureSensor,
|
|
18289
|
+
outputCap: "pressure-sensor",
|
|
18290
|
+
outputField: "hpa",
|
|
18291
|
+
quantity: "pressure",
|
|
18292
|
+
defaultAggregate: "mean",
|
|
18293
|
+
empty: "null",
|
|
18294
|
+
companions: [NOW]
|
|
18295
|
+
}),
|
|
18296
|
+
numeric({
|
|
18297
|
+
id: "illuminance",
|
|
18298
|
+
summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
|
|
18299
|
+
role: DeviceRole.AmbientLightSensor,
|
|
18300
|
+
outputCap: "ambient-light-sensor",
|
|
18301
|
+
outputField: "lux",
|
|
18302
|
+
quantity: "illuminance",
|
|
18303
|
+
defaultAggregate: "mean",
|
|
18304
|
+
empty: "null",
|
|
18305
|
+
companions: [NOW]
|
|
18306
|
+
}),
|
|
18307
|
+
numeric({
|
|
18308
|
+
id: "power",
|
|
18309
|
+
summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
|
|
18310
|
+
role: DeviceRole.PowerSensor,
|
|
18311
|
+
outputCap: "power-meter",
|
|
18312
|
+
outputField: "watts",
|
|
18313
|
+
quantity: "power",
|
|
18314
|
+
defaultAggregate: "sum",
|
|
18315
|
+
empty: "omit",
|
|
18316
|
+
companions: [NOW]
|
|
18317
|
+
}),
|
|
18318
|
+
numeric({
|
|
18319
|
+
id: "energy",
|
|
18320
|
+
summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
|
|
18321
|
+
role: DeviceRole.EnergySensor,
|
|
18322
|
+
outputCap: "power-meter",
|
|
18323
|
+
outputField: "kwhTotal",
|
|
18324
|
+
quantity: "energy",
|
|
18325
|
+
defaultAggregate: "sum",
|
|
18326
|
+
empty: "omit",
|
|
18327
|
+
companions: [NOW]
|
|
18328
|
+
}),
|
|
18329
|
+
numeric({
|
|
18330
|
+
id: "battery",
|
|
18331
|
+
summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
|
|
18332
|
+
role: DeviceRole.BatterySensor,
|
|
18333
|
+
outputCap: "battery",
|
|
18334
|
+
outputField: "percentage",
|
|
18335
|
+
quantity: "percentage",
|
|
18336
|
+
defaultAggregate: "mean",
|
|
18337
|
+
empty: "null",
|
|
18338
|
+
companions: [
|
|
18339
|
+
{
|
|
18340
|
+
fieldPath: "charging",
|
|
18341
|
+
expr: "\"unknown\""
|
|
18342
|
+
},
|
|
18343
|
+
{
|
|
18344
|
+
fieldPath: "sleeping",
|
|
18345
|
+
expr: "false"
|
|
18346
|
+
},
|
|
18347
|
+
{
|
|
18348
|
+
fieldPath: "lastUpdated",
|
|
18349
|
+
expr: "now"
|
|
18350
|
+
}
|
|
18351
|
+
]
|
|
18352
|
+
}),
|
|
18353
|
+
numeric({
|
|
18354
|
+
id: "numeric",
|
|
18355
|
+
summary: "A numeric reading in the members’ shared quantity. Mixed quantities are refused. Nobody reporting publishes null, not 0.",
|
|
18356
|
+
role: DeviceRole.NumericSensor,
|
|
18357
|
+
outputCap: "numeric-sensor",
|
|
18358
|
+
outputField: "value",
|
|
18359
|
+
quantity: "device",
|
|
18360
|
+
defaultAggregate: "mean",
|
|
18361
|
+
empty: "null",
|
|
18362
|
+
companions: [NOW]
|
|
18363
|
+
}),
|
|
18364
|
+
binary({
|
|
18365
|
+
id: "motion",
|
|
18366
|
+
summary: "Any or all motion. Offline and unknown members are left out, never read as off. Nobody reporting takes the device offline.",
|
|
18367
|
+
role: DeviceRole.MotionSensor,
|
|
18368
|
+
outputCap: "motion",
|
|
18369
|
+
outputField: "detected",
|
|
18370
|
+
defaultAggregate: "any",
|
|
18371
|
+
accept: [{
|
|
18372
|
+
cap: "motion",
|
|
18373
|
+
fieldPath: "detected"
|
|
18374
|
+
}],
|
|
18375
|
+
companions: [{
|
|
18376
|
+
fieldPath: "lastDetectedAt",
|
|
18377
|
+
expr: "null"
|
|
18378
|
+
}, {
|
|
18379
|
+
fieldPath: "autoClearAfterMs",
|
|
18380
|
+
expr: "null"
|
|
18381
|
+
}]
|
|
18382
|
+
}),
|
|
18383
|
+
binary({
|
|
18384
|
+
id: "contact",
|
|
18385
|
+
summary: "Any or all open contacts. Offline and unknown members are left out, never read as closed.",
|
|
18386
|
+
role: DeviceRole.ContactSensor,
|
|
18387
|
+
outputCap: "contact",
|
|
18388
|
+
outputField: "entryOpen",
|
|
18389
|
+
defaultAggregate: "any",
|
|
18390
|
+
accept: [{
|
|
18391
|
+
cap: "contact",
|
|
18392
|
+
fieldPath: "entryOpen"
|
|
18393
|
+
}],
|
|
18394
|
+
companions: [CHANGED]
|
|
18395
|
+
}),
|
|
18396
|
+
binary({
|
|
18397
|
+
id: "leak",
|
|
18398
|
+
summary: "Any or all leak detections. Offline and unknown members are left out, never read as dry.",
|
|
18399
|
+
role: DeviceRole.LeakSensor,
|
|
18400
|
+
outputCap: "flood",
|
|
18401
|
+
outputField: "flooded",
|
|
18402
|
+
defaultAggregate: "any",
|
|
18403
|
+
accept: [{
|
|
18404
|
+
cap: "flood",
|
|
18405
|
+
fieldPath: "flooded"
|
|
18406
|
+
}],
|
|
18407
|
+
companions: [CHANGED]
|
|
18408
|
+
}),
|
|
18409
|
+
binary({
|
|
18410
|
+
id: "smoke",
|
|
18411
|
+
summary: "Any or all smoke detections. Offline and unknown members are left out, never read as clear.",
|
|
18412
|
+
role: DeviceRole.SmokeSensor,
|
|
18413
|
+
outputCap: "smoke",
|
|
18414
|
+
outputField: "detected",
|
|
18415
|
+
defaultAggregate: "any",
|
|
18416
|
+
accept: [{
|
|
18417
|
+
cap: "smoke",
|
|
18418
|
+
fieldPath: "detected"
|
|
18419
|
+
}],
|
|
18420
|
+
companions: [CHANGED]
|
|
18421
|
+
}),
|
|
18422
|
+
binary({
|
|
18423
|
+
id: "occupancy",
|
|
18424
|
+
summary: "Any or all of motion or binary sensors, published as a binary. Presence zones are not aggregated. Nobody reporting is offline, not off.",
|
|
18425
|
+
role: DeviceRole.BinarySensor,
|
|
18426
|
+
outputCap: "binary",
|
|
18427
|
+
outputField: "on",
|
|
18428
|
+
defaultAggregate: "any",
|
|
18429
|
+
accept: [{
|
|
18430
|
+
cap: "motion",
|
|
18431
|
+
fieldPath: "detected"
|
|
18432
|
+
}, {
|
|
18433
|
+
cap: "binary",
|
|
18434
|
+
fieldPath: "on"
|
|
18435
|
+
}],
|
|
18436
|
+
companions: [CHANGED]
|
|
18437
|
+
}),
|
|
18438
|
+
binary({
|
|
18439
|
+
id: "binary",
|
|
18440
|
+
summary: "Any or all generic binary sensors. Offline and unknown members are left out, never read as off.",
|
|
18441
|
+
role: DeviceRole.BinarySensor,
|
|
18442
|
+
outputCap: "binary",
|
|
18443
|
+
outputField: "on",
|
|
18444
|
+
defaultAggregate: "any",
|
|
18445
|
+
accept: [{
|
|
18446
|
+
cap: "binary",
|
|
18447
|
+
fieldPath: "on"
|
|
18448
|
+
}],
|
|
18449
|
+
companions: [CHANGED]
|
|
18450
|
+
}),
|
|
18451
|
+
actuator({
|
|
18452
|
+
id: "switch",
|
|
18453
|
+
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.",
|
|
18454
|
+
deviceType: DeviceType.Switch,
|
|
18455
|
+
outputCap: "switch",
|
|
18456
|
+
commandCaps: ["switch"]
|
|
18457
|
+
}),
|
|
18458
|
+
actuator({
|
|
18459
|
+
id: "light",
|
|
18460
|
+
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.",
|
|
18461
|
+
deviceType: DeviceType.Light,
|
|
18462
|
+
outputCap: "switch",
|
|
18463
|
+
commandCaps: [
|
|
18464
|
+
"switch",
|
|
18465
|
+
"brightness",
|
|
18466
|
+
"color"
|
|
18467
|
+
]
|
|
18468
|
+
})
|
|
18469
|
+
];
|
|
18470
|
+
function actuator(input) {
|
|
18471
|
+
return {
|
|
18472
|
+
...input,
|
|
18473
|
+
family: "actuator",
|
|
18474
|
+
role: void 0,
|
|
18475
|
+
memberDeviceType: input.deviceType,
|
|
18476
|
+
outputField: "on",
|
|
18477
|
+
quantity: "none",
|
|
18478
|
+
baseUnit: void 0,
|
|
18479
|
+
aggregates: BINARY_AGGREGATES,
|
|
18480
|
+
defaultAggregate: "any",
|
|
18481
|
+
empty: "offline",
|
|
18482
|
+
accept: [{
|
|
18483
|
+
cap: "switch",
|
|
18484
|
+
fieldPath: "on"
|
|
18485
|
+
}],
|
|
18486
|
+
companions: [CHANGED]
|
|
18487
|
+
};
|
|
18488
|
+
}
|
|
18489
|
+
new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
|
|
18490
|
+
DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
|
|
18103
18491
|
`${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
|
|
18104
18492
|
/**
|
|
18105
18493
|
* What a composition may BUILD on a device type (G2, D676): the caps a new
|
|
@@ -19926,12 +20314,12 @@ function buildDeviceAdminUrl(parts) {
|
|
|
19926
20314
|
* providers detect renames by matching `uniqueId` and patch `id`
|
|
19927
20315
|
* in-place so dispatch keeps working.
|
|
19928
20316
|
*
|
|
19929
|
-
* **
|
|
19930
|
-
*
|
|
19931
|
-
*
|
|
19932
|
-
*
|
|
19933
|
-
* (`ha-domain-mapping`)
|
|
19934
|
-
* stored after that point.
|
|
20317
|
+
* **Display unit and precision are NOT stored here.** They are a read-time
|
|
20318
|
+
* preference (D944). A polymorphic reading stores its `quantity` here once,
|
|
20319
|
+
* at adopt, and a date/time value stores `dateTimeShape` the same way —
|
|
20320
|
+
* neither is repeated on each sample. The `device_class` tag is an
|
|
20321
|
+
* HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
|
|
20322
|
+
* to derive the CamStack `DeviceRole`; it is never stored after that point.
|
|
19935
20323
|
*
|
|
19936
20324
|
* The `raw` escape hatch carries provider-specific attributes that don't
|
|
19937
20325
|
* fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
|
|
@@ -19945,6 +20333,14 @@ var SourceInfoSchema = object({
|
|
|
19945
20333
|
/** Immutable upstream identifier when available (HA `unique_id`, … ).
|
|
19946
20334
|
* Used to detect rename when `id` changes. */
|
|
19947
20335
|
uniqueId: string().optional(),
|
|
20336
|
+
/** Quantity of a polymorphic reading, written once at adopt (D944). */
|
|
20337
|
+
quantity: ReadingQuantitySchema.optional(),
|
|
20338
|
+
/** Date / time / datetime shape of a string reading, written once at adopt. */
|
|
20339
|
+
dateTimeShape: _enum([
|
|
20340
|
+
"date",
|
|
20341
|
+
"time",
|
|
20342
|
+
"datetime"
|
|
20343
|
+
]).optional(),
|
|
19948
20344
|
/** Provider-specific extras that don't fit the structured slots.
|
|
19949
20345
|
* Whitelist what goes here per provider — do NOT dump entire HA
|
|
19950
20346
|
* attribute blobs (would explode the meta JSON column). */
|
|
@@ -19990,6 +20386,80 @@ function mergeSourceInfo(prev, patch) {
|
|
|
19990
20386
|
return next;
|
|
19991
20387
|
}
|
|
19992
20388
|
/**
|
|
20389
|
+
* Coerce common unit-of-measurement string variants to a canonical form
|
|
20390
|
+
* so the UI's display templates can rely on stable strings. Pure
|
|
20391
|
+
* passthrough for unknown units — kept conservative on purpose, the goal
|
|
20392
|
+
* is to normalise the long tail of HA `unit_of_measurement` values that
|
|
20393
|
+
* have multiple historical spellings (`'°C'` vs `'C'` vs `'celsius'`).
|
|
20394
|
+
*
|
|
20395
|
+
* Extend the map as we encounter real-world data. Returning the raw
|
|
20396
|
+
* input for unknown values is intentional: providers should never
|
|
20397
|
+
* lose information silently here.
|
|
20398
|
+
*/
|
|
20399
|
+
function normalizeUnit(raw) {
|
|
20400
|
+
if (raw == null) return void 0;
|
|
20401
|
+
const trimmed = raw.trim();
|
|
20402
|
+
if (trimmed === "") return void 0;
|
|
20403
|
+
const lower = trimmed.toLowerCase();
|
|
20404
|
+
if (lower === "°c" || lower === "c" || lower === "celsius") return "°C";
|
|
20405
|
+
if (lower === "°f" || lower === "f" || lower === "fahrenheit") return "°F";
|
|
20406
|
+
if (lower === "k" || lower === "kelvin") return "K";
|
|
20407
|
+
if (lower === "%" || lower === "percent" || lower === "percentage") return "%";
|
|
20408
|
+
if (lower === "lx" || lower === "lux") return "lx";
|
|
20409
|
+
if (lower === "hpa" || lower === "hectopascal") return "hPa";
|
|
20410
|
+
if (lower === "kpa") return "kPa";
|
|
20411
|
+
if (lower === "pa") return "Pa";
|
|
20412
|
+
if (lower === "mbar" || lower === "millibar") return "mbar";
|
|
20413
|
+
if (lower === "w" || lower === "watt") return "W";
|
|
20414
|
+
if (lower === "kw" || lower === "kilowatt") return "kW";
|
|
20415
|
+
if (lower === "wh" || lower === "watt-hour") return "Wh";
|
|
20416
|
+
if (lower === "kwh" || lower === "kilowatt-hour") return "kWh";
|
|
20417
|
+
if (lower === "v" || lower === "volt") return "V";
|
|
20418
|
+
if (lower === "a" || lower === "amp" || lower === "ampere") return "A";
|
|
20419
|
+
if (lower === "ma" || lower === "milliampere") return "mA";
|
|
20420
|
+
if (lower === "va") return "VA";
|
|
20421
|
+
if (lower === "ppm") return "ppm";
|
|
20422
|
+
if (lower === "ppb") return "ppb";
|
|
20423
|
+
if (lower === "µg/m³" || lower === "ug/m3" || lower === "µg/m3") return "µg/m³";
|
|
20424
|
+
if (lower === "km/h" || lower === "kph") return "km/h";
|
|
20425
|
+
if (lower === "mph") return "mph";
|
|
20426
|
+
if (lower === "m/s") return "m/s";
|
|
20427
|
+
if (lower === "m") return "m";
|
|
20428
|
+
if (lower === "m²" || lower === "m2") return "m²";
|
|
20429
|
+
if (lower === "mm") return "mm";
|
|
20430
|
+
if (lower === "cm") return "cm";
|
|
20431
|
+
if (lower === "km") return "km";
|
|
20432
|
+
if (lower === "in" || lower === "inch" || lower === "inches") return "in";
|
|
20433
|
+
if (lower === "ft" || lower === "foot" || lower === "feet") return "ft";
|
|
20434
|
+
if (lower === "kg") return "kg";
|
|
20435
|
+
if (lower === "g") return "g";
|
|
20436
|
+
if (lower === "l" || lower === "liter" || lower === "litre" || lower === "liters" || lower === "litres") return "L";
|
|
20437
|
+
if (lower === "ml") return "mL";
|
|
20438
|
+
if (lower === "gal" || lower === "gallon" || lower === "gallons") return "gal";
|
|
20439
|
+
if (lower === "s" || lower === "sec" || lower === "second" || lower === "seconds") return "s";
|
|
20440
|
+
if (lower === "ms") return "ms";
|
|
20441
|
+
if (lower === "min" || lower === "minute" || lower === "minutes") return "min";
|
|
20442
|
+
if (lower === "h" || lower === "hr" || lower === "hour" || lower === "hours") return "h";
|
|
20443
|
+
if (lower === "d" || lower === "day" || lower === "days") return "d";
|
|
20444
|
+
if (lower === "b" || lower === "byte" || lower === "bytes") return "B";
|
|
20445
|
+
if (lower === "kib") return "KiB";
|
|
20446
|
+
if (lower === "mib") return "MiB";
|
|
20447
|
+
if (lower === "gib") return "GiB";
|
|
20448
|
+
if (lower === "tib") return "TiB";
|
|
20449
|
+
if (lower === "kb") return "kB";
|
|
20450
|
+
if (lower === "mb") return "MB";
|
|
20451
|
+
if (lower === "gb") return "GB";
|
|
20452
|
+
if (lower === "dbm") return "dBm";
|
|
20453
|
+
if (lower === "dbfs") return "dBFS";
|
|
20454
|
+
if (lower === "inhg") return "inHg";
|
|
20455
|
+
if (lower === "mm/h") return "mm/h";
|
|
20456
|
+
if (lower === "w/m²" || lower === "w/m2") return "W/m²";
|
|
20457
|
+
if (lower === "°" || lower === "deg" || lower === "degree" || lower === "degrees") return "°";
|
|
20458
|
+
if (lower === "kbps") return "kbps";
|
|
20459
|
+
if (lower === "fps") return "fps";
|
|
20460
|
+
return trimmed;
|
|
20461
|
+
}
|
|
20462
|
+
/**
|
|
19993
20463
|
* Key under which `SourceInfo` lives inside `DeviceMeta.metadata`. The
|
|
19994
20464
|
* blob is a free-form `Record<string, unknown>` shared with hardware-
|
|
19995
20465
|
* identity fields (`manufacturer`, `model`, `firmware`, …); SourceInfo
|
|
@@ -41049,7 +41519,8 @@ method(object({
|
|
|
41049
41519
|
"enum"
|
|
41050
41520
|
]),
|
|
41051
41521
|
enumValues: array(string()).optional(),
|
|
41052
|
-
item: boolean().optional()
|
|
41522
|
+
item: boolean().optional(),
|
|
41523
|
+
quantity: ReadingQuantitySchema.optional()
|
|
41053
41524
|
})).readonly(),
|
|
41054
41525
|
itemArray: object({
|
|
41055
41526
|
path: string(),
|
|
@@ -44893,8 +45364,8 @@ var MaskPolygonShapeSchema = object({
|
|
|
44893
45364
|
/** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
|
|
44894
45365
|
var MaskGridShapeSchema = object({
|
|
44895
45366
|
kind: literal("grid"),
|
|
44896
|
-
gridWidth: number(),
|
|
44897
|
-
gridHeight: number(),
|
|
45367
|
+
gridWidth: number().meta(readingMeta("none")),
|
|
45368
|
+
gridHeight: number().meta(readingMeta("none")),
|
|
44898
45369
|
cells: array(boolean())
|
|
44899
45370
|
});
|
|
44900
45371
|
discriminatedUnion("kind", [
|
|
@@ -45006,6 +45477,8 @@ var NcSystemEventKindSchema = _enum([
|
|
|
45006
45477
|
"detection-blind",
|
|
45007
45478
|
"motion-noisy",
|
|
45008
45479
|
"motion-calm",
|
|
45480
|
+
"package-llm-silent",
|
|
45481
|
+
"package-llm-answered",
|
|
45009
45482
|
"recording-stalled",
|
|
45010
45483
|
"recording-healthy",
|
|
45011
45484
|
"recording-degraded",
|
|
@@ -56097,6 +56570,28 @@ var PackageVersionInfoSchema = object({
|
|
|
56097
56570
|
deprecated: string().optional(),
|
|
56098
56571
|
distTags: array(string()).readonly()
|
|
56099
56572
|
});
|
|
56573
|
+
/**
|
|
56574
|
+
* One published document. `ok` is markdown from the registry. `missing` means
|
|
56575
|
+
* the registry answered and the file was not there. `error` is a registry or
|
|
56576
|
+
* tarball failure — never an empty string standing in for either.
|
|
56577
|
+
*/
|
|
56578
|
+
var PackageDocBodySchema = discriminatedUnion("status", [
|
|
56579
|
+
object({
|
|
56580
|
+
status: literal("ok"),
|
|
56581
|
+
markdown: string()
|
|
56582
|
+
}),
|
|
56583
|
+
object({ status: literal("missing") }),
|
|
56584
|
+
object({
|
|
56585
|
+
status: literal("error"),
|
|
56586
|
+
message: string()
|
|
56587
|
+
})
|
|
56588
|
+
]);
|
|
56589
|
+
var PackageDocsSchema = object({
|
|
56590
|
+
name: string(),
|
|
56591
|
+
version: string(),
|
|
56592
|
+
readme: PackageDocBodySchema,
|
|
56593
|
+
changelog: PackageDocBodySchema
|
|
56594
|
+
});
|
|
56100
56595
|
var SearchResultSchema = object({
|
|
56101
56596
|
name: string(),
|
|
56102
56597
|
version: string(),
|
|
@@ -56315,7 +56810,10 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
|
|
|
56315
56810
|
}), object({ success: literal(true) }), {
|
|
56316
56811
|
kind: "mutation",
|
|
56317
56812
|
auth: "admin"
|
|
56318
|
-
}), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({
|
|
56813
|
+
}), method(object({ name: string() }), array(PackageVersionInfoSchema).readonly()), method(object({
|
|
56814
|
+
name: string().min(1),
|
|
56815
|
+
version: string().min(1).optional()
|
|
56816
|
+
}), PackageDocsSchema, { auth: "admin" }), method(object({ addonId: string() }), RestartAddonResultSchema, {
|
|
56319
56817
|
kind: "mutation",
|
|
56320
56818
|
auth: "admin"
|
|
56321
56819
|
}), method(object({ packageName: string() }), object({ success: literal(true) }), {
|
|
@@ -56374,27 +56872,17 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
|
|
|
56374
56872
|
*/
|
|
56375
56873
|
var AirQualitySensorStatusSchema = object({
|
|
56376
56874
|
/** Carbon dioxide concentration in ppm. */
|
|
56377
|
-
co2Ppm: number().min(0).optional(),
|
|
56875
|
+
co2Ppm: number().min(0).optional().meta(readingMeta("concentration-volume")),
|
|
56378
56876
|
/** Total volatile organic compounds in ppb. */
|
|
56379
|
-
vocPpb: number().min(0).optional(),
|
|
56877
|
+
vocPpb: number().min(0).optional().meta(readingMeta("concentration-ppb")),
|
|
56380
56878
|
/** Particulate matter ≤ 2.5 μm in µg/m³. */
|
|
56381
|
-
pm25: number().min(0).optional(),
|
|
56879
|
+
pm25: number().min(0).optional().meta(readingMeta("concentration-mass")),
|
|
56382
56880
|
/** Particulate matter ≤ 10 μm in µg/m³. */
|
|
56383
|
-
pm10: number().min(0).optional(),
|
|
56881
|
+
pm10: number().min(0).optional().meta(readingMeta("concentration-mass")),
|
|
56384
56882
|
/** Composite AQI value (typically 0..500). */
|
|
56385
|
-
aqi: number().optional(),
|
|
56883
|
+
aqi: number().optional().meta(readingMeta("index")),
|
|
56386
56884
|
/** Ms epoch when the slice was last updated. */
|
|
56387
|
-
lastFetchedAt: number()
|
|
56388
|
-
/** Live display unit of the single metric this slice carries (e.g. HA
|
|
56389
|
-
* `attributes.unit_of_measurement` → 'ppm' / 'ppb' / 'µg/m³'). Each
|
|
56390
|
-
* upstream `sensor.*` entity surfaces ONE device_class, so one unit
|
|
56391
|
-
* per slice is unambiguous. */
|
|
56392
|
-
unit: string().optional(),
|
|
56393
|
-
/** Suggested decimal places for numeric display.
|
|
56394
|
-
* Populated live from the upstream source when provided (e.g. HA
|
|
56395
|
-
* `attributes.suggested_display_precision`). Falls back to
|
|
56396
|
-
* auto-formatting when absent. */
|
|
56397
|
-
precision: number().int().min(0).max(10).optional()
|
|
56885
|
+
lastFetchedAt: number()
|
|
56398
56886
|
});
|
|
56399
56887
|
var airQualitySensorCapability = {
|
|
56400
56888
|
name: "air-quality-sensor",
|
|
@@ -56431,12 +56919,12 @@ var airQualitySensorCapability = {
|
|
|
56431
56919
|
},
|
|
56432
56920
|
pm25: {
|
|
56433
56921
|
kind: "gauge",
|
|
56434
|
-
unit: "
|
|
56922
|
+
unit: "µg/m³",
|
|
56435
56923
|
deadband: 1
|
|
56436
56924
|
},
|
|
56437
56925
|
pm10: {
|
|
56438
56926
|
kind: "gauge",
|
|
56439
|
-
unit: "
|
|
56927
|
+
unit: "µg/m³",
|
|
56440
56928
|
deadband: 1
|
|
56441
56929
|
},
|
|
56442
56930
|
aqi: {
|
|
@@ -56454,22 +56942,13 @@ var airQualitySensorCapability = {
|
|
|
56454
56942
|
* entries with `device_class: illuminance`.
|
|
56455
56943
|
*/
|
|
56456
56944
|
var AmbientLightSensorStatusSchema = object({
|
|
56457
|
-
/** Current illuminance in lux (lx). */
|
|
56458
|
-
lux: number().min(0).meta({
|
|
56459
|
-
|
|
56945
|
+
/** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
|
|
56946
|
+
lux: number().min(0).nullable().meta(readingMeta("illuminance", {
|
|
56947
|
+
precision: 0,
|
|
56460
56948
|
label: "Illuminance"
|
|
56461
|
-
}),
|
|
56949
|
+
})),
|
|
56462
56950
|
/** Ms epoch when the slice was last updated. */
|
|
56463
|
-
lastFetchedAt: number()
|
|
56464
|
-
/** Live display unit from the upstream source (e.g. HA
|
|
56465
|
-
* `attributes.unit_of_measurement`). The UI prefers this over the
|
|
56466
|
-
* role's canonical unit. Absent → fall back to the canonical unit. */
|
|
56467
|
-
unit: string().optional(),
|
|
56468
|
-
/** Suggested decimal places for numeric display.
|
|
56469
|
-
* Populated live from the upstream source when provided (e.g. HA
|
|
56470
|
-
* `attributes.suggested_display_precision`). Falls back to
|
|
56471
|
-
* auto-formatting when absent. */
|
|
56472
|
-
precision: number().int().min(0).max(10).optional()
|
|
56951
|
+
lastFetchedAt: number()
|
|
56473
56952
|
});
|
|
56474
56953
|
var ambientLightSensorCapability = {
|
|
56475
56954
|
name: "ambient-light-sensor",
|
|
@@ -56501,12 +56980,7 @@ var ambientLightSensorCapability = {
|
|
|
56501
56980
|
} } },
|
|
56502
56981
|
/** Clock fields: written, but excluded from the compare that decides
|
|
56503
56982
|
* whether persisting is worth a SQLite commit. */
|
|
56504
|
-
volatileStateFields: ["lastFetchedAt"]
|
|
56505
|
-
customize: {
|
|
56506
|
-
version: 1,
|
|
56507
|
-
advanced: "derive",
|
|
56508
|
-
targets: []
|
|
56509
|
-
}
|
|
56983
|
+
volatileStateFields: ["lastFetchedAt"]
|
|
56510
56984
|
};
|
|
56511
56985
|
/**
|
|
56512
56986
|
* Per-class audio metrics aggregated over a sliding window.
|
|
@@ -56733,10 +57207,7 @@ var BatteryStatusSchema = object({
|
|
|
56733
57207
|
* Consumers must SKIP a null rather than coerce it — `battery-band` already
|
|
56734
57208
|
* declines to band a non-finite reading, which is the correct shape.
|
|
56735
57209
|
*/
|
|
56736
|
-
percentage: number().min(0).max(100).nullable().meta({
|
|
56737
|
-
unit: "%",
|
|
56738
|
-
label: "Charge"
|
|
56739
|
-
}),
|
|
57210
|
+
percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
|
|
56740
57211
|
/**
|
|
56741
57212
|
* Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
|
|
56742
57213
|
* Reolink-specific for the Solar Panel 2 accessory (will become
|
|
@@ -56763,7 +57234,14 @@ var BatteryStatusSchema = object({
|
|
|
56763
57234
|
*/
|
|
56764
57235
|
sleeping: boolean().meta({ label: "Sleeping" }),
|
|
56765
57236
|
/** Ms epoch of the last observation. Lets consumers reason about freshness. */
|
|
56766
|
-
lastUpdated: number(),
|
|
57237
|
+
lastUpdated: number().meta(readingMeta("none")),
|
|
57238
|
+
/**
|
|
57239
|
+
* Battery voltage in volts, for a cell the firmware reports as voltage
|
|
57240
|
+
* rather than a percentage (Ecowitt capacitor packs). Absent on cameras
|
|
57241
|
+
* that only report charge. A second reading on this cap, not a second
|
|
57242
|
+
* `numeric-sensor` on the same device.
|
|
57243
|
+
*/
|
|
57244
|
+
volts: number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
|
|
56767
57245
|
/**
|
|
56768
57246
|
* Ms epoch of the last time the device PROVED it was reachable — a
|
|
56769
57247
|
* completed firmware round-trip, an observed wake, or an inbound push
|
|
@@ -56815,7 +57293,6 @@ var BATTERY_DEVICE_TYPES = [
|
|
|
56815
57293
|
/** Generic Customize target for the percentage reading (D936). */
|
|
56816
57294
|
var batteryCustomize = {
|
|
56817
57295
|
version: 1,
|
|
56818
|
-
advanced: "derive",
|
|
56819
57296
|
targets: [{
|
|
56820
57297
|
id: "percentage",
|
|
56821
57298
|
cardinality: "scalar",
|
|
@@ -56824,7 +57301,7 @@ var batteryCustomize = {
|
|
|
56824
57301
|
stateField: "percentage"
|
|
56825
57302
|
},
|
|
56826
57303
|
deviceTypes: BATTERY_DEVICE_TYPES,
|
|
56827
|
-
apply: "add
|
|
57304
|
+
apply: "add",
|
|
56828
57305
|
path: "percentage",
|
|
56829
57306
|
sources: [{
|
|
56830
57307
|
kind: "field",
|
|
@@ -56945,10 +57422,16 @@ onStatusChanged: { data: object({
|
|
|
56945
57422
|
/**
|
|
56946
57423
|
* History (D850): recorded series. The generic capture replaces the `battery` series of `pipeline-analytics:stats-5m`.
|
|
56947
57424
|
*/
|
|
56948
|
-
history: { fields: {
|
|
56949
|
-
|
|
56950
|
-
|
|
56951
|
-
|
|
57425
|
+
history: { fields: {
|
|
57426
|
+
percentage: {
|
|
57427
|
+
kind: "gauge",
|
|
57428
|
+
unit: "%"
|
|
57429
|
+
},
|
|
57430
|
+
volts: {
|
|
57431
|
+
kind: "gauge",
|
|
57432
|
+
unit: "V"
|
|
57433
|
+
}
|
|
57434
|
+
} },
|
|
56952
57435
|
customize: batteryCustomize,
|
|
56953
57436
|
/** Clock fields: written, but excluded from the compare that decides
|
|
56954
57437
|
* whether persisting is worth a SQLite commit. */
|
|
@@ -56995,12 +57478,7 @@ var binaryCapability = {
|
|
|
56995
57478
|
history: { fields: { on: { kind: "boolean" } } },
|
|
56996
57479
|
/** Clock fields: written, but excluded from the compare that decides
|
|
56997
57480
|
* whether persisting is worth a SQLite commit. */
|
|
56998
|
-
volatileStateFields: ["lastChangedAt"]
|
|
56999
|
-
customize: {
|
|
57000
|
-
version: 1,
|
|
57001
|
-
advanced: "derive",
|
|
57002
|
-
targets: []
|
|
57003
|
-
}
|
|
57481
|
+
volatileStateFields: ["lastChangedAt"]
|
|
57004
57482
|
};
|
|
57005
57483
|
/**
|
|
57006
57484
|
* Dimmable-light brightness control. Co-exists with `switch` on the
|
|
@@ -57016,7 +57494,7 @@ var binaryCapability = {
|
|
|
57016
57494
|
*/
|
|
57017
57495
|
var BrightnessStatusSchema = object({
|
|
57018
57496
|
/** Current level as 0..100 inclusive. Firmware-reported. */
|
|
57019
|
-
percentage: number().min(0).max(100),
|
|
57497
|
+
percentage: number().min(0).max(100).meta(readingMeta("percentage")),
|
|
57020
57498
|
/** Ms epoch of the last operator-driven change. Useful for UI freshness. */
|
|
57021
57499
|
lastChangedAt: number()
|
|
57022
57500
|
});
|
|
@@ -57470,21 +57948,21 @@ var ClimateControlStatusSchema = object({
|
|
|
57470
57948
|
/** Single setpoint in Celsius. Used by single-target modes
|
|
57471
57949
|
* (heat / cool / dry). Null when the device is in a range mode
|
|
57472
57950
|
* (`heat_cool`) or has no setpoint at all (`fan_only`/`off`). */
|
|
57473
|
-
target: number().nullable(),
|
|
57951
|
+
target: number().nullable().meta(readingMeta("temperature")),
|
|
57474
57952
|
/** Upper bound of the dual setpoint in Celsius. Populated only
|
|
57475
57953
|
* in `heat_cool` mode. */
|
|
57476
|
-
targetHigh: number().nullable(),
|
|
57954
|
+
targetHigh: number().nullable().meta(readingMeta("temperature")),
|
|
57477
57955
|
/** Lower bound of the dual setpoint in Celsius. Populated only
|
|
57478
57956
|
* in `heat_cool` mode. */
|
|
57479
|
-
targetLow: number().nullable(),
|
|
57957
|
+
targetLow: number().nullable().meta(readingMeta("temperature")),
|
|
57480
57958
|
/** Target relative humidity (0..100). Null when the device has no
|
|
57481
57959
|
* humidity control. */
|
|
57482
|
-
targetHumidity: number().min(0).max(100).nullable(),
|
|
57960
|
+
targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
|
|
57483
57961
|
/** Current measured temperature in Celsius. Read-only — pushed by
|
|
57484
57962
|
* the device's internal sensor. */
|
|
57485
|
-
currentTemp: number().nullable(),
|
|
57963
|
+
currentTemp: number().nullable().meta(readingMeta("temperature")),
|
|
57486
57964
|
/** Current measured relative humidity (0..100). Read-only. */
|
|
57487
|
-
currentHumidity: number().min(0).max(100).nullable(),
|
|
57965
|
+
currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
|
|
57488
57966
|
/** Vertical louver swing on/off. Null when the device has no vertical
|
|
57489
57967
|
* swing surface (gate with `DeviceFeature.ClimateSwingVertical`). */
|
|
57490
57968
|
swingVertical: boolean().nullable(),
|
|
@@ -57492,11 +57970,11 @@ var ClimateControlStatusSchema = object({
|
|
|
57492
57970
|
* horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
|
|
57493
57971
|
swingHorizontal: boolean().nullable(),
|
|
57494
57972
|
/** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
|
|
57495
|
-
minTemp: number().nullable().optional(),
|
|
57973
|
+
minTemp: number().nullable().optional().meta(readingMeta("temperature")),
|
|
57496
57974
|
/** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
|
|
57497
|
-
maxTemp: number().nullable().optional(),
|
|
57975
|
+
maxTemp: number().nullable().optional().meta(readingMeta("temperature")),
|
|
57498
57976
|
/** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
|
|
57499
|
-
tempStep: number().positive().nullable().optional(),
|
|
57977
|
+
tempStep: number().positive().nullable().optional().meta(readingMeta("temperature")),
|
|
57500
57978
|
/** Ms epoch when the slice was last updated (push or command). */
|
|
57501
57979
|
lastFetchedAt: number()
|
|
57502
57980
|
});
|
|
@@ -57638,17 +58116,17 @@ var climateControlCapability = {
|
|
|
57638
58116
|
* modes to expose in the picker without a slice fetch.
|
|
57639
58117
|
*/
|
|
57640
58118
|
var RgbTripletSchema = object({
|
|
57641
|
-
r: number().int().min(0).max(255),
|
|
57642
|
-
g: number().int().min(0).max(255),
|
|
57643
|
-
b: number().int().min(0).max(255)
|
|
58119
|
+
r: number().int().min(0).max(255).meta(readingMeta("none")),
|
|
58120
|
+
g: number().int().min(0).max(255).meta(readingMeta("none")),
|
|
58121
|
+
b: number().int().min(0).max(255).meta(readingMeta("none"))
|
|
57644
58122
|
});
|
|
57645
58123
|
var HsvTripletSchema = object({
|
|
57646
58124
|
/** Hue in degrees, 0..360. */
|
|
57647
|
-
h: number().min(0).max(360),
|
|
58125
|
+
h: number().min(0).max(360).meta(readingMeta("none")),
|
|
57648
58126
|
/** Saturation as 0..100 inclusive. */
|
|
57649
|
-
s: number().min(0).max(100),
|
|
58127
|
+
s: number().min(0).max(100).meta(readingMeta("none")),
|
|
57650
58128
|
/** Value/brightness as 0..100 inclusive. */
|
|
57651
|
-
v: number().min(0).max(100)
|
|
58129
|
+
v: number().min(0).max(100).meta(readingMeta("none"))
|
|
57652
58130
|
});
|
|
57653
58131
|
var ColorInputSchema = discriminatedUnion("mode", [
|
|
57654
58132
|
object({
|
|
@@ -57677,7 +58155,7 @@ var ColorStatusSchema = object({
|
|
|
57677
58155
|
/** Populated when `mode === 'hsv'`. */
|
|
57678
58156
|
hsv: HsvTripletSchema.optional(),
|
|
57679
58157
|
/** Populated when `mode === 'mired'`. */
|
|
57680
|
-
mireds: number().int().optional(),
|
|
58158
|
+
mireds: number().int().optional().meta(readingMeta("colour-temperature")),
|
|
57681
58159
|
/** Ms epoch of the last operator-driven change. */
|
|
57682
58160
|
lastChangedAt: number()
|
|
57683
58161
|
});
|
|
@@ -57872,18 +58350,12 @@ _enum([
|
|
|
57872
58350
|
"empty",
|
|
57873
58351
|
"full"
|
|
57874
58352
|
]);
|
|
57875
|
-
/**
|
|
57876
|
-
|
|
57877
|
-
|
|
57878
|
-
"days",
|
|
57879
|
-
"hours",
|
|
57880
|
-
"cycles"
|
|
57881
|
-
]);
|
|
57882
|
-
/** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
|
|
57883
|
-
* days). `value` null = unknown (D393). */
|
|
58353
|
+
/** Whether `remaining.value` is a duration in seconds or a dimensionless count. */
|
|
58354
|
+
var ConsumableRemainingKindSchema = _enum(["duration", "count"]);
|
|
58355
|
+
/** Remaining life. `value` null = unknown (D393). Duration is seconds. */
|
|
57884
58356
|
var ConsumableRemainingSchema = object({
|
|
57885
|
-
value: number().min(0).nullable(),
|
|
57886
|
-
|
|
58357
|
+
value: number().min(0).nullable().meta(readingMeta("device")),
|
|
58358
|
+
kind: ConsumableRemainingKindSchema
|
|
57887
58359
|
});
|
|
57888
58360
|
/** A single consumable item. Either a continuous `level` (remaining
|
|
57889
58361
|
* life %, or fill % for a `supply` / `waste` item) or a discrete `status`
|
|
@@ -57896,7 +58368,7 @@ var ConsumableItemSchema = object({
|
|
|
57896
58368
|
/** Display name. */
|
|
57897
58369
|
label: string().min(1),
|
|
57898
58370
|
/** Remaining life % (`wear`) or fill % (`supply`, `waste`) when known (0..100). */
|
|
57899
|
-
level: number().min(0).max(100).nullable(),
|
|
58371
|
+
level: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
57900
58372
|
/** The provider's discrete verdict when known (binary mode). */
|
|
57901
58373
|
status: ConsumableStatusSchema.nullable(),
|
|
57902
58374
|
/** What the item is — decides whether low or full is bad. Absent = `wear`. */
|
|
@@ -57978,8 +58450,8 @@ var consumablesCustomize = {
|
|
|
57978
58450
|
transforms: [{ kind: "identity" }]
|
|
57979
58451
|
}],
|
|
57980
58452
|
literals: [{
|
|
57981
|
-
path: "remaining.
|
|
57982
|
-
values: [...
|
|
58453
|
+
path: "remaining.kind",
|
|
58454
|
+
values: [...ConsumableRemainingKindSchema.options]
|
|
57983
58455
|
}]
|
|
57984
58456
|
},
|
|
57985
58457
|
{
|
|
@@ -58108,7 +58580,6 @@ reset: method(object({
|
|
|
58108
58580
|
},
|
|
58109
58581
|
"remaining.value": {
|
|
58110
58582
|
kind: "gauge",
|
|
58111
|
-
unitFrom: "remaining.unit",
|
|
58112
58583
|
deadband: 1,
|
|
58113
58584
|
displayName: "Remaining"
|
|
58114
58585
|
}
|
|
@@ -58178,10 +58649,9 @@ var contactCapability = {
|
|
|
58178
58649
|
* (empty array for the other kinds). It lives in the slice because
|
|
58179
58650
|
* HA `select.*` entities may update their options at runtime.
|
|
58180
58651
|
*
|
|
58181
|
-
*
|
|
58182
|
-
*
|
|
58183
|
-
*
|
|
58184
|
-
* push — consistent with `options` and the sensor-unit approach.
|
|
58652
|
+
* Numeric `min` / `max` / `step` live in the slice and share the device
|
|
58653
|
+
* quantity (`SourceInfo.quantity`, D944). Display precision is a read-time
|
|
58654
|
+
* preference. A datetime control's shape is `SourceInfo.dateTimeShape`.
|
|
58185
58655
|
*/
|
|
58186
58656
|
var ControlStatusSchema = object({
|
|
58187
58657
|
kind: _enum([
|
|
@@ -58193,39 +58663,17 @@ var ControlStatusSchema = object({
|
|
|
58193
58663
|
/** Current value. Numeric kind → number; select/text/datetime → string.
|
|
58194
58664
|
* Datetime values are ISO 8601 strings (full timestamp, date-only,
|
|
58195
58665
|
* or time-only — HA accepts all three forms). */
|
|
58196
|
-
value: union([number(), string()]),
|
|
58666
|
+
value: union([number().meta(readingMeta("device")), string()]),
|
|
58197
58667
|
/** Acceptable values when `kind === 'select'`. Empty for other kinds. */
|
|
58198
58668
|
options: array(string()),
|
|
58199
58669
|
/** Ms epoch when the slice was last updated. */
|
|
58200
58670
|
lastChangedAt: number(),
|
|
58201
|
-
/**
|
|
58202
|
-
|
|
58203
|
-
|
|
58204
|
-
|
|
58205
|
-
/**
|
|
58206
|
-
|
|
58207
|
-
min: number().optional(),
|
|
58208
|
-
/** Maximum allowed value. Populated live from HA `attributes.max`.
|
|
58209
|
-
* Meaningful for `kind === 'numeric'`; absent for other kinds. */
|
|
58210
|
-
max: number().optional(),
|
|
58211
|
-
/** Step increment. Populated live from HA `attributes.step`.
|
|
58212
|
-
* Meaningful for `kind === 'numeric'`; absent for other kinds. */
|
|
58213
|
-
step: number().optional(),
|
|
58214
|
-
/** Suggested decimal places for numeric display. Populated live from HA
|
|
58215
|
-
* `attributes.suggested_display_precision`, or derived from `step` when the
|
|
58216
|
-
* HA attribute is absent. Meaningful for `kind === 'numeric'`; absent for
|
|
58217
|
-
* other kinds. Falls back to auto-formatting when absent. */
|
|
58218
|
-
precision: number().int().min(0).max(10).optional(),
|
|
58219
|
-
/** Date-picker granularity for `kind === 'datetime'`. 'date' → date-only,
|
|
58220
|
-
* 'time' → time-only, 'datetime' → full timestamp. Absent for other kinds
|
|
58221
|
-
* (UI defaults to 'datetime'). Derived live from the device's HA domain
|
|
58222
|
-
* (date.* / time.* / datetime.*) or, for input_datetime, from the entity's
|
|
58223
|
-
* `has_date`/`has_time` attributes on each push. */
|
|
58224
|
-
format: _enum([
|
|
58225
|
-
"date",
|
|
58226
|
-
"time",
|
|
58227
|
-
"datetime"
|
|
58228
|
-
]).optional()
|
|
58671
|
+
/** Minimum allowed value, in the device quantity's base unit. */
|
|
58672
|
+
min: number().optional().meta(readingMeta("device")),
|
|
58673
|
+
/** Maximum allowed value, in the device quantity's base unit. */
|
|
58674
|
+
max: number().optional().meta(readingMeta("device")),
|
|
58675
|
+
/** Step increment, in the device quantity's base unit. */
|
|
58676
|
+
step: number().optional().meta(readingMeta("device"))
|
|
58229
58677
|
});
|
|
58230
58678
|
var ControlSetValueInputSchema = discriminatedUnion("kind", [
|
|
58231
58679
|
object({
|
|
@@ -58291,9 +58739,9 @@ var CoverStatusSchema = object({
|
|
|
58291
58739
|
]),
|
|
58292
58740
|
/** 0 = fully closed, 100 = fully open. Null when the device has
|
|
58293
58741
|
* no intermediate position surface. */
|
|
58294
|
-
position: number().min(0).max(100).nullable(),
|
|
58742
|
+
position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
58295
58743
|
/** 0 = closed slats, 100 = open slats. Null when no tilt support. */
|
|
58296
|
-
tiltPosition: number().min(0).max(100).nullable(),
|
|
58744
|
+
tiltPosition: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
58297
58745
|
/** Ms epoch when the slice was last updated. */
|
|
58298
58746
|
lastChangedAt: number()
|
|
58299
58747
|
});
|
|
@@ -58396,9 +58844,9 @@ var NormalizedRangeSchema$1 = object({
|
|
|
58396
58844
|
var DayNightStatusSchema = object({
|
|
58397
58845
|
mode: DayNightModeSchema,
|
|
58398
58846
|
/** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
|
|
58399
|
-
sensitivity: number().optional(),
|
|
58847
|
+
sensitivity: number().optional().meta(readingMeta("none")),
|
|
58400
58848
|
/** Delay before the IR-cut filter flips, in seconds. */
|
|
58401
|
-
switchDelaySec: number().optional(),
|
|
58849
|
+
switchDelaySec: number().optional().meta(readingMeta("duration")),
|
|
58402
58850
|
lastFetchedAt: number()
|
|
58403
58851
|
});
|
|
58404
58852
|
/**
|
|
@@ -58616,11 +59064,11 @@ var DishwasherStatusSchema = object({
|
|
|
58616
59064
|
/** Feature keys of the programs the appliance currently offers — drives the UI selector. */
|
|
58617
59065
|
availablePrograms: array(string()),
|
|
58618
59066
|
/** Seconds remaining on the active cycle, or null when not running / not known. */
|
|
58619
|
-
remainingSeconds: number().int().nonnegative().nullable(),
|
|
59067
|
+
remainingSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
|
|
58620
59068
|
/** Seconds the delayed start is set to (time until the cycle starts), or null. */
|
|
58621
|
-
delayedStartSeconds: number().int().nonnegative().nullable(),
|
|
59069
|
+
delayedStartSeconds: number().int().nonnegative().nullable().meta(readingMeta("duration")),
|
|
58622
59070
|
/** Cycle completion 0..100, or null when not applicable. */
|
|
58623
|
-
progressPercent: number().min(0).max(100).nullable(),
|
|
59071
|
+
progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
58624
59072
|
/** True when the door is reported open; null when the door state is not known. */
|
|
58625
59073
|
doorOpen: boolean().nullable(),
|
|
58626
59074
|
/** True when the door is latched (locked) during a cycle; null when not reported. */
|
|
@@ -58644,7 +59092,7 @@ var DishwasherStatusSchema = object({
|
|
|
58644
59092
|
/** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
|
|
58645
59093
|
machineCareDue: boolean().nullable(),
|
|
58646
59094
|
/** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
|
|
58647
|
-
machineCareRemainingRuns: number().int().nonnegative().nullable(),
|
|
59095
|
+
machineCareRemainingRuns: number().int().nonnegative().nullable().meta(readingMeta("count")),
|
|
58648
59096
|
/** Option states: null when the appliance does not expose the option or has not said. */
|
|
58649
59097
|
halfLoad: boolean().nullable(),
|
|
58650
59098
|
hygienePlus: boolean().nullable(),
|
|
@@ -58659,7 +59107,7 @@ var DishwasherStatusSchema = object({
|
|
|
58659
59107
|
/** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
|
|
58660
59108
|
activeEvents: array(string()),
|
|
58661
59109
|
/** Numeric error code when the appliance reports one; null otherwise. */
|
|
58662
|
-
errorCode: number().nullable(),
|
|
59110
|
+
errorCode: number().nullable().meta(readingMeta("none")),
|
|
58663
59111
|
/** Name of the raised alert-level event while the appliance is in error; null otherwise. */
|
|
58664
59112
|
errorLabel: string().nullable(),
|
|
58665
59113
|
/** Ms epoch of the last value received from the appliance; 0 = never. */
|
|
@@ -58890,7 +59338,7 @@ var DoorbellStatusSchema = object({
|
|
|
58890
59338
|
/** Ms epoch of the last press. null = never observed since this provider started. */
|
|
58891
59339
|
lastPressedAt: number().nullable(),
|
|
58892
59340
|
/** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
|
|
58893
|
-
pressCountSinceStart: number()
|
|
59341
|
+
pressCountSinceStart: number().meta(readingMeta("count"))
|
|
58894
59342
|
});
|
|
58895
59343
|
var DoorbellPressEventSchema = object({
|
|
58896
59344
|
deviceId: number(),
|
|
@@ -58955,17 +59403,7 @@ onPressed: {
|
|
|
58955
59403
|
history: "none",
|
|
58956
59404
|
customize: doorbellCustomize
|
|
58957
59405
|
};
|
|
58958
|
-
|
|
58959
|
-
* Enum-state sensor — a string value picked from a finite option set.
|
|
58960
|
-
* Drives HA `sensor` entries with `state_class: enum` (HVAC action
|
|
58961
|
-
* states, weather conditions, contact-source channels, mode strings).
|
|
58962
|
-
* The option set is stable across observations; it is persisted in the
|
|
58963
|
-
* device config blob (via the control slice at adoption) and is NOT
|
|
58964
|
-
* stored in `sourceInfo`.
|
|
58965
|
-
*/
|
|
58966
|
-
/** Locale-render shape for an ISO `value` carried by a `DateTimeSensor`-role
|
|
58967
|
-
* enum sensor: `date` (date-only), `time` (time-only), `datetime` (both). */
|
|
58968
|
-
var EnumSensorDateTimeFormatSchema = _enum([
|
|
59406
|
+
_enum([
|
|
58969
59407
|
"date",
|
|
58970
59408
|
"time",
|
|
58971
59409
|
"datetime"
|
|
@@ -58973,12 +59411,11 @@ var EnumSensorDateTimeFormatSchema = _enum([
|
|
|
58973
59411
|
var EnumSensorStatusSchema = object({
|
|
58974
59412
|
value: string(),
|
|
58975
59413
|
/**
|
|
58976
|
-
* Set
|
|
58977
|
-
* as
|
|
58978
|
-
*
|
|
58979
|
-
* a time-only sensor → `'time'`.
|
|
59414
|
+
* Set when `value` is a timestamp rather than an enum state, so history
|
|
59415
|
+
* does not learn ISO strings as states. The display shape (date / time /
|
|
59416
|
+
* datetime) lives on `SourceInfo.dateTimeShape`, written once at adopt.
|
|
58980
59417
|
*/
|
|
58981
|
-
|
|
59418
|
+
timestamp: boolean().optional(),
|
|
58982
59419
|
/** Ms epoch when the slice was last updated. */
|
|
58983
59420
|
lastFetchedAt: number()
|
|
58984
59421
|
});
|
|
@@ -59005,11 +59442,11 @@ var enumSensorCapability = {
|
|
|
59005
59442
|
* History (D850): the value as an `enum` series (an appliance's program or
|
|
59006
59443
|
* operation state, an HVAC action). The option set is per device, so the
|
|
59007
59444
|
* value list is LEARNED as values are first seen (bounded). A sensor with a
|
|
59008
|
-
* `
|
|
59445
|
+
* `timestamp` carries a clock, not a state, and is not recorded.
|
|
59009
59446
|
*/
|
|
59010
59447
|
history: { fields: { value: {
|
|
59011
59448
|
kind: "enum",
|
|
59012
|
-
skipWhenSet: "
|
|
59449
|
+
skipWhenSet: "timestamp"
|
|
59013
59450
|
} } },
|
|
59014
59451
|
/** Clock fields: written, but excluded from the compare that decides
|
|
59015
59452
|
* whether persisting is worth a SQLite commit. */
|
|
@@ -59029,13 +59466,13 @@ var EventFireSchema = object({
|
|
|
59029
59466
|
deviceId: number(),
|
|
59030
59467
|
eventType: string(),
|
|
59031
59468
|
data: record(string(), unknown()).nullable(),
|
|
59032
|
-
timestamp: number(),
|
|
59033
|
-
seq: number()
|
|
59469
|
+
timestamp: number().meta(readingMeta("none")),
|
|
59470
|
+
seq: number().meta(readingMeta("count"))
|
|
59034
59471
|
});
|
|
59035
59472
|
var EventEmitterStatusSchema = object({
|
|
59036
59473
|
eventTypes: array(string()),
|
|
59037
59474
|
lastEvent: EventFireSchema.nullable(),
|
|
59038
|
-
eventCountSinceStart: number()
|
|
59475
|
+
eventCountSinceStart: number().meta(readingMeta("count"))
|
|
59039
59476
|
});
|
|
59040
59477
|
var eventEmitterCapability = {
|
|
59041
59478
|
name: "event-emitter",
|
|
@@ -59448,10 +59885,10 @@ var FanDirectionSchema = _enum(["forward", "reverse"]);
|
|
|
59448
59885
|
var FanControlStatusSchema = object({
|
|
59449
59886
|
/** Active speed as 0..100 inclusive. Null when the device has no
|
|
59450
59887
|
* speed surface (single-speed fan). */
|
|
59451
|
-
percentage: number().min(0).max(100).nullable(),
|
|
59888
|
+
percentage: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
59452
59889
|
/** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
|
|
59453
59890
|
* a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
|
|
59454
|
-
percentageStep: number().positive().optional(),
|
|
59891
|
+
percentageStep: number().positive().optional().meta(readingMeta("percentage")),
|
|
59455
59892
|
/** Active preset mode (`auto` / `quiet` / `turbo` / vendor-specific)
|
|
59456
59893
|
* — empty string when no preset is active or supported. */
|
|
59457
59894
|
preset: string(),
|
|
@@ -59572,7 +60009,7 @@ var FeatureProbeStatusSchema = object({
|
|
|
59572
60009
|
/** Camera/firmware model string. `null` when the firmware doesn't expose it. */
|
|
59573
60010
|
model: string().nullable(),
|
|
59574
60011
|
/** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
|
|
59575
|
-
channelCount: number().nullable(),
|
|
60012
|
+
channelCount: number().nullable().meta(readingMeta("count")),
|
|
59576
60013
|
/**
|
|
59577
60014
|
* Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
|
|
59578
60015
|
* completes — drivers' `getAccessoryChildren()` should treat zero as
|
|
@@ -59695,9 +60132,91 @@ var gasCapability = {
|
|
|
59695
60132
|
* whether persisting is worth a SQLite commit. */
|
|
59696
60133
|
volatileStateFields: ["lastChangedAt"]
|
|
59697
60134
|
};
|
|
60135
|
+
var GroupCommandsSchema = object({
|
|
60136
|
+
/** One row per member that has been commanded. A later command replaces that member's row. */
|
|
60137
|
+
results: array(object({
|
|
60138
|
+
deviceId: number().int().nonnegative(),
|
|
60139
|
+
cap: string().min(1),
|
|
60140
|
+
method: string().min(1),
|
|
60141
|
+
ok: boolean(),
|
|
60142
|
+
error: string().optional(),
|
|
60143
|
+
/** Ms epoch of the attempt. */
|
|
60144
|
+
at: number()
|
|
60145
|
+
})) });
|
|
60146
|
+
var groupCommandsCapability = {
|
|
60147
|
+
name: "group-commands",
|
|
60148
|
+
scope: "device",
|
|
60149
|
+
deviceNative: true,
|
|
60150
|
+
mode: "singleton",
|
|
60151
|
+
deviceTypes: [DeviceType.Switch, DeviceType.Light],
|
|
60152
|
+
methods: {},
|
|
60153
|
+
status: {
|
|
60154
|
+
schema: GroupCommandsSchema,
|
|
60155
|
+
kind: "push"
|
|
60156
|
+
},
|
|
60157
|
+
runtimeState: GroupCommandsSchema,
|
|
60158
|
+
/**
|
|
60159
|
+
* Runtime-state durability: **session** — the last attempt. A restored
|
|
60160
|
+
* failure would outlive the command that caused it.
|
|
60161
|
+
*
|
|
60162
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
60163
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
60164
|
+
*/
|
|
60165
|
+
durability: "session",
|
|
60166
|
+
history: "none"
|
|
60167
|
+
};
|
|
60168
|
+
/**
|
|
60169
|
+
* How many members of a helper group answered (D944). Counts are integers,
|
|
60170
|
+
* not measurements: zero included is a real count, never a stand-in reading.
|
|
60171
|
+
* The reading itself stays on the output capability. History is none — the
|
|
60172
|
+
* series that later statistics read is the output cap's own history block.
|
|
60173
|
+
*/
|
|
60174
|
+
var GroupStatusSchema = object({
|
|
60175
|
+
/** Members whose current value entered the aggregate. */
|
|
60176
|
+
included: number().int().nonnegative().meta(readingMeta("count", {
|
|
60177
|
+
precision: 0,
|
|
60178
|
+
label: "Reporting"
|
|
60179
|
+
})),
|
|
60180
|
+
/** Members left out: offline, unknown, or not a finite reading (D393). */
|
|
60181
|
+
excluded: number().int().nonnegative().meta(readingMeta("count", {
|
|
60182
|
+
precision: 0,
|
|
60183
|
+
label: "Excluded"
|
|
60184
|
+
})),
|
|
60185
|
+
/** `included` + `excluded`. */
|
|
60186
|
+
total: number().int().nonnegative().meta(readingMeta("count", {
|
|
60187
|
+
precision: 0,
|
|
60188
|
+
label: "Members"
|
|
60189
|
+
}))
|
|
60190
|
+
});
|
|
60191
|
+
var groupStatusCapability = {
|
|
60192
|
+
name: "group-status",
|
|
60193
|
+
scope: "device",
|
|
60194
|
+
deviceNative: true,
|
|
60195
|
+
mode: "singleton",
|
|
60196
|
+
deviceTypes: [
|
|
60197
|
+
DeviceType.Sensor,
|
|
60198
|
+
DeviceType.Switch,
|
|
60199
|
+
DeviceType.Light
|
|
60200
|
+
],
|
|
60201
|
+
methods: {},
|
|
60202
|
+
status: {
|
|
60203
|
+
schema: GroupStatusSchema,
|
|
60204
|
+
kind: "push"
|
|
60205
|
+
},
|
|
60206
|
+
runtimeState: GroupStatusSchema,
|
|
60207
|
+
/**
|
|
60208
|
+
* Runtime-state durability: **session** — recomputed from members on the
|
|
60209
|
+
* next source event; a restored count would outlive the readings it named.
|
|
60210
|
+
*
|
|
60211
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
60212
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
60213
|
+
*/
|
|
60214
|
+
durability: "session",
|
|
60215
|
+
history: "none"
|
|
60216
|
+
};
|
|
59698
60217
|
var HeatingValveStatusSchema = object({
|
|
59699
60218
|
/** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
|
|
59700
|
-
position: number().min(0).max(100).nullable(),
|
|
60219
|
+
position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
59701
60220
|
/** Ms epoch when the slice was last updated (push or poll). */
|
|
59702
60221
|
lastFetchedAt: number()
|
|
59703
60222
|
});
|
|
@@ -59740,9 +60259,9 @@ var HumidifierStatusSchema = object({
|
|
|
59740
60259
|
/** Whether the humidifier is currently on. */
|
|
59741
60260
|
on: boolean(),
|
|
59742
60261
|
/** Current measured relative humidity (0..100). Null when not reported. */
|
|
59743
|
-
currentHumidity: number().min(0).max(100).nullable(),
|
|
60262
|
+
currentHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
|
|
59744
60263
|
/** Target relative humidity (0..100). Null when no setpoint surface. */
|
|
59745
|
-
targetHumidity: number().min(0).max(100).nullable(),
|
|
60264
|
+
targetHumidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
|
|
59746
60265
|
/** Active mode (`auto` / `normal` / `baby` / vendor). Null when the
|
|
59747
60266
|
* device has no mode surface. */
|
|
59748
60267
|
mode: string().nullable(),
|
|
@@ -59752,9 +60271,9 @@ var HumidifierStatusSchema = object({
|
|
|
59752
60271
|
* `idle` / `off`). Null when the device doesn't report it. */
|
|
59753
60272
|
action: string().nullable(),
|
|
59754
60273
|
/** HA `min_humidity` attribute. Null → UI uses 0. */
|
|
59755
|
-
minHumidity: number().nullable(),
|
|
60274
|
+
minHumidity: number().nullable().meta(readingMeta("humidity")),
|
|
59756
60275
|
/** HA `max_humidity` attribute. Null → UI uses 100. */
|
|
59757
|
-
maxHumidity: number().nullable(),
|
|
60276
|
+
maxHumidity: number().nullable().meta(readingMeta("humidity")),
|
|
59758
60277
|
/** Ms epoch when the slice was last updated. */
|
|
59759
60278
|
lastChangedAt: number()
|
|
59760
60279
|
});
|
|
@@ -59824,22 +60343,13 @@ var humidifierCapability = {
|
|
|
59824
60343
|
* `sourceInfo`.
|
|
59825
60344
|
*/
|
|
59826
60345
|
var HumiditySensorStatusSchema = object({
|
|
59827
|
-
/** Current relative humidity, 0..100. */
|
|
59828
|
-
percent: number().min(0).max(100).meta({
|
|
59829
|
-
|
|
60346
|
+
/** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
|
|
60347
|
+
percent: number().min(0).max(100).nullable().meta(readingMeta("humidity", {
|
|
60348
|
+
precision: 0,
|
|
59830
60349
|
label: "Humidity"
|
|
59831
|
-
}),
|
|
60350
|
+
})),
|
|
59832
60351
|
/** Ms epoch when the slice was last updated. */
|
|
59833
|
-
lastFetchedAt: number()
|
|
59834
|
-
/** Live display unit from the upstream source (e.g. HA
|
|
59835
|
-
* `attributes.unit_of_measurement`). The UI prefers this over the
|
|
59836
|
-
* role's canonical unit. Absent → fall back to the canonical unit. */
|
|
59837
|
-
unit: string().optional(),
|
|
59838
|
-
/** Suggested decimal places for numeric display.
|
|
59839
|
-
* Populated live from the upstream source when provided (e.g. HA
|
|
59840
|
-
* `attributes.suggested_display_precision`). Falls back to
|
|
59841
|
-
* auto-formatting when absent. */
|
|
59842
|
-
precision: number().int().min(0).max(10).optional()
|
|
60352
|
+
lastFetchedAt: number()
|
|
59843
60353
|
});
|
|
59844
60354
|
var humiditySensorCapability = {
|
|
59845
60355
|
name: "humidity-sensor",
|
|
@@ -59870,12 +60380,7 @@ var humiditySensorCapability = {
|
|
|
59870
60380
|
} } },
|
|
59871
60381
|
/** Clock fields: written, but excluded from the compare that decides
|
|
59872
60382
|
* whether persisting is worth a SQLite commit. */
|
|
59873
|
-
volatileStateFields: ["lastFetchedAt"]
|
|
59874
|
-
customize: {
|
|
59875
|
-
version: 1,
|
|
59876
|
-
advanced: "derive",
|
|
59877
|
-
targets: []
|
|
59878
|
-
}
|
|
60383
|
+
volatileStateFields: ["lastFetchedAt"]
|
|
59879
60384
|
};
|
|
59880
60385
|
/**
|
|
59881
60386
|
* Image display cap. Models a single still image exposed by an integration —
|
|
@@ -59896,7 +60401,7 @@ var ImageStatusSchema = object({
|
|
|
59896
60401
|
* entity exposes no `entity_picture` (yet). */
|
|
59897
60402
|
url: string().nullable(),
|
|
59898
60403
|
/** Ms epoch of the upstream last-updated timestamp. Null at cold-start. */
|
|
59899
|
-
lastUpdated: number().nullable()
|
|
60404
|
+
lastUpdated: number().nullable().meta(readingMeta("none"))
|
|
59900
60405
|
});
|
|
59901
60406
|
var imageCapability = {
|
|
59902
60407
|
name: "image",
|
|
@@ -59984,19 +60489,19 @@ var NormalizedRangeSchema = object({
|
|
|
59984
60489
|
*/
|
|
59985
60490
|
var ImageSettingsStatusSchema = object({
|
|
59986
60491
|
/** Normalized 0–100. */
|
|
59987
|
-
brightness: number().optional(),
|
|
60492
|
+
brightness: number().optional().meta(readingMeta("none")),
|
|
59988
60493
|
/** Normalized 0–100. */
|
|
59989
|
-
contrast: number().optional(),
|
|
60494
|
+
contrast: number().optional().meta(readingMeta("none")),
|
|
59990
60495
|
/** Normalized 0–100. */
|
|
59991
|
-
saturation: number().optional(),
|
|
60496
|
+
saturation: number().optional().meta(readingMeta("none")),
|
|
59992
60497
|
/** Normalized 0–100. */
|
|
59993
|
-
sharpness: number().optional(),
|
|
60498
|
+
sharpness: number().optional().meta(readingMeta("none")),
|
|
59994
60499
|
mirror: boolean().optional(),
|
|
59995
60500
|
flip: boolean().optional(),
|
|
59996
60501
|
rotate: ImageRotateSchema.optional(),
|
|
59997
60502
|
whiteBalance: WhiteBalanceModeSchema.optional(),
|
|
59998
60503
|
/** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
|
|
59999
|
-
warmth: number().optional(),
|
|
60504
|
+
warmth: number().optional().meta(readingMeta("none")),
|
|
60000
60505
|
exposureMode: ExposureModeSchema.optional(),
|
|
60001
60506
|
backlightMode: BacklightModeSchema.optional(),
|
|
60002
60507
|
lastFetchedAt: number()
|
|
@@ -60275,10 +60780,10 @@ var IntercomAbilitySchema = object({
|
|
|
60275
60780
|
/** Codecs accepted by firmware, in preference order. Driver-authored strings. */
|
|
60276
60781
|
codecs: array(string()),
|
|
60277
60782
|
/** Sample rate (Hz) the firmware expects. Reolink typical: 16000. */
|
|
60278
|
-
sampleRate: number(),
|
|
60783
|
+
sampleRate: number().meta(readingMeta("none")),
|
|
60279
60784
|
duplex: _enum(["half", "full"]),
|
|
60280
60785
|
/** Max in-flight audio backlog before drop (ms). Server uses this to bound latency. */
|
|
60281
|
-
maxBacklogMs: number()
|
|
60786
|
+
maxBacklogMs: number().meta(readingMeta("duration-ms"))
|
|
60282
60787
|
});
|
|
60283
60788
|
var IntercomStatusSchema = object({
|
|
60284
60789
|
/** True while a talk session is open. */
|
|
@@ -60505,13 +61010,13 @@ var LawnMowerControlStatusSchema = object({
|
|
|
60505
61010
|
activity: LawnMowerActivitySchema,
|
|
60506
61011
|
/** 0..100 battery percentage. Null when the device has no battery
|
|
60507
61012
|
* reading. */
|
|
60508
|
-
batteryLevel: number().min(0).max(100).nullable(),
|
|
61013
|
+
batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
60509
61014
|
/** 0..100 mowing-completion percentage of the current task, or null when no
|
|
60510
61015
|
* task is active / progress is unavailable. */
|
|
60511
|
-
progressPercent: number().min(0).max(100).nullable(),
|
|
61016
|
+
progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
60512
61017
|
/** Current device/event code (dynamic — mostly status, sometimes an error),
|
|
60513
61018
|
* or null when unknown. */
|
|
60514
|
-
currentCode: number().nullable(),
|
|
61019
|
+
currentCode: number().nullable().meta(readingMeta("none")),
|
|
60515
61020
|
/** Human label for {@link currentCode}, or null when undecodable. */
|
|
60516
61021
|
currentCodeLabel: string().nullable(),
|
|
60517
61022
|
/** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
|
|
@@ -61044,7 +61549,7 @@ var MediaPlayerStatusSchema = object({
|
|
|
61044
61549
|
state: MediaPlayerStateSchema,
|
|
61045
61550
|
/** Volume as 0..100 inclusive. Null when the device has no volume
|
|
61046
61551
|
* surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
|
|
61047
|
-
volumeLevel: number().min(0).max(100).nullable(),
|
|
61552
|
+
volumeLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
61048
61553
|
/** Mute toggle distinct from volume=0. Null when no mute surface.
|
|
61049
61554
|
* Pair with `DeviceFeature.MediaPlayerMute`. */
|
|
61050
61555
|
isMuted: boolean().nullable(),
|
|
@@ -61057,10 +61562,10 @@ var MediaPlayerStatusSchema = object({
|
|
|
61057
61562
|
currentMedia: MediaInfoSchema.nullable(),
|
|
61058
61563
|
/** Current playback position in ms. Null when not seekable or
|
|
61059
61564
|
* nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
|
|
61060
|
-
positionMs: number().int().nonnegative().nullable(),
|
|
61565
|
+
positionMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
|
|
61061
61566
|
/** Total duration of the current media in ms. Null when unknown
|
|
61062
61567
|
* (live stream) or nothing is playing. */
|
|
61063
|
-
durationMs: number().int().nonnegative().nullable(),
|
|
61568
|
+
durationMs: number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
|
|
61064
61569
|
/** Shuffle toggle. Null when no shuffle surface.
|
|
61065
61570
|
* Pair with `DeviceFeature.MediaPlayerShuffle`. */
|
|
61066
61571
|
shuffle: boolean().nullable(),
|
|
@@ -61369,7 +61874,7 @@ var motionCustomize = {
|
|
|
61369
61874
|
eventName: "onMotionChanged"
|
|
61370
61875
|
},
|
|
61371
61876
|
deviceTypes: [DeviceType.Camera],
|
|
61372
|
-
apply: "
|
|
61877
|
+
apply: "add",
|
|
61373
61878
|
applyAuthority: {
|
|
61374
61879
|
kind: "camera-sensors",
|
|
61375
61880
|
timeoutField: "motionCooldownMs"
|
|
@@ -61441,7 +61946,7 @@ var MotionStatusSchema = object({
|
|
|
61441
61946
|
* it, every provider seeded it at boot, and every session left it
|
|
61442
61947
|
* `detected: true` after its detach.
|
|
61443
61948
|
*/
|
|
61444
|
-
autoClearAfterMs: number().nullable()
|
|
61949
|
+
autoClearAfterMs: number().nullable().meta(readingMeta("duration-ms"))
|
|
61445
61950
|
});
|
|
61446
61951
|
/**
|
|
61447
61952
|
* Payload of `motion.onMotionChanged` event + the corresponding bus
|
|
@@ -61478,12 +61983,18 @@ var motionCapability = {
|
|
|
61478
61983
|
methods: {
|
|
61479
61984
|
/**
|
|
61480
61985
|
* Pull the current motion state synchronously. Convenience shortcut
|
|
61481
|
-
* for consumers that don't need the full status object
|
|
61482
|
-
*
|
|
61483
|
-
* `getStatus` once the auto-injected status surface lands in every
|
|
61484
|
-
* consumer.
|
|
61986
|
+
* for consumers that don't need the full status object. A composed
|
|
61987
|
+
* group answers from its slice (D944): unknown throws, it is not off.
|
|
61485
61988
|
*/
|
|
61486
61989
|
isDetected: method(object({ deviceId: number() }), boolean()) },
|
|
61990
|
+
/**
|
|
61991
|
+
* A composed motion group serves `isDetected` from `detected` (D944).
|
|
61992
|
+
* Without this the cap stays uncomposable (`has-commands`).
|
|
61993
|
+
*/
|
|
61994
|
+
composedMethods: { isDetected: {
|
|
61995
|
+
kind: "slice",
|
|
61996
|
+
field: "detected"
|
|
61997
|
+
} },
|
|
61487
61998
|
events: {
|
|
61488
61999
|
/**
|
|
61489
62000
|
* Fires every time the runner transitions a camera between
|
|
@@ -61623,7 +62134,7 @@ var MotionZoneRegionSchema = object({
|
|
|
61623
62134
|
* the grid region(s). */
|
|
61624
62135
|
var MotionZoneStatusSchema = object({
|
|
61625
62136
|
enabled: boolean(),
|
|
61626
|
-
sensitivity: number(),
|
|
62137
|
+
sensitivity: number().meta(readingMeta("none")),
|
|
61627
62138
|
/** Grid region(s). Today exactly one `grid` shape. */
|
|
61628
62139
|
regions: array(MotionZoneRegionSchema),
|
|
61629
62140
|
lastFetchedAt: number()
|
|
@@ -61993,7 +62504,7 @@ var NavigationStatusSchema = object({
|
|
|
61993
62504
|
following: boolean(),
|
|
61994
62505
|
flash: boolean(),
|
|
61995
62506
|
lightMode: NavigationLightModeSchema,
|
|
61996
|
-
lightLevel: number().min(40).max(100),
|
|
62507
|
+
lightLevel: number().min(40).max(100).meta(readingMeta("none")),
|
|
61997
62508
|
/** Ms epoch when the slice was last updated. */
|
|
61998
62509
|
lastChangedAt: number()
|
|
61999
62510
|
});
|
|
@@ -62127,13 +62638,13 @@ var NetworkLinkStatusSchema = object({
|
|
|
62127
62638
|
* one whose reading has not landed must not be drawn at 0 %. Consumers
|
|
62128
62639
|
* SKIP a null rather than coerce it.
|
|
62129
62640
|
*/
|
|
62130
|
-
signalPercent: number().min(0).max(100).nullable(),
|
|
62641
|
+
signalPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
62131
62642
|
/** Raw received signal strength in dBm, when the firmware reports one. */
|
|
62132
|
-
rssiDbm: number().optional(),
|
|
62643
|
+
rssiDbm: number().optional().meta(readingMeta("signal-level")),
|
|
62133
62644
|
/** Network name of a wireless link, when the firmware reports it. */
|
|
62134
62645
|
ssid: string().optional(),
|
|
62135
62646
|
/** Ms epoch of the last observation. Lets consumers reason about freshness. */
|
|
62136
|
-
lastUpdated: number()
|
|
62647
|
+
lastUpdated: number().meta(readingMeta("none"))
|
|
62137
62648
|
});
|
|
62138
62649
|
var networkLinkCapability = {
|
|
62139
62650
|
name: "network-link",
|
|
@@ -62518,7 +63029,7 @@ var NotifierStatusSchema = object({
|
|
|
62518
63029
|
lastError: string().nullable(),
|
|
62519
63030
|
/** Number of deliveries currently buffered server-side (rate-limited
|
|
62520
63031
|
* notifiers may queue). 0 when the provider sends synchronously. */
|
|
62521
|
-
queueDepth: number().int().nonnegative(),
|
|
63032
|
+
queueDepth: number().int().nonnegative().meta(readingMeta("count")),
|
|
62522
63033
|
/** Per-feature capability matrix — authoritative source for the
|
|
62523
63034
|
* compose-form field gating. */
|
|
62524
63035
|
supports: NotifierSupportsSchema
|
|
@@ -62610,11 +63121,9 @@ onSent: { data: object({
|
|
|
62610
63121
|
* cap fits (HA `sensor` whose `device_class` we don't have a typed cap
|
|
62611
63122
|
* for: water flow, distance, weight, frequency, signal strength, …).
|
|
62612
63123
|
*
|
|
62613
|
-
* The
|
|
62614
|
-
*
|
|
62615
|
-
*
|
|
62616
|
-
* The UI reads them directly from the slice — they are the single source
|
|
62617
|
-
* of truth for generic numeric rendering and do NOT live on `sourceInfo`.
|
|
63124
|
+
* The quantity is not on this slice. It is chosen when the device is
|
|
63125
|
+
* adopted and stored once on `SourceInfo.quantity` (D944). `value` is in
|
|
63126
|
+
* that quantity's base unit. `null` is a refused sample (D393).
|
|
62618
63127
|
*
|
|
62619
63128
|
* Prefer the typed alternatives (`temperature-sensor`, `humidity-sensor`,
|
|
62620
63129
|
* `ambient-light-sensor`, `pressure-sensor`, `power-meter`,
|
|
@@ -62623,15 +63132,8 @@ onSent: { data: object({
|
|
|
62623
63132
|
* numeric values land here.
|
|
62624
63133
|
*/
|
|
62625
63134
|
var NumericSensorStatusSchema = object({
|
|
62626
|
-
|
|
62627
|
-
|
|
62628
|
-
* Populated live from the upstream source on each state push.
|
|
62629
|
-
* Absent for unitless sensors. */
|
|
62630
|
-
unit: string().optional(),
|
|
62631
|
-
/** Suggested decimal places for numeric display.
|
|
62632
|
-
* Populated live from the upstream source when provided.
|
|
62633
|
-
* Falls back to auto-formatting when absent. */
|
|
62634
|
-
precision: number().int().min(0).max(10).optional(),
|
|
63135
|
+
/** Reading in the device quantity's base unit. `null` when refused (D393). */
|
|
63136
|
+
value: number().nullable().meta(readingMeta("device")),
|
|
62635
63137
|
/** Ms epoch when the slice was last updated. */
|
|
62636
63138
|
lastFetchedAt: number()
|
|
62637
63139
|
});
|
|
@@ -62655,11 +63157,11 @@ var numericSensorCapability = {
|
|
|
62655
63157
|
*/
|
|
62656
63158
|
durability: "restored",
|
|
62657
63159
|
/**
|
|
62658
|
-
* History (D850): recorded series. The unit is
|
|
63160
|
+
* History (D850): recorded series. The unit is the device quantity's base,
|
|
63161
|
+
* resolved when the series is read — the cap has no single unit.
|
|
62659
63162
|
*/
|
|
62660
63163
|
history: { fields: { value: {
|
|
62661
63164
|
kind: "gauge",
|
|
62662
|
-
unitFrom: "unit",
|
|
62663
63165
|
minIntervalMs: 3e4
|
|
62664
63166
|
} } },
|
|
62665
63167
|
/** Clock fields: written, but excluded from the compare that decides
|
|
@@ -62951,23 +63453,26 @@ var PetFeederStatusSchema = object({
|
|
|
62951
63453
|
/** Food currently in the bowl (grams). Null when the device has not
|
|
62952
63454
|
* reported a reading yet. On dual-hopper models this is the combined
|
|
62953
63455
|
* bowl reading; per-hopper levels live in `food1`/`food2`. */
|
|
62954
|
-
foodLevel: number().nullable(),
|
|
63456
|
+
foodLevel: number().nullable().meta(readingMeta("none")),
|
|
62955
63457
|
/** Hopper-1 food level (grams) on dual-hopper feeders; null on
|
|
62956
63458
|
* single-hopper models. */
|
|
62957
|
-
food1: number().nullable(),
|
|
63459
|
+
food1: number().nullable().meta(readingMeta("none")),
|
|
62958
63460
|
/** Hopper-2 food level (grams) on dual-hopper feeders; null on
|
|
62959
63461
|
* single-hopper models. */
|
|
62960
|
-
food2: number().nullable(),
|
|
63462
|
+
food2: number().nullable().meta(readingMeta("none")),
|
|
62961
63463
|
/** Derived low-food flag — mirrors the HA `food_level` binary_sensor
|
|
62962
63464
|
* (`device_class: problem`, on = low). True when the bowl is empty /
|
|
62963
63465
|
* below the feeder's low threshold. */
|
|
62964
63466
|
lowFood: boolean(),
|
|
62965
63467
|
/** Battery charge 0..100 (%). Null on mains-powered models or when the
|
|
62966
63468
|
* device has no battery reading. */
|
|
62967
|
-
batteryPower: number().min(0).max(100).nullable(),
|
|
62968
|
-
/**
|
|
62969
|
-
*
|
|
62970
|
-
|
|
63469
|
+
batteryPower: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
63470
|
+
/**
|
|
63471
|
+
* Desiccant life remaining, stored in seconds (duration base, D944).
|
|
63472
|
+
* Providers convert from the days the feeder reports. Null when the
|
|
63473
|
+
* model has no desiccant sensor.
|
|
63474
|
+
*/
|
|
63475
|
+
desiccantLeftDays: number().nullable().meta(readingMeta("duration")),
|
|
62971
63476
|
/** True while a feed is in progress. */
|
|
62972
63477
|
feeding: boolean(),
|
|
62973
63478
|
/** Decoded connectivity / power status (HA petkit device-status enum).
|
|
@@ -62978,7 +63483,7 @@ var PetFeederStatusSchema = object({
|
|
|
62978
63483
|
* with `errorCode` for consumers that want the raw integer. */
|
|
62979
63484
|
error: string().nullable(),
|
|
62980
63485
|
/** Raw device error code (0 / null = no error). */
|
|
62981
|
-
errorCode: number().nullable(),
|
|
63486
|
+
errorCode: number().nullable().meta(readingMeta("none")),
|
|
62982
63487
|
/** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
|
|
62983
63488
|
isDualHopper: boolean(),
|
|
62984
63489
|
/** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
|
|
@@ -62988,7 +63493,7 @@ var PetFeederStatusSchema = object({
|
|
|
62988
63493
|
/** Play a chime when dispensing. */
|
|
62989
63494
|
feedSound: boolean(),
|
|
62990
63495
|
/** Speaker / prompt volume level (device-scaled integer). */
|
|
62991
|
-
volume: number(),
|
|
63496
|
+
volume: number().meta(readingMeta("none")),
|
|
62992
63497
|
/** Ms epoch when the slice was last refreshed from the cloud. */
|
|
62993
63498
|
lastFetchedAt: number()
|
|
62994
63499
|
});
|
|
@@ -63107,7 +63612,7 @@ var petFeederCapability = {
|
|
|
63107
63612
|
},
|
|
63108
63613
|
desiccantLeftDays: {
|
|
63109
63614
|
kind: "gauge",
|
|
63110
|
-
unit: "
|
|
63615
|
+
unit: "s"
|
|
63111
63616
|
}
|
|
63112
63617
|
} }
|
|
63113
63618
|
};
|
|
@@ -63508,36 +64013,19 @@ method(_void(), PlatformCapabilitiesSchema), method(_void(), HardwareInfoSchema)
|
|
|
63508
64013
|
*/
|
|
63509
64014
|
var PowerMeterStatusSchema = object({
|
|
63510
64015
|
/** Instantaneous power draw in watts. */
|
|
63511
|
-
watts: number().optional().meta({
|
|
63512
|
-
unit: "W",
|
|
63513
|
-
label: "Power"
|
|
63514
|
-
}),
|
|
64016
|
+
watts: number().optional().meta(readingMeta("power", { label: "Power" })),
|
|
63515
64017
|
/** Cumulative energy in kilowatt-hours since the meter was reset. */
|
|
63516
|
-
kwhTotal: number().optional().meta({
|
|
63517
|
-
unit: "kWh",
|
|
63518
|
-
label: "Energy"
|
|
63519
|
-
}),
|
|
64018
|
+
kwhTotal: number().optional().meta(readingMeta("energy", { label: "Energy" })),
|
|
63520
64019
|
/** Voltage in volts. */
|
|
63521
|
-
volts: number().optional(),
|
|
64020
|
+
volts: number().optional().meta(readingMeta("voltage")),
|
|
63522
64021
|
/** Current in amperes. */
|
|
63523
|
-
amps: number().optional(),
|
|
63524
|
-
/** Apparent power in volt-amperes
|
|
63525
|
-
va: number().optional(),
|
|
64022
|
+
amps: number().optional().meta(readingMeta("current")),
|
|
64023
|
+
/** Apparent power in volt-amperes. */
|
|
64024
|
+
va: number().optional().meta(readingMeta("apparent-power")),
|
|
63526
64025
|
/** Power factor (0..1) if reported. */
|
|
63527
|
-
powerFactor: number().min(0).max(1).optional(),
|
|
64026
|
+
powerFactor: number().min(0).max(1).optional().meta(readingMeta("ratio")),
|
|
63528
64027
|
/** Ms epoch when the slice was last updated. */
|
|
63529
|
-
lastFetchedAt: number()
|
|
63530
|
-
/** Live display unit of the single metric this slice carries (e.g. HA
|
|
63531
|
-
* `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
|
|
63532
|
-
* Each upstream `sensor.*` entity surfaces ONE device_class, so one
|
|
63533
|
-
* unit per slice is unambiguous. The UI prefers this over the role's
|
|
63534
|
-
* canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
|
|
63535
|
-
unit: string().optional(),
|
|
63536
|
-
/** Suggested decimal places for numeric display.
|
|
63537
|
-
* Populated live from the upstream source when provided (e.g. HA
|
|
63538
|
-
* `attributes.suggested_display_precision`). Falls back to
|
|
63539
|
-
* auto-formatting when absent. */
|
|
63540
|
-
precision: number().int().min(0).max(10).optional()
|
|
64028
|
+
lastFetchedAt: number()
|
|
63541
64029
|
});
|
|
63542
64030
|
var powerMeterCapability = {
|
|
63543
64031
|
name: "power-meter",
|
|
@@ -63601,12 +64089,7 @@ var powerMeterCapability = {
|
|
|
63601
64089
|
} },
|
|
63602
64090
|
/** Clock fields: written, but excluded from the compare that decides
|
|
63603
64091
|
* whether persisting is worth a SQLite commit. */
|
|
63604
|
-
volatileStateFields: ["lastFetchedAt"]
|
|
63605
|
-
customize: {
|
|
63606
|
-
version: 1,
|
|
63607
|
-
advanced: "derive",
|
|
63608
|
-
targets: []
|
|
63609
|
-
}
|
|
64092
|
+
volatileStateFields: ["lastFetchedAt"]
|
|
63610
64093
|
};
|
|
63611
64094
|
/**
|
|
63612
64095
|
* Presence cap. Models HA `person.*` and `device_tracker.*` entities
|
|
@@ -63626,11 +64109,11 @@ var powerMeterCapability = {
|
|
|
63626
64109
|
*/
|
|
63627
64110
|
var GpsLocationSchema = object({
|
|
63628
64111
|
/** Latitude in decimal degrees, -90..90. */
|
|
63629
|
-
latitude: number().min(-90).max(90),
|
|
64112
|
+
latitude: number().min(-90).max(90).meta(readingMeta("angle")),
|
|
63630
64113
|
/** Longitude in decimal degrees, -180..180. */
|
|
63631
|
-
longitude: number().min(-180).max(180),
|
|
64114
|
+
longitude: number().min(-180).max(180).meta(readingMeta("angle")),
|
|
63632
64115
|
/** Reported accuracy in meters (lower = better). */
|
|
63633
|
-
accuracyMeters: number().nonnegative()
|
|
64116
|
+
accuracyMeters: number().nonnegative().meta(readingMeta("length"))
|
|
63634
64117
|
});
|
|
63635
64118
|
var PresenceStatusSchema = object({
|
|
63636
64119
|
/** `home` / `not_home` / any user-defined zone name. */
|
|
@@ -63642,7 +64125,7 @@ var PresenceStatusSchema = object({
|
|
|
63642
64125
|
gps: GpsLocationSchema.nullable(),
|
|
63643
64126
|
/** Optional battery level of the tracking device (0..100). Null
|
|
63644
64127
|
* when not reported. */
|
|
63645
|
-
batteryPercent: number().min(0).max(100).nullable(),
|
|
64128
|
+
batteryPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
63646
64129
|
/** Ms epoch when the slice was last updated. */
|
|
63647
64130
|
lastChangedAt: number()
|
|
63648
64131
|
});
|
|
@@ -63692,19 +64175,10 @@ var presenceCapability = {
|
|
|
63692
64175
|
* `sourceInfo`.
|
|
63693
64176
|
*/
|
|
63694
64177
|
var PressureSensorStatusSchema = object({
|
|
63695
|
-
/** Current pressure in hPa. */
|
|
63696
|
-
hpa: number(),
|
|
64178
|
+
/** Current pressure in hPa. `null` when the sample was refused (D393). */
|
|
64179
|
+
hpa: number().nullable().meta(readingMeta("pressure", { precision: 0 })),
|
|
63697
64180
|
/** Ms epoch when the slice was last updated. */
|
|
63698
|
-
lastFetchedAt: number()
|
|
63699
|
-
/** Live display unit from the upstream source (e.g. HA
|
|
63700
|
-
* `attributes.unit_of_measurement`). The UI prefers this over the
|
|
63701
|
-
* role's canonical unit. Absent → fall back to the canonical unit. */
|
|
63702
|
-
unit: string().optional(),
|
|
63703
|
-
/** Suggested decimal places for numeric display.
|
|
63704
|
-
* Populated live from the upstream source when provided (e.g. HA
|
|
63705
|
-
* `attributes.suggested_display_precision`). Falls back to
|
|
63706
|
-
* auto-formatting when absent. */
|
|
63707
|
-
precision: number().int().min(0).max(10).optional()
|
|
64181
|
+
lastFetchedAt: number()
|
|
63708
64182
|
});
|
|
63709
64183
|
var pressureSensorCapability = {
|
|
63710
64184
|
name: "pressure-sensor",
|
|
@@ -64118,8 +64592,8 @@ DeviceType.Camera, method(PtzMoveCommandSchema.extend({ deviceId: number() }), _
|
|
|
64118
64592
|
}), _void(), { kind: "mutation" });
|
|
64119
64593
|
var PtzAutotrackSettingsSchema = object({
|
|
64120
64594
|
targetType: string().describe("Vendor target string (people/vehicle/pet); empty = camera default"),
|
|
64121
|
-
stopDelaySeconds: number().int().min(0).max(300),
|
|
64122
|
-
disappearDelaySeconds: number().int().min(0).max(300)
|
|
64595
|
+
stopDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration")),
|
|
64596
|
+
disappearDelaySeconds: number().int().min(0).max(300).meta(readingMeta("duration"))
|
|
64123
64597
|
});
|
|
64124
64598
|
/**
|
|
64125
64599
|
* One option the camera advertises as a valid `targetType`. Drivers
|
|
@@ -64273,7 +64747,7 @@ var RunningStateStatusSchema = object({
|
|
|
64273
64747
|
/** null = unknown — never coerced to false (D393). */
|
|
64274
64748
|
running: boolean().nullable(),
|
|
64275
64749
|
/** Epoch ms the current boolean began; null while `running` is null. */
|
|
64276
|
-
since: number().nullable(),
|
|
64750
|
+
since: number().nullable().meta(readingMeta("none")),
|
|
64277
64751
|
source: RunningStateSourceSchema.meta({ label: "Source" }),
|
|
64278
64752
|
phase: string().nullable(),
|
|
64279
64753
|
/** null until a reading has been published (D393). */
|
|
@@ -64288,7 +64762,6 @@ var RUNNING_DEVICE_TYPES = Object.values(DeviceType);
|
|
|
64288
64762
|
/** Generic Customize target for the running boolean (D936). */
|
|
64289
64763
|
var runningCustomize = {
|
|
64290
64764
|
version: 1,
|
|
64291
|
-
advanced: "derive",
|
|
64292
64765
|
targets: [{
|
|
64293
64766
|
id: "running",
|
|
64294
64767
|
cardinality: "scalar",
|
|
@@ -64308,7 +64781,7 @@ var runningCustomize = {
|
|
|
64308
64781
|
DeviceType.Thermostat,
|
|
64309
64782
|
DeviceType.PetFeeder
|
|
64310
64783
|
],
|
|
64311
|
-
apply: "add
|
|
64784
|
+
apply: "add",
|
|
64312
64785
|
path: "running",
|
|
64313
64786
|
sources: [
|
|
64314
64787
|
{
|
|
@@ -65572,9 +66045,9 @@ var RecordTriggerSchema = _enum([
|
|
|
65572
66045
|
*/
|
|
65573
66046
|
var RecordWindowSchema = object({
|
|
65574
66047
|
trigger: RecordTriggerSchema,
|
|
65575
|
-
day: number().int().min(0).max(6),
|
|
65576
|
-
startMinute: number().int().min(0).max(1439),
|
|
65577
|
-
endMinute: number().int().min(1).max(1440)
|
|
66048
|
+
day: number().int().min(0).max(6).meta(readingMeta("none")),
|
|
66049
|
+
startMinute: number().int().min(0).max(1439).meta(readingMeta("none")),
|
|
66050
|
+
endMinute: number().int().min(1).max(1440).meta(readingMeta("none"))
|
|
65578
66051
|
});
|
|
65579
66052
|
/** Status of one physical volume, as the camera itself describes it. */
|
|
65580
66053
|
var OnboardStorageVolumeSchema = object({
|
|
@@ -65650,10 +66123,10 @@ var RecordingOnboardStatusSchema = object({
|
|
|
65650
66123
|
/** Master "record to the card at all" switch. */
|
|
65651
66124
|
enabled: boolean().nullable(),
|
|
65652
66125
|
overwriteWhenFull: boolean().nullable(),
|
|
65653
|
-
preRecordSec: number().nullable(),
|
|
65654
|
-
postRecordSec: number().nullable(),
|
|
66126
|
+
preRecordSec: number().nullable().meta(readingMeta("duration")),
|
|
66127
|
+
postRecordSec: number().nullable().meta(readingMeta("duration")),
|
|
65655
66128
|
/** Length of one recorded file, in minutes. */
|
|
65656
|
-
segmentMinutes: number().nullable(),
|
|
66129
|
+
segmentMinutes: number().nullable().meta(readingMeta("none")),
|
|
65657
66130
|
/** The primary track's weekly windows, flattened. */
|
|
65658
66131
|
windows: array(RecordWindowSchema),
|
|
65659
66132
|
/**
|
|
@@ -65669,7 +66142,7 @@ var RecordingOnboardStatusSchema = object({
|
|
|
65669
66142
|
* provider reporting a non-zero count also reports the schedule as not
|
|
65670
66143
|
* writable.
|
|
65671
66144
|
*/
|
|
65672
|
-
unreadableWindows: number(),
|
|
66145
|
+
unreadableWindows: number().meta(readingMeta("count")),
|
|
65673
66146
|
/**
|
|
65674
66147
|
* The camera is scheduled to record and has NO usable storage.
|
|
65675
66148
|
*
|
|
@@ -66514,7 +66987,7 @@ var CamStreamDescriptorSchema = object({
|
|
|
66514
66987
|
url: string().optional(),
|
|
66515
66988
|
codec: string().optional(),
|
|
66516
66989
|
resolution: CamStreamResolutionSchema.optional(),
|
|
66517
|
-
fps: number().positive().optional(),
|
|
66990
|
+
fps: number().positive().optional().meta(readingMeta("rate")),
|
|
66518
66991
|
label: string().optional(),
|
|
66519
66992
|
/** Device-level features (e.g. `battery-operated`) — drives broker policy. */
|
|
66520
66993
|
deviceFeatures: array(string()).optional(),
|
|
@@ -66601,18 +67074,18 @@ var StreamProfileSchema = _enum([
|
|
|
66601
67074
|
]);
|
|
66602
67075
|
/** The current encoding config of one stream profile. */
|
|
66603
67076
|
var StreamProfileConfigSchema = object({
|
|
66604
|
-
width: number(),
|
|
66605
|
-
height: number(),
|
|
67077
|
+
width: number().meta(readingMeta("none")),
|
|
67078
|
+
height: number().meta(readingMeta("none")),
|
|
66606
67079
|
codec: _enum(["h264", "h265"]),
|
|
66607
|
-
framerate: number(),
|
|
66608
|
-
bitrate: number(),
|
|
67080
|
+
framerate: number().meta(readingMeta("rate")),
|
|
67081
|
+
bitrate: number().meta(readingMeta("bitrate")),
|
|
66609
67082
|
bitrateMode: _enum(["vbr", "cbr"]).optional(),
|
|
66610
67083
|
encoderProfile: _enum([
|
|
66611
67084
|
"high",
|
|
66612
67085
|
"main",
|
|
66613
67086
|
"baseline"
|
|
66614
67087
|
]).optional(),
|
|
66615
|
-
gop: number().optional(),
|
|
67088
|
+
gop: number().optional().meta(readingMeta("none")),
|
|
66616
67089
|
/**
|
|
66617
67090
|
* Whether THIS profile currently carries an audio track. READ-ONLY here.
|
|
66618
67091
|
*
|
|
@@ -66833,12 +67306,7 @@ var switchCapability = {
|
|
|
66833
67306
|
label: "On",
|
|
66834
67307
|
kind: "boolean"
|
|
66835
67308
|
}
|
|
66836
|
-
}] }
|
|
66837
|
-
customize: {
|
|
66838
|
-
version: 1,
|
|
66839
|
-
advanced: "derive",
|
|
66840
|
-
targets: []
|
|
66841
|
-
}
|
|
67309
|
+
}] }
|
|
66842
67310
|
};
|
|
66843
67311
|
/** C0 controls, DEL and the C1 range: none of them belongs in a name. */
|
|
66844
67312
|
function hasControlCharacter(value) {
|
|
@@ -67421,35 +67889,21 @@ var tamperCapability = {
|
|
|
67421
67889
|
* entries with `device_class: temperature` and any future native
|
|
67422
67890
|
* thermometer.
|
|
67423
67891
|
*
|
|
67424
|
-
*
|
|
67425
|
-
*
|
|
67426
|
-
* (climate-control's `currentTemp`, energy analytics) can compose
|
|
67427
|
-
* without per-source unit fixups. The canonical display unit (`°C`) is
|
|
67428
|
-
* a descriptor constant in the UI (ROLE_DESCRIPTOR), not stored in
|
|
67429
|
-
* `sourceInfo`.
|
|
67892
|
+
* The number is Celsius (D944). Providers convert on ingest. A failed
|
|
67893
|
+
* conversion is `null`, never a guess. Display unit is a read-time preference.
|
|
67430
67894
|
*
|
|
67431
67895
|
* Status `lastFetchedAt` lets staleness-aware consumers detect a
|
|
67432
67896
|
* frozen feed (provider hung) distinct from a "temperature hasn't
|
|
67433
67897
|
* changed" steady state.
|
|
67434
67898
|
*/
|
|
67435
67899
|
var TemperatureSensorStatusSchema = object({
|
|
67436
|
-
/** Current temperature in Celsius. */
|
|
67437
|
-
celsius: number().meta({
|
|
67438
|
-
|
|
67900
|
+
/** Current temperature in Celsius. `null` when the sample was refused (D393). */
|
|
67901
|
+
celsius: number().nullable().meta(readingMeta("temperature", {
|
|
67902
|
+
precision: 1,
|
|
67439
67903
|
label: "Temperature"
|
|
67440
|
-
}),
|
|
67904
|
+
})),
|
|
67441
67905
|
/** Ms epoch when the slice was last updated (push or poll). */
|
|
67442
|
-
lastFetchedAt: number()
|
|
67443
|
-
/** Live display unit from the upstream source (e.g. HA
|
|
67444
|
-
* `attributes.unit_of_measurement`). The UI prefers this over the
|
|
67445
|
-
* role's canonical unit, so a Fahrenheit feed renders '°F' not '°C'.
|
|
67446
|
-
* Absent → the UI falls back to the role's canonical unit. */
|
|
67447
|
-
unit: string().optional(),
|
|
67448
|
-
/** Suggested decimal places for numeric display.
|
|
67449
|
-
* Populated live from the upstream source when provided (e.g. HA
|
|
67450
|
-
* `attributes.suggested_display_precision`). Falls back to
|
|
67451
|
-
* auto-formatting when absent. */
|
|
67452
|
-
precision: number().int().min(0).max(10).optional()
|
|
67906
|
+
lastFetchedAt: number()
|
|
67453
67907
|
});
|
|
67454
67908
|
var temperatureSensorCapability = {
|
|
67455
67909
|
name: "temperature-sensor",
|
|
@@ -67480,12 +67934,7 @@ var temperatureSensorCapability = {
|
|
|
67480
67934
|
} } },
|
|
67481
67935
|
/** Clock fields: written, but excluded from the compare that decides
|
|
67482
67936
|
* whether persisting is worth a SQLite commit. */
|
|
67483
|
-
volatileStateFields: ["lastFetchedAt"]
|
|
67484
|
-
customize: {
|
|
67485
|
-
version: 1,
|
|
67486
|
-
advanced: "derive",
|
|
67487
|
-
targets: []
|
|
67488
|
-
}
|
|
67937
|
+
volatileStateFields: ["lastFetchedAt"]
|
|
67489
67938
|
};
|
|
67490
67939
|
/**
|
|
67491
67940
|
* toast — system-scoped singleton capability that streams toast
|
|
@@ -67768,16 +68217,16 @@ var VacuumControlStatusSchema = object({
|
|
|
67768
68217
|
]),
|
|
67769
68218
|
/** 0..100 battery percentage. Null when the device has no battery
|
|
67770
68219
|
* reading. */
|
|
67771
|
-
batteryLevel: number().min(0).max(100).nullable(),
|
|
68220
|
+
batteryLevel: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
67772
68221
|
/** Current fan-speed token (provider-verbatim). Null when unknown or
|
|
67773
68222
|
* the vacuum has no speed control. */
|
|
67774
68223
|
fanSpeed: string().nullable(),
|
|
67775
68224
|
/** Speed tokens the hardware accepts — drives the UI selector. */
|
|
67776
68225
|
availableFanSpeeds: array(string()),
|
|
67777
68226
|
/** 0..100 cleaning-completion percentage of the current task, or null. */
|
|
67778
|
-
progressPercent: number().min(0).max(100).nullable(),
|
|
68227
|
+
progressPercent: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
67779
68228
|
/** Current error code (0 / null = no error). */
|
|
67780
|
-
errorCode: number().nullable(),
|
|
68229
|
+
errorCode: number().nullable().meta(readingMeta("none")),
|
|
67781
68230
|
/** Human label for {@link errorCode}, or null when none / undecodable. */
|
|
67782
68231
|
errorLabel: string().nullable(),
|
|
67783
68232
|
/** Ms epoch when the slice was last updated. */
|
|
@@ -67855,7 +68304,7 @@ var ValveStatusSchema = object({
|
|
|
67855
68304
|
]),
|
|
67856
68305
|
/** 0 = fully closed, 100 = fully open. Null when the device has no
|
|
67857
68306
|
* intermediate position surface. */
|
|
67858
|
-
position: number().min(0).max(100).nullable(),
|
|
68307
|
+
position: number().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
67859
68308
|
/** Ms epoch when the slice was last updated. */
|
|
67860
68309
|
lastChangedAt: number()
|
|
67861
68310
|
});
|
|
@@ -67963,9 +68412,9 @@ var vibrationCapability = {
|
|
|
67963
68412
|
*/
|
|
67964
68413
|
var WaterHeaterStatusSchema = object({
|
|
67965
68414
|
/** Current measured temperature. Null when not reported. */
|
|
67966
|
-
currentTemp: number().nullable(),
|
|
68415
|
+
currentTemp: number().nullable().meta(readingMeta("temperature")),
|
|
67967
68416
|
/** Target temperature setpoint. Null when no setpoint surface. */
|
|
67968
|
-
targetTemp: number().nullable(),
|
|
68417
|
+
targetTemp: number().nullable().meta(readingMeta("temperature")),
|
|
67969
68418
|
/** Active operation mode = HA `state` (`eco` / `electric` / `gas` /
|
|
67970
68419
|
* `heat_pump` / `high_demand` / `performance` / `off`). Null when the
|
|
67971
68420
|
* device reports an unknown state. */
|
|
@@ -67976,9 +68425,9 @@ var WaterHeaterStatusSchema = object({
|
|
|
67976
68425
|
* has no away surface. */
|
|
67977
68426
|
away: boolean().nullable(),
|
|
67978
68427
|
/** HA `min_temp` attribute. Null when not reported. */
|
|
67979
|
-
minTemp: number().nullable(),
|
|
68428
|
+
minTemp: number().nullable().meta(readingMeta("temperature")),
|
|
67980
68429
|
/** HA `max_temp` attribute. Null when not reported. */
|
|
67981
|
-
maxTemp: number().nullable(),
|
|
68430
|
+
maxTemp: number().nullable().meta(readingMeta("temperature")),
|
|
67982
68431
|
/** Ms epoch when the slice was last updated. */
|
|
67983
68432
|
lastChangedAt: number()
|
|
67984
68433
|
});
|
|
@@ -68063,25 +68512,18 @@ var waterHeaterCapability = {
|
|
|
68063
68512
|
* Forecast deferred — current conditions only for v1.
|
|
68064
68513
|
*/
|
|
68065
68514
|
var WeatherStatusSchema = object({
|
|
68066
|
-
/** Verbatim
|
|
68067
|
-
* when no condition has been reported yet. */
|
|
68515
|
+
/** Verbatim condition state (`sunny`, `cloudy`, `rainy`, …). Null when none yet. */
|
|
68068
68516
|
condition: string().nullable(),
|
|
68069
|
-
/** Current temperature in
|
|
68070
|
-
temperature: number().nullable(),
|
|
68071
|
-
/** Temperature unit string (e.g. `°C` / `°F`). Null when not provided. */
|
|
68072
|
-
temperatureUnit: string().nullable(),
|
|
68517
|
+
/** Current temperature in Celsius. Null when not provided or refused. */
|
|
68518
|
+
temperature: number().nullable().meta(readingMeta("temperature", { precision: 1 })),
|
|
68073
68519
|
/** Relative humidity (0..100). Null when not provided. */
|
|
68074
|
-
humidity: number().min(0).max(100).nullable(),
|
|
68075
|
-
/** Barometric pressure in
|
|
68076
|
-
pressure: number().nullable(),
|
|
68077
|
-
/**
|
|
68078
|
-
|
|
68079
|
-
/** Wind speed in the reported unit. Null when not provided. */
|
|
68080
|
-
windSpeed: number().nullable(),
|
|
68081
|
-
/** Wind-speed unit string (e.g. `km/h` / `mph`). Null when not provided. */
|
|
68082
|
-
windSpeedUnit: string().nullable(),
|
|
68520
|
+
humidity: number().min(0).max(100).nullable().meta(readingMeta("humidity")),
|
|
68521
|
+
/** Barometric pressure in hPa. Null when not provided or refused. */
|
|
68522
|
+
pressure: number().nullable().meta(readingMeta("pressure")),
|
|
68523
|
+
/** Wind speed in metres per second. Null when not provided or refused. */
|
|
68524
|
+
windSpeed: number().nullable().meta(readingMeta("speed")),
|
|
68083
68525
|
/** Wind bearing in degrees (0..360, meteorological). Null when not provided. */
|
|
68084
|
-
windBearing: number().nullable(),
|
|
68526
|
+
windBearing: number().nullable().meta(readingMeta("angle")),
|
|
68085
68527
|
/** Ms epoch when the slice was last updated. */
|
|
68086
68528
|
lastFetchedAt: number()
|
|
68087
68529
|
});
|
|
@@ -68109,12 +68551,12 @@ var weatherCapability = {
|
|
|
68109
68551
|
*/
|
|
68110
68552
|
durability: "session",
|
|
68111
68553
|
/**
|
|
68112
|
-
* History (D850): recorded series.
|
|
68554
|
+
* History (D850): recorded series. Numbers are the quantity base (D944).
|
|
68113
68555
|
*/
|
|
68114
68556
|
history: { fields: {
|
|
68115
68557
|
temperature: {
|
|
68116
68558
|
kind: "gauge",
|
|
68117
|
-
|
|
68559
|
+
unit: "°C",
|
|
68118
68560
|
deadband: .2
|
|
68119
68561
|
},
|
|
68120
68562
|
humidity: {
|
|
@@ -68124,12 +68566,12 @@ var weatherCapability = {
|
|
|
68124
68566
|
},
|
|
68125
68567
|
pressure: {
|
|
68126
68568
|
kind: "gauge",
|
|
68127
|
-
|
|
68569
|
+
unit: "hPa",
|
|
68128
68570
|
deadband: .5
|
|
68129
68571
|
},
|
|
68130
68572
|
windSpeed: {
|
|
68131
68573
|
kind: "gauge",
|
|
68132
|
-
|
|
68574
|
+
unit: "m/s",
|
|
68133
68575
|
deadband: .5
|
|
68134
68576
|
}
|
|
68135
68577
|
} }
|
|
@@ -69030,6 +69472,389 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
|
|
|
69030
69472
|
kind: "mutation",
|
|
69031
69473
|
auth: "admin"
|
|
69032
69474
|
});
|
|
69475
|
+
/**
|
|
69476
|
+
* Frozen unit → spec table. `•` in the doc marks each dimension's canonical
|
|
69477
|
+
* unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
|
|
69478
|
+
* `factor = 5/9`, `offset = -160/9` (kept as exact expressions, not rounded
|
|
69479
|
+
* decimals). K derives from `°C = K − 273.15`.
|
|
69480
|
+
*/
|
|
69481
|
+
var UNIT_TABLE = {
|
|
69482
|
+
"°C": {
|
|
69483
|
+
dimension: "temperature",
|
|
69484
|
+
factor: 1,
|
|
69485
|
+
offset: 0
|
|
69486
|
+
},
|
|
69487
|
+
"°F": {
|
|
69488
|
+
dimension: "temperature",
|
|
69489
|
+
factor: 5 / 9,
|
|
69490
|
+
offset: -160 / 9
|
|
69491
|
+
},
|
|
69492
|
+
K: {
|
|
69493
|
+
dimension: "temperature",
|
|
69494
|
+
factor: 1,
|
|
69495
|
+
offset: -273.15
|
|
69496
|
+
},
|
|
69497
|
+
hPa: {
|
|
69498
|
+
dimension: "pressure",
|
|
69499
|
+
factor: 1,
|
|
69500
|
+
offset: 0
|
|
69501
|
+
},
|
|
69502
|
+
kPa: {
|
|
69503
|
+
dimension: "pressure",
|
|
69504
|
+
factor: 10,
|
|
69505
|
+
offset: 0
|
|
69506
|
+
},
|
|
69507
|
+
Pa: {
|
|
69508
|
+
dimension: "pressure",
|
|
69509
|
+
factor: .01,
|
|
69510
|
+
offset: 0
|
|
69511
|
+
},
|
|
69512
|
+
mbar: {
|
|
69513
|
+
dimension: "pressure",
|
|
69514
|
+
factor: 1,
|
|
69515
|
+
offset: 0
|
|
69516
|
+
},
|
|
69517
|
+
bar: {
|
|
69518
|
+
dimension: "pressure",
|
|
69519
|
+
factor: 1e3,
|
|
69520
|
+
offset: 0
|
|
69521
|
+
},
|
|
69522
|
+
inHg: {
|
|
69523
|
+
dimension: "pressure",
|
|
69524
|
+
factor: 33.8639,
|
|
69525
|
+
offset: 0
|
|
69526
|
+
},
|
|
69527
|
+
mmHg: {
|
|
69528
|
+
dimension: "pressure",
|
|
69529
|
+
factor: 1.33322,
|
|
69530
|
+
offset: 0
|
|
69531
|
+
},
|
|
69532
|
+
psi: {
|
|
69533
|
+
dimension: "pressure",
|
|
69534
|
+
factor: 68.9476,
|
|
69535
|
+
offset: 0
|
|
69536
|
+
},
|
|
69537
|
+
"m/s": {
|
|
69538
|
+
dimension: "speed",
|
|
69539
|
+
factor: 1,
|
|
69540
|
+
offset: 0
|
|
69541
|
+
},
|
|
69542
|
+
"km/h": {
|
|
69543
|
+
dimension: "speed",
|
|
69544
|
+
factor: 1 / 3.6,
|
|
69545
|
+
offset: 0
|
|
69546
|
+
},
|
|
69547
|
+
mph: {
|
|
69548
|
+
dimension: "speed",
|
|
69549
|
+
factor: .44704,
|
|
69550
|
+
offset: 0
|
|
69551
|
+
},
|
|
69552
|
+
kn: {
|
|
69553
|
+
dimension: "speed",
|
|
69554
|
+
factor: .514444,
|
|
69555
|
+
offset: 0
|
|
69556
|
+
},
|
|
69557
|
+
"mm/h": {
|
|
69558
|
+
dimension: "precipitation-rate",
|
|
69559
|
+
factor: 1,
|
|
69560
|
+
offset: 0
|
|
69561
|
+
},
|
|
69562
|
+
"in/h": {
|
|
69563
|
+
dimension: "precipitation-rate",
|
|
69564
|
+
factor: 25.4,
|
|
69565
|
+
offset: 0
|
|
69566
|
+
},
|
|
69567
|
+
m: {
|
|
69568
|
+
dimension: "length",
|
|
69569
|
+
factor: 1,
|
|
69570
|
+
offset: 0
|
|
69571
|
+
},
|
|
69572
|
+
mm: {
|
|
69573
|
+
dimension: "length",
|
|
69574
|
+
factor: .001,
|
|
69575
|
+
offset: 0
|
|
69576
|
+
},
|
|
69577
|
+
cm: {
|
|
69578
|
+
dimension: "length",
|
|
69579
|
+
factor: .01,
|
|
69580
|
+
offset: 0
|
|
69581
|
+
},
|
|
69582
|
+
km: {
|
|
69583
|
+
dimension: "length",
|
|
69584
|
+
factor: 1e3,
|
|
69585
|
+
offset: 0
|
|
69586
|
+
},
|
|
69587
|
+
in: {
|
|
69588
|
+
dimension: "length",
|
|
69589
|
+
factor: .0254,
|
|
69590
|
+
offset: 0
|
|
69591
|
+
},
|
|
69592
|
+
ft: {
|
|
69593
|
+
dimension: "length",
|
|
69594
|
+
factor: .3048,
|
|
69595
|
+
offset: 0
|
|
69596
|
+
},
|
|
69597
|
+
mi: {
|
|
69598
|
+
dimension: "length",
|
|
69599
|
+
factor: 1609.344,
|
|
69600
|
+
offset: 0
|
|
69601
|
+
},
|
|
69602
|
+
"m²": {
|
|
69603
|
+
dimension: "area",
|
|
69604
|
+
factor: 1,
|
|
69605
|
+
offset: 0
|
|
69606
|
+
},
|
|
69607
|
+
lx: {
|
|
69608
|
+
dimension: "illuminance",
|
|
69609
|
+
factor: 1,
|
|
69610
|
+
offset: 0
|
|
69611
|
+
},
|
|
69612
|
+
"W/m²": {
|
|
69613
|
+
dimension: "irradiance",
|
|
69614
|
+
factor: 1,
|
|
69615
|
+
offset: 0
|
|
69616
|
+
},
|
|
69617
|
+
W: {
|
|
69618
|
+
dimension: "power",
|
|
69619
|
+
factor: 1,
|
|
69620
|
+
offset: 0
|
|
69621
|
+
},
|
|
69622
|
+
kW: {
|
|
69623
|
+
dimension: "power",
|
|
69624
|
+
factor: 1e3,
|
|
69625
|
+
offset: 0
|
|
69626
|
+
},
|
|
69627
|
+
VA: {
|
|
69628
|
+
dimension: "apparent-power",
|
|
69629
|
+
factor: 1,
|
|
69630
|
+
offset: 0
|
|
69631
|
+
},
|
|
69632
|
+
Wh: {
|
|
69633
|
+
dimension: "energy",
|
|
69634
|
+
factor: 1,
|
|
69635
|
+
offset: 0
|
|
69636
|
+
},
|
|
69637
|
+
kWh: {
|
|
69638
|
+
dimension: "energy",
|
|
69639
|
+
factor: 1e3,
|
|
69640
|
+
offset: 0
|
|
69641
|
+
},
|
|
69642
|
+
MWh: {
|
|
69643
|
+
dimension: "energy",
|
|
69644
|
+
factor: 1e6,
|
|
69645
|
+
offset: 0
|
|
69646
|
+
},
|
|
69647
|
+
V: {
|
|
69648
|
+
dimension: "voltage",
|
|
69649
|
+
factor: 1,
|
|
69650
|
+
offset: 0
|
|
69651
|
+
},
|
|
69652
|
+
mV: {
|
|
69653
|
+
dimension: "voltage",
|
|
69654
|
+
factor: .001,
|
|
69655
|
+
offset: 0
|
|
69656
|
+
},
|
|
69657
|
+
A: {
|
|
69658
|
+
dimension: "current",
|
|
69659
|
+
factor: 1,
|
|
69660
|
+
offset: 0
|
|
69661
|
+
},
|
|
69662
|
+
mA: {
|
|
69663
|
+
dimension: "current",
|
|
69664
|
+
factor: .001,
|
|
69665
|
+
offset: 0
|
|
69666
|
+
},
|
|
69667
|
+
ppm: {
|
|
69668
|
+
dimension: "concentration-volume",
|
|
69669
|
+
factor: 1,
|
|
69670
|
+
offset: 0
|
|
69671
|
+
},
|
|
69672
|
+
ppb: {
|
|
69673
|
+
dimension: "concentration-volume",
|
|
69674
|
+
factor: .001,
|
|
69675
|
+
offset: 0
|
|
69676
|
+
},
|
|
69677
|
+
"µg/m³": {
|
|
69678
|
+
dimension: "concentration-mass",
|
|
69679
|
+
factor: 1,
|
|
69680
|
+
offset: 0
|
|
69681
|
+
},
|
|
69682
|
+
"mg/m³": {
|
|
69683
|
+
dimension: "concentration-mass",
|
|
69684
|
+
factor: 1e3,
|
|
69685
|
+
offset: 0
|
|
69686
|
+
},
|
|
69687
|
+
kg: {
|
|
69688
|
+
dimension: "mass",
|
|
69689
|
+
factor: 1,
|
|
69690
|
+
offset: 0
|
|
69691
|
+
},
|
|
69692
|
+
g: {
|
|
69693
|
+
dimension: "mass",
|
|
69694
|
+
factor: .001,
|
|
69695
|
+
offset: 0
|
|
69696
|
+
},
|
|
69697
|
+
lb: {
|
|
69698
|
+
dimension: "mass",
|
|
69699
|
+
factor: .453592,
|
|
69700
|
+
offset: 0
|
|
69701
|
+
},
|
|
69702
|
+
oz: {
|
|
69703
|
+
dimension: "mass",
|
|
69704
|
+
factor: .0283495,
|
|
69705
|
+
offset: 0
|
|
69706
|
+
},
|
|
69707
|
+
"%": {
|
|
69708
|
+
dimension: "percentage",
|
|
69709
|
+
factor: 1,
|
|
69710
|
+
offset: 0
|
|
69711
|
+
},
|
|
69712
|
+
L: {
|
|
69713
|
+
dimension: "volume",
|
|
69714
|
+
factor: 1,
|
|
69715
|
+
offset: 0
|
|
69716
|
+
},
|
|
69717
|
+
mL: {
|
|
69718
|
+
dimension: "volume",
|
|
69719
|
+
factor: .001,
|
|
69720
|
+
offset: 0
|
|
69721
|
+
},
|
|
69722
|
+
gal: {
|
|
69723
|
+
dimension: "volume",
|
|
69724
|
+
factor: 3.785411784,
|
|
69725
|
+
offset: 0
|
|
69726
|
+
},
|
|
69727
|
+
s: {
|
|
69728
|
+
dimension: "duration",
|
|
69729
|
+
factor: 1,
|
|
69730
|
+
offset: 0
|
|
69731
|
+
},
|
|
69732
|
+
ms: {
|
|
69733
|
+
dimension: "duration",
|
|
69734
|
+
factor: .001,
|
|
69735
|
+
offset: 0
|
|
69736
|
+
},
|
|
69737
|
+
min: {
|
|
69738
|
+
dimension: "duration",
|
|
69739
|
+
factor: 60,
|
|
69740
|
+
offset: 0
|
|
69741
|
+
},
|
|
69742
|
+
h: {
|
|
69743
|
+
dimension: "duration",
|
|
69744
|
+
factor: 3600,
|
|
69745
|
+
offset: 0
|
|
69746
|
+
},
|
|
69747
|
+
d: {
|
|
69748
|
+
dimension: "duration",
|
|
69749
|
+
factor: 86400,
|
|
69750
|
+
offset: 0
|
|
69751
|
+
},
|
|
69752
|
+
B: {
|
|
69753
|
+
dimension: "information",
|
|
69754
|
+
factor: 1,
|
|
69755
|
+
offset: 0
|
|
69756
|
+
},
|
|
69757
|
+
KiB: {
|
|
69758
|
+
dimension: "information",
|
|
69759
|
+
factor: 1024,
|
|
69760
|
+
offset: 0
|
|
69761
|
+
},
|
|
69762
|
+
MiB: {
|
|
69763
|
+
dimension: "information",
|
|
69764
|
+
factor: 1024 ** 2,
|
|
69765
|
+
offset: 0
|
|
69766
|
+
},
|
|
69767
|
+
GiB: {
|
|
69768
|
+
dimension: "information",
|
|
69769
|
+
factor: 1024 ** 3,
|
|
69770
|
+
offset: 0
|
|
69771
|
+
},
|
|
69772
|
+
TiB: {
|
|
69773
|
+
dimension: "information",
|
|
69774
|
+
factor: 1024 ** 4,
|
|
69775
|
+
offset: 0
|
|
69776
|
+
},
|
|
69777
|
+
kB: {
|
|
69778
|
+
dimension: "information",
|
|
69779
|
+
factor: 1e3,
|
|
69780
|
+
offset: 0
|
|
69781
|
+
},
|
|
69782
|
+
MB: {
|
|
69783
|
+
dimension: "information",
|
|
69784
|
+
factor: 1e6,
|
|
69785
|
+
offset: 0
|
|
69786
|
+
},
|
|
69787
|
+
GB: {
|
|
69788
|
+
dimension: "information",
|
|
69789
|
+
factor: 1e9,
|
|
69790
|
+
offset: 0
|
|
69791
|
+
},
|
|
69792
|
+
"°": {
|
|
69793
|
+
dimension: "angle",
|
|
69794
|
+
factor: 1,
|
|
69795
|
+
offset: 0
|
|
69796
|
+
},
|
|
69797
|
+
dBFS: {
|
|
69798
|
+
dimension: "sound-level",
|
|
69799
|
+
factor: 1,
|
|
69800
|
+
offset: 0
|
|
69801
|
+
},
|
|
69802
|
+
dBm: {
|
|
69803
|
+
dimension: "signal-level",
|
|
69804
|
+
factor: 1,
|
|
69805
|
+
offset: 0
|
|
69806
|
+
},
|
|
69807
|
+
mired: {
|
|
69808
|
+
dimension: "colour-temperature",
|
|
69809
|
+
factor: 1,
|
|
69810
|
+
offset: 0
|
|
69811
|
+
},
|
|
69812
|
+
kbps: {
|
|
69813
|
+
dimension: "bitrate",
|
|
69814
|
+
factor: 1,
|
|
69815
|
+
offset: 0
|
|
69816
|
+
},
|
|
69817
|
+
Mbps: {
|
|
69818
|
+
dimension: "bitrate",
|
|
69819
|
+
factor: 1e3,
|
|
69820
|
+
offset: 0
|
|
69821
|
+
},
|
|
69822
|
+
fps: {
|
|
69823
|
+
dimension: "rate",
|
|
69824
|
+
factor: 1,
|
|
69825
|
+
offset: 0
|
|
69826
|
+
}
|
|
69827
|
+
};
|
|
69828
|
+
/** Resolve a unit's `UnitSpec`, or `undefined` when the spelling is unknown. */
|
|
69829
|
+
function specFor(unit) {
|
|
69830
|
+
return Object.hasOwn(UNIT_TABLE, unit) ? UNIT_TABLE[unit] : void 0;
|
|
69831
|
+
}
|
|
69832
|
+
/** The dimension a unit belongs to, or `undefined` for an unknown unit. */
|
|
69833
|
+
function unitDimension(unit) {
|
|
69834
|
+
return specFor(unit)?.dimension;
|
|
69835
|
+
}
|
|
69836
|
+
/** Value in `spec`'s unit → its dimension's canonical unit. */
|
|
69837
|
+
function toCanonical(value, spec) {
|
|
69838
|
+
return value * spec.factor + spec.offset;
|
|
69839
|
+
}
|
|
69840
|
+
/** Canonical-unit value → the unit described by `spec`. */
|
|
69841
|
+
function fromCanonical(canonical, spec) {
|
|
69842
|
+
return (canonical - spec.offset) / spec.factor;
|
|
69843
|
+
}
|
|
69844
|
+
/**
|
|
69845
|
+
* Graceful variant for the render path: returns the converted value, or
|
|
69846
|
+
* `undefined` when the conversion is refused (unknown unit / cross-dimension).
|
|
69847
|
+
* Never throws — a render caller falls back to the raw value + source unit.
|
|
69848
|
+
*/
|
|
69849
|
+
function tryConvertUnit(value, from, to) {
|
|
69850
|
+
if (from === to) return value;
|
|
69851
|
+
const fromSpec = specFor(from);
|
|
69852
|
+
if (!fromSpec) return void 0;
|
|
69853
|
+
const toSpec = specFor(to);
|
|
69854
|
+
if (!toSpec) return void 0;
|
|
69855
|
+
if (fromSpec.dimension !== toSpec.dimension) return void 0;
|
|
69856
|
+
return fromCanonical(toCanonical(value, fromSpec), toSpec);
|
|
69857
|
+
}
|
|
69033
69858
|
SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
|
|
69034
69859
|
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;
|
|
69035
69860
|
String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
|
|
@@ -69360,6 +70185,8 @@ var DEVICE_LOCAL_STATE_CAPS = {
|
|
|
69360
70185
|
featureProbe: featureProbeCapability,
|
|
69361
70186
|
flood: floodCapability,
|
|
69362
70187
|
gas: gasCapability,
|
|
70188
|
+
groupCommands: groupCommandsCapability,
|
|
70189
|
+
groupStatus: groupStatusCapability,
|
|
69363
70190
|
heatingValve: heatingValveCapability,
|
|
69364
70191
|
humidifier: humidifierCapability,
|
|
69365
70192
|
humiditySensor: humiditySensorCapability,
|
|
@@ -70736,6 +71563,13 @@ Object.freeze({
|
|
|
70736
71563
|
access: "view",
|
|
70737
71564
|
role: "admin"
|
|
70738
71565
|
},
|
|
71566
|
+
"addons.getPackageDocs": {
|
|
71567
|
+
capName: "addons",
|
|
71568
|
+
capScope: "system",
|
|
71569
|
+
addonId: null,
|
|
71570
|
+
access: "view",
|
|
71571
|
+
role: "admin"
|
|
71572
|
+
},
|
|
70739
71573
|
"addons.getVersions": {
|
|
70740
71574
|
capName: "addons",
|
|
70741
71575
|
capScope: "system",
|
|
@@ -73844,6 +74678,20 @@ Object.freeze({
|
|
|
73844
74678
|
access: "view",
|
|
73845
74679
|
role: "viewer"
|
|
73846
74680
|
},
|
|
74681
|
+
"groupCommands.getStatus": {
|
|
74682
|
+
capName: "group-commands",
|
|
74683
|
+
capScope: "device",
|
|
74684
|
+
addonId: null,
|
|
74685
|
+
access: "view",
|
|
74686
|
+
role: "viewer"
|
|
74687
|
+
},
|
|
74688
|
+
"groupStatus.getStatus": {
|
|
74689
|
+
capName: "group-status",
|
|
74690
|
+
capScope: "device",
|
|
74691
|
+
addonId: null,
|
|
74692
|
+
access: "view",
|
|
74693
|
+
role: "viewer"
|
|
74694
|
+
},
|
|
73847
74695
|
"heatingValve.getStatus": {
|
|
73848
74696
|
capName: "heating-valve",
|
|
73849
74697
|
capScope: "device",
|
|
@@ -83666,6 +84514,37 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
83666
84514
|
icon: "flame"
|
|
83667
84515
|
}]
|
|
83668
84516
|
},
|
|
84517
|
+
"group-commands": {
|
|
84518
|
+
scope: "device",
|
|
84519
|
+
noun: t("Group command", "Comando di gruppo")
|
|
84520
|
+
},
|
|
84521
|
+
"group-status": {
|
|
84522
|
+
scope: "device",
|
|
84523
|
+
noun: t("Group", "Gruppo"),
|
|
84524
|
+
state: [
|
|
84525
|
+
{
|
|
84526
|
+
kind: "numeric",
|
|
84527
|
+
field: "included",
|
|
84528
|
+
quantity: t("the members reporting", "i membri che rispondono"),
|
|
84529
|
+
label: t("Reporting", "Rispondono"),
|
|
84530
|
+
icon: "users"
|
|
84531
|
+
},
|
|
84532
|
+
{
|
|
84533
|
+
kind: "numeric",
|
|
84534
|
+
field: "excluded",
|
|
84535
|
+
quantity: t("the members left out", "i membri esclusi"),
|
|
84536
|
+
label: t("Excluded", "Esclusi"),
|
|
84537
|
+
icon: "users"
|
|
84538
|
+
},
|
|
84539
|
+
{
|
|
84540
|
+
kind: "numeric",
|
|
84541
|
+
field: "total",
|
|
84542
|
+
quantity: t("the member count", "il numero di membri"),
|
|
84543
|
+
label: t("Members", "Membri"),
|
|
84544
|
+
icon: "sigma"
|
|
84545
|
+
}
|
|
84546
|
+
]
|
|
84547
|
+
},
|
|
83669
84548
|
"humidity-sensor": {
|
|
83670
84549
|
scope: "device",
|
|
83671
84550
|
noun: t("Humidity", "Umidità"),
|
|
@@ -83699,7 +84578,6 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
83699
84578
|
field: "value",
|
|
83700
84579
|
quantity: t("the reading", "il valore"),
|
|
83701
84580
|
label: t("Reading", "Valore"),
|
|
83702
|
-
unitField: "unit",
|
|
83703
84581
|
icon: "gauge"
|
|
83704
84582
|
}]
|
|
83705
84583
|
},
|
|
@@ -83835,7 +84713,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
83835
84713
|
field: "temperature",
|
|
83836
84714
|
quantity: t("the outdoor temperature", "la temperatura esterna"),
|
|
83837
84715
|
label: t("Temperature", "Temperatura"),
|
|
83838
|
-
|
|
84716
|
+
unit: "°C",
|
|
83839
84717
|
icon: "thermometer"
|
|
83840
84718
|
},
|
|
83841
84719
|
{
|
|
@@ -83851,7 +84729,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
83851
84729
|
field: "windSpeed",
|
|
83852
84730
|
quantity: t("the wind speed", "la velocità del vento"),
|
|
83853
84731
|
label: t("Wind", "Vento"),
|
|
83854
|
-
|
|
84732
|
+
unit: "m/s",
|
|
83855
84733
|
icon: "wind"
|
|
83856
84734
|
},
|
|
83857
84735
|
{
|
|
@@ -83859,7 +84737,7 @@ var SENSOR_CAP_VOCABULARY = {
|
|
|
83859
84737
|
field: "pressure",
|
|
83860
84738
|
quantity: t("the air pressure", "la pressione atmosferica"),
|
|
83861
84739
|
label: t("Pressure", "Pressione"),
|
|
83862
|
-
|
|
84740
|
+
unit: "hPa",
|
|
83863
84741
|
icon: "gauge"
|
|
83864
84742
|
}
|
|
83865
84743
|
]
|
|
@@ -85132,6 +86010,16 @@ Object.freeze({
|
|
|
85132
86010
|
form: "single",
|
|
85133
86011
|
optional: false
|
|
85134
86012
|
}],
|
|
86013
|
+
"groupCommands.getStatus": [{
|
|
86014
|
+
name: "deviceId",
|
|
86015
|
+
form: "single",
|
|
86016
|
+
optional: false
|
|
86017
|
+
}],
|
|
86018
|
+
"groupStatus.getStatus": [{
|
|
86019
|
+
name: "deviceId",
|
|
86020
|
+
form: "single",
|
|
86021
|
+
optional: false
|
|
86022
|
+
}],
|
|
85135
86023
|
"heatingValve.getStatus": [{
|
|
85136
86024
|
name: "deviceId",
|
|
85137
86025
|
form: "single",
|
|
@@ -87116,6 +88004,8 @@ new Map(Object.entries({
|
|
|
87116
88004
|
"detection-blind": "device-events",
|
|
87117
88005
|
"motion-noisy": "device-events",
|
|
87118
88006
|
"motion-calm": "device-events",
|
|
88007
|
+
"package-llm-silent": "device-events",
|
|
88008
|
+
"package-llm-answered": "device-events",
|
|
87119
88009
|
"recording-stalled": "device-events",
|
|
87120
88010
|
"recording-healthy": "device-events",
|
|
87121
88011
|
"recording-degraded": "device-events",
|
|
@@ -87199,6 +88089,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
|
|
|
87199
88089
|
label: "Motion without a subject",
|
|
87200
88090
|
kinds: ["motion-noisy", "motion-calm"]
|
|
87201
88091
|
},
|
|
88092
|
+
{
|
|
88093
|
+
id: "package-llm",
|
|
88094
|
+
family: "device-events",
|
|
88095
|
+
label: "Package confirm",
|
|
88096
|
+
kinds: ["package-llm-silent", "package-llm-answered"]
|
|
88097
|
+
},
|
|
87202
88098
|
{
|
|
87203
88099
|
id: "recording",
|
|
87204
88100
|
family: "device-events",
|
|
@@ -87380,6 +88276,8 @@ var DEVICE_EVENTS = [
|
|
|
87380
88276
|
"detection-blind",
|
|
87381
88277
|
"motion-noisy",
|
|
87382
88278
|
"motion-calm",
|
|
88279
|
+
"package-llm-silent",
|
|
88280
|
+
"package-llm-answered",
|
|
87383
88281
|
"recording-stalled",
|
|
87384
88282
|
"recording-healthy",
|
|
87385
88283
|
"recording-degraded",
|
|
@@ -87648,6 +88546,15 @@ new Map([
|
|
|
87648
88546
|
snapshotLabel: "Cameras with constant motion now",
|
|
87649
88547
|
snapshotSection: null
|
|
87650
88548
|
},
|
|
88549
|
+
{
|
|
88550
|
+
id: "package-llm-confirm",
|
|
88551
|
+
family: "device-events",
|
|
88552
|
+
label: "Package confirm (not answering / answering again)",
|
|
88553
|
+
bad: ["package-llm-silent"],
|
|
88554
|
+
recovery: ["package-llm-answered"],
|
|
88555
|
+
snapshotLabel: "Cameras whose package confirm is not answering",
|
|
88556
|
+
snapshotSection: null
|
|
88557
|
+
},
|
|
87651
88558
|
{
|
|
87652
88559
|
id: "recording-health",
|
|
87653
88560
|
family: "device-events",
|
|
@@ -88224,6 +89131,96 @@ DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
|
|
|
88224
89131
|
DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
|
|
88225
89132
|
DEFAULT_NATIVE_LEASE_SETTINGS.sceneBudgetMb;
|
|
88226
89133
|
DEFAULT_NATIVE_LEASE_SETTINGS.admission;
|
|
89134
|
+
/**
|
|
89135
|
+
* Convert an upstream number into the stored base of its quantity (D944 §4).
|
|
89136
|
+
* An unknown unit or a unit from another dimension is a refusal: the caller
|
|
89137
|
+
* logs it and publishes null (D393). This function never guesses.
|
|
89138
|
+
*/
|
|
89139
|
+
var DIMENSION_QUANTITY = {
|
|
89140
|
+
temperature: "temperature",
|
|
89141
|
+
pressure: "pressure",
|
|
89142
|
+
speed: "speed",
|
|
89143
|
+
"precipitation-rate": "precipitation-rate",
|
|
89144
|
+
length: "length",
|
|
89145
|
+
area: "area",
|
|
89146
|
+
illuminance: "illuminance",
|
|
89147
|
+
irradiance: "irradiance",
|
|
89148
|
+
power: "power",
|
|
89149
|
+
"apparent-power": "apparent-power",
|
|
89150
|
+
energy: "energy",
|
|
89151
|
+
voltage: "voltage",
|
|
89152
|
+
current: "current",
|
|
89153
|
+
"concentration-volume": "concentration-volume",
|
|
89154
|
+
"concentration-mass": "concentration-mass",
|
|
89155
|
+
mass: "mass",
|
|
89156
|
+
percentage: "percentage",
|
|
89157
|
+
volume: "volume",
|
|
89158
|
+
duration: "duration",
|
|
89159
|
+
information: "information",
|
|
89160
|
+
angle: "angle",
|
|
89161
|
+
"sound-level": "sound-level",
|
|
89162
|
+
"signal-level": "signal-level",
|
|
89163
|
+
"colour-temperature": "colour-temperature",
|
|
89164
|
+
bitrate: "bitrate",
|
|
89165
|
+
rate: "rate"
|
|
89166
|
+
};
|
|
89167
|
+
/**
|
|
89168
|
+
* Quantity implied by a unit symbol. `ppb` and `ms` are the two symbols
|
|
89169
|
+
* whose stored base is not the dimension's canonical unit. `mm` is length;
|
|
89170
|
+
* precipitation is chosen by the caller (a device class), not by the symbol.
|
|
89171
|
+
*/
|
|
89172
|
+
function quantityFromUnit(rawUnit) {
|
|
89173
|
+
const unit = normalizeUnit(rawUnit);
|
|
89174
|
+
if (unit === void 0) return void 0;
|
|
89175
|
+
if (unit === "ppb") return "concentration-ppb";
|
|
89176
|
+
if (unit === "ms") return "duration-ms";
|
|
89177
|
+
const dimension = unitDimension(unit);
|
|
89178
|
+
if (dimension === void 0) return void 0;
|
|
89179
|
+
return DIMENSION_QUANTITY[dimension];
|
|
89180
|
+
}
|
|
89181
|
+
function ingestReading(input) {
|
|
89182
|
+
if (!Number.isFinite(input.value)) return {
|
|
89183
|
+
ok: false,
|
|
89184
|
+
reason: "non-finite"
|
|
89185
|
+
};
|
|
89186
|
+
if (input.quantity === "device") return {
|
|
89187
|
+
ok: false,
|
|
89188
|
+
reason: "unknown-unit"
|
|
89189
|
+
};
|
|
89190
|
+
const raw = input.rawUnit?.trim();
|
|
89191
|
+
if (raw === void 0 || raw.length === 0) return {
|
|
89192
|
+
ok: true,
|
|
89193
|
+
value: input.value
|
|
89194
|
+
};
|
|
89195
|
+
const unit = normalizeUnit(raw);
|
|
89196
|
+
if (unit === void 0) return {
|
|
89197
|
+
ok: false,
|
|
89198
|
+
reason: "unknown-unit"
|
|
89199
|
+
};
|
|
89200
|
+
const base = baseUnitOf(input.quantity);
|
|
89201
|
+
if (base === void 0) {
|
|
89202
|
+
if (unitDimension(unit) !== void 0) return {
|
|
89203
|
+
ok: false,
|
|
89204
|
+
reason: "cross-dimension"
|
|
89205
|
+
};
|
|
89206
|
+
return {
|
|
89207
|
+
ok: false,
|
|
89208
|
+
reason: "unknown-unit"
|
|
89209
|
+
};
|
|
89210
|
+
}
|
|
89211
|
+
const converted = tryConvertUnit(input.value, unit, base);
|
|
89212
|
+
if (converted === void 0) return unitDimension(unit) === void 0 ? {
|
|
89213
|
+
ok: false,
|
|
89214
|
+
reason: "unknown-unit"
|
|
89215
|
+
} : {
|
|
89216
|
+
ok: false,
|
|
89217
|
+
reason: "cross-dimension"
|
|
89218
|
+
};
|
|
89219
|
+
return {
|
|
89220
|
+
ok: true,
|
|
89221
|
+
value: converted
|
|
89222
|
+
};
|
|
89223
|
+
}
|
|
88227
89224
|
var MB = 1024 * 1024;
|
|
88228
89225
|
1024 * MB, 3072 * MB;
|
|
88229
89226
|
//#endregion
|
|
@@ -94610,6 +95607,23 @@ function capForQuantity(quantity) {
|
|
|
94610
95607
|
* the generic `numeric-sensor` cap but carry a typed role, so the UI renders a
|
|
94611
95608
|
* wind / rain / UV / solar glyph instead of a bare hash.
|
|
94612
95609
|
*/
|
|
95610
|
+
/** Ecowitt quantity → the stored reading quantity. Unknown names return null. */
|
|
95611
|
+
function readingQuantityFor(quantity) {
|
|
95612
|
+
switch (quantity) {
|
|
95613
|
+
case "temperature": return "temperature";
|
|
95614
|
+
case "humidity": return "humidity";
|
|
95615
|
+
case "pressure": return "pressure";
|
|
95616
|
+
case "wind_speed": return "speed";
|
|
95617
|
+
case "wind_direction": return "angle";
|
|
95618
|
+
case "precipitation": return "precipitation";
|
|
95619
|
+
case "precipitation_rate": return "precipitation-rate";
|
|
95620
|
+
case "uv": return "index";
|
|
95621
|
+
case "irradiance": return "irradiance";
|
|
95622
|
+
case "lightning_distance": return "length";
|
|
95623
|
+
case "lightning_count": return "count";
|
|
95624
|
+
default: return null;
|
|
95625
|
+
}
|
|
95626
|
+
}
|
|
94613
95627
|
function roleForQuantity(quantity) {
|
|
94614
95628
|
switch (quantity) {
|
|
94615
95629
|
case "temperature": return DeviceRole.TemperatureSensor;
|
|
@@ -94868,13 +95882,6 @@ var ecowittSensorSchema = object({
|
|
|
94868
95882
|
system: literal("ecowitt").optional(),
|
|
94869
95883
|
integrationId: string().optional()
|
|
94870
95884
|
});
|
|
94871
|
-
/**
|
|
94872
|
-
* Slice key the V-battery `numeric-sensor` cap writes/reads. It is the cap's own
|
|
94873
|
-
* name (`'numeric-sensor'`) — the same key the codegen `getStatus` reads — so
|
|
94874
|
-
* the manual `setCapState` here lands in the slice the framework serves. Only
|
|
94875
|
-
* registered when the PRIMARY cap is NOT itself `numeric-sensor` (no collision).
|
|
94876
|
-
*/
|
|
94877
|
-
var BATTERY_VOLTAGE_CAP = numericSensorCapability.name;
|
|
94878
95885
|
var SUPPORTED_CAPS = new Set([
|
|
94879
95886
|
"temperature-sensor",
|
|
94880
95887
|
"humidity-sensor",
|
|
@@ -94893,16 +95900,10 @@ var SUPPORTED_CAPS = new Set([
|
|
|
94893
95900
|
* library discriminates two battery forms via `batteryUnit`:
|
|
94894
95901
|
* - `'%'` (binary/bar batteries, already decoded to a percent) → the typed
|
|
94895
95902
|
* `battery` cap (which models a 0..100 percentage).
|
|
94896
|
-
* - `'V'` (raw voltage, e.g. WS90/WH90/WS85 capacitor + soil/leaf push) →
|
|
94897
|
-
* `
|
|
94898
|
-
*
|
|
94899
|
-
*
|
|
94900
|
-
*
|
|
94901
|
-
* A child registers AT MOST one battery cap. When the child's PRIMARY sensor cap
|
|
94902
|
-
* is itself `numeric-sensor` (e.g. a precipitation channel) a V-battery cannot
|
|
94903
|
-
* also be hosted as a second `numeric-sensor` on the same device (the cap slice
|
|
94904
|
-
* is keyed by cap name) — that rare case is skipped with a debug note rather
|
|
94905
|
-
* than adding a new cap.
|
|
95903
|
+
* - `'V'` (raw voltage, e.g. WS90/WH90/WS85 capacitor + soil/leaf push) →
|
|
95904
|
+
* `battery.volts`. A voltage has no honest percentage, so `percentage`
|
|
95905
|
+
* stays null. The field lives on `battery`, so a numeric primary sensor
|
|
95906
|
+
* and a voltage battery do not share a slice.
|
|
94906
95907
|
*/
|
|
94907
95908
|
var EcowittSensorDevice = class extends BaseDevice {
|
|
94908
95909
|
features = [];
|
|
@@ -94910,12 +95911,12 @@ var EcowittSensorDevice = class extends BaseDevice {
|
|
|
94910
95911
|
gatewayId;
|
|
94911
95912
|
capName;
|
|
94912
95913
|
valueKey;
|
|
95914
|
+
ecowittQuantity;
|
|
94913
95915
|
/**
|
|
94914
95916
|
* Which battery cap (if any) was registered for this sensor:
|
|
94915
95917
|
* - `'percent'` → the typed `battery` cap (percentage),
|
|
94916
|
-
* - `'voltage'` →
|
|
94917
|
-
* - `'none'` → no battery cap
|
|
94918
|
-
* whose primary cap is already `numeric-sensor`).
|
|
95918
|
+
* - `'voltage'` → `battery.volts` (percentage stays null),
|
|
95919
|
+
* - `'none'` → no battery cap.
|
|
94919
95920
|
*/
|
|
94920
95921
|
batteryMode = "none";
|
|
94921
95922
|
snapshotUnsub = null;
|
|
@@ -94927,10 +95928,13 @@ var EcowittSensorDevice = class extends BaseDevice {
|
|
|
94927
95928
|
const cap = capForQuantity(persisted.quantity);
|
|
94928
95929
|
this.capName = SUPPORTED_CAPS.has(cap) ? cap : "numeric-sensor";
|
|
94929
95930
|
this.valueKey = valueFieldForCap(this.capName);
|
|
95931
|
+
this.ecowittQuantity = String(persisted.quantity);
|
|
94930
95932
|
this.online = true;
|
|
95933
|
+
const named = readingQuantityFor(persisted.quantity);
|
|
94931
95934
|
this.updateSourceInfo({
|
|
94932
95935
|
id: this.sensorId,
|
|
94933
|
-
system: "ecowitt"
|
|
95936
|
+
system: "ecowitt",
|
|
95937
|
+
...this.capName === "numeric-sensor" && named !== null ? { quantity: named } : {}
|
|
94934
95938
|
});
|
|
94935
95939
|
}
|
|
94936
95940
|
resolveSensor() {
|
|
@@ -94974,10 +95978,10 @@ var EcowittSensorDevice = class extends BaseDevice {
|
|
|
94974
95978
|
const sensor = this.resolveSensor();
|
|
94975
95979
|
if (sensor === null) return;
|
|
94976
95980
|
const lastFetchedAt = sensor.lastUpdated || Date.now();
|
|
95981
|
+
const stored = this.toBase(sensor.value, sensor.unit);
|
|
94977
95982
|
const slice = {
|
|
94978
|
-
[this.valueKey]: clampForCap(this.capName,
|
|
94979
|
-
lastFetchedAt
|
|
94980
|
-
...sensor.unit.length > 0 ? { unit: sensor.unit } : {}
|
|
95983
|
+
[this.valueKey]: stored === null ? null : clampForCap(this.capName, stored),
|
|
95984
|
+
lastFetchedAt
|
|
94981
95985
|
};
|
|
94982
95986
|
this.runtimeState.setCapState(this.capName, slice);
|
|
94983
95987
|
if (this.batteryMode === "percent" && sensor.batteryUnit === "%" && typeof sensor.battery === "number") {
|
|
@@ -94991,11 +95995,13 @@ var EcowittSensorDevice = class extends BaseDevice {
|
|
|
94991
95995
|
}
|
|
94992
95996
|
if (this.batteryMode === "voltage" && sensor.batteryUnit === "V" && typeof sensor.battery === "number" && Number.isFinite(sensor.battery)) {
|
|
94993
95997
|
const next = {
|
|
94994
|
-
|
|
94995
|
-
|
|
94996
|
-
|
|
95998
|
+
percentage: null,
|
|
95999
|
+
volts: sensor.battery,
|
|
96000
|
+
charging: "none",
|
|
96001
|
+
sleeping: false,
|
|
96002
|
+
lastUpdated: lastFetchedAt
|
|
94997
96003
|
};
|
|
94998
|
-
this.runtimeState.setCapState(
|
|
96004
|
+
this.runtimeState.setCapState("battery", next);
|
|
94999
96005
|
}
|
|
95000
96006
|
}
|
|
95001
96007
|
registerSensorCap() {
|
|
@@ -95048,11 +96054,8 @@ var EcowittSensorDevice = class extends BaseDevice {
|
|
|
95048
96054
|
}
|
|
95049
96055
|
}
|
|
95050
96056
|
/**
|
|
95051
|
-
* Register the
|
|
95052
|
-
*
|
|
95053
|
-
* - `'V'` → `numeric-sensor` cap carrying volts (unit `'V'`), unless the
|
|
95054
|
-
* sensor's primary cap is already `numeric-sensor` (slice-key collision),
|
|
95055
|
-
* in which case it is skipped with a debug note.
|
|
96057
|
+
* Register the `battery` cap. Percent batteries fill `percentage`. Voltage
|
|
96058
|
+
* batteries fill `volts` and leave `percentage` null.
|
|
95056
96059
|
*/
|
|
95057
96060
|
maybeRegisterBatteryCap() {
|
|
95058
96061
|
const sensor = this.resolveSensor();
|
|
@@ -95079,29 +96082,63 @@ var EcowittSensorDevice = class extends BaseDevice {
|
|
|
95079
96082
|
this.runtimeState.setCapState("battery", COLD);
|
|
95080
96083
|
this.batteryMode = "percent";
|
|
95081
96084
|
}
|
|
95082
|
-
/**
|
|
95083
|
-
* Register a `numeric-sensor` (unit `'V'`) cap for a raw-voltage battery. The
|
|
95084
|
-
* voltage has no honest percentage, so the percentage-shaped `battery` cap is
|
|
95085
|
-
* not used. Skipped when the primary cap already occupies the `numeric-sensor`
|
|
95086
|
-
* slice on this device (a second instance would collide).
|
|
95087
|
-
*/
|
|
96085
|
+
/** Voltage batteries share the `battery` cap. `percentage` stays unknown. */
|
|
95088
96086
|
registerVoltageBatteryCap() {
|
|
95089
|
-
if (this.capName === "numeric-sensor") {
|
|
95090
|
-
this.ctx.logger.debug("ecowitt sensor: V-battery not surfaced — primary cap already numeric-sensor", { meta: { sensorId: this.sensorId } });
|
|
95091
|
-
return;
|
|
95092
|
-
}
|
|
95093
96087
|
const COLD = {
|
|
95094
|
-
|
|
95095
|
-
|
|
95096
|
-
|
|
96088
|
+
percentage: null,
|
|
96089
|
+
volts: null,
|
|
96090
|
+
charging: "unknown",
|
|
96091
|
+
sleeping: false,
|
|
96092
|
+
lastUpdated: 0
|
|
95097
96093
|
};
|
|
95098
|
-
this.ctx.registerNativeCap(
|
|
95099
|
-
|
|
96094
|
+
this.ctx.registerNativeCap(batteryCapability, {
|
|
96095
|
+
getStatus: async () => this.runtimeState.getCapState("battery") ?? COLD,
|
|
96096
|
+
wakeForStream: async () => ({
|
|
96097
|
+
awoke: true,
|
|
96098
|
+
durationMs: 0
|
|
96099
|
+
})
|
|
96100
|
+
});
|
|
96101
|
+
this.runtimeState.setCapState("battery", COLD);
|
|
95100
96102
|
this.batteryMode = "voltage";
|
|
95101
96103
|
}
|
|
95102
96104
|
readSlice(fallback) {
|
|
95103
96105
|
return this.runtimeState.getCapState(this.capName) ?? fallback;
|
|
95104
96106
|
}
|
|
96107
|
+
/** Station number → the quantity's base. An unknown unit publishes null. */
|
|
96108
|
+
toBase(value, unit) {
|
|
96109
|
+
const named = readingQuantityFor(this.ecowittQuantity);
|
|
96110
|
+
const fromUnit = unit.length === 0 ? void 0 : quantityFromUnit(unit);
|
|
96111
|
+
const quantity = named ?? (unit.length === 0 ? "none" : fromUnit ?? null);
|
|
96112
|
+
if (quantity === null || quantity === "device") {
|
|
96113
|
+
this.ctx.logger.warn("numeric reading refused", {
|
|
96114
|
+
tags: { deviceId: this.id },
|
|
96115
|
+
meta: {
|
|
96116
|
+
quantity: "device",
|
|
96117
|
+
unit,
|
|
96118
|
+
reason: "unknown-unit"
|
|
96119
|
+
}
|
|
96120
|
+
});
|
|
96121
|
+
return null;
|
|
96122
|
+
}
|
|
96123
|
+
const result = ingestReading({
|
|
96124
|
+
value,
|
|
96125
|
+
rawUnit: unit.length > 0 ? unit : void 0,
|
|
96126
|
+
quantity
|
|
96127
|
+
});
|
|
96128
|
+
if (!result.ok) {
|
|
96129
|
+
this.ctx.logger.warn("numeric reading refused", {
|
|
96130
|
+
tags: { deviceId: this.id },
|
|
96131
|
+
meta: {
|
|
96132
|
+
quantity,
|
|
96133
|
+
unit,
|
|
96134
|
+
reason: result.reason
|
|
96135
|
+
}
|
|
96136
|
+
});
|
|
96137
|
+
return null;
|
|
96138
|
+
}
|
|
96139
|
+
if (this.capName === "numeric-sensor" && this.sourceInfo.quantity !== quantity) this.updateSourceInfo({ quantity });
|
|
96140
|
+
return result.value;
|
|
96141
|
+
}
|
|
95105
96142
|
};
|
|
95106
96143
|
/** Clamp a raw SI value to the cap's slice constraints (humidity/lux are bounded). */
|
|
95107
96144
|
function clampForCap(cap, value) {
|