@camstack/addon-provider-dreame 0.2.117 → 0.2.118
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/dist/addon.js +1671 -278
- package/dist/addon.mjs +1671 -278
- package/package.json +2 -2
package/dist/addon.js
CHANGED
|
@@ -46344,6 +46344,11 @@ var VACUUM_CONSUMABLES = [
|
|
|
46344
46344
|
siid: 9,
|
|
46345
46345
|
piid: 2
|
|
46346
46346
|
},
|
|
46347
|
+
timeLeft: {
|
|
46348
|
+
siid: 9,
|
|
46349
|
+
piid: 1,
|
|
46350
|
+
unit: "hours"
|
|
46351
|
+
},
|
|
46347
46352
|
reset: {
|
|
46348
46353
|
siid: 9,
|
|
46349
46354
|
aiid: 1
|
|
@@ -46356,6 +46361,11 @@ var VACUUM_CONSUMABLES = [
|
|
|
46356
46361
|
siid: 10,
|
|
46357
46362
|
piid: 2
|
|
46358
46363
|
},
|
|
46364
|
+
timeLeft: {
|
|
46365
|
+
siid: 10,
|
|
46366
|
+
piid: 1,
|
|
46367
|
+
unit: "hours"
|
|
46368
|
+
},
|
|
46359
46369
|
reset: {
|
|
46360
46370
|
siid: 10,
|
|
46361
46371
|
aiid: 1
|
|
@@ -46368,6 +46378,11 @@ var VACUUM_CONSUMABLES = [
|
|
|
46368
46378
|
siid: 11,
|
|
46369
46379
|
piid: 1
|
|
46370
46380
|
},
|
|
46381
|
+
timeLeft: {
|
|
46382
|
+
siid: 11,
|
|
46383
|
+
piid: 2,
|
|
46384
|
+
unit: "hours"
|
|
46385
|
+
},
|
|
46371
46386
|
reset: {
|
|
46372
46387
|
siid: 11,
|
|
46373
46388
|
aiid: 1
|
|
@@ -46375,11 +46390,16 @@ var VACUUM_CONSUMABLES = [
|
|
|
46375
46390
|
},
|
|
46376
46391
|
{
|
|
46377
46392
|
key: "sensor",
|
|
46378
|
-
label: "Sensor",
|
|
46393
|
+
label: "Sensor Dirty",
|
|
46379
46394
|
life: {
|
|
46380
46395
|
siid: 16,
|
|
46381
46396
|
piid: 1
|
|
46382
46397
|
},
|
|
46398
|
+
timeLeft: {
|
|
46399
|
+
siid: 16,
|
|
46400
|
+
piid: 2,
|
|
46401
|
+
unit: "hours"
|
|
46402
|
+
},
|
|
46383
46403
|
reset: {
|
|
46384
46404
|
siid: 16,
|
|
46385
46405
|
aiid: 1
|
|
@@ -46392,6 +46412,11 @@ var VACUUM_CONSUMABLES = [
|
|
|
46392
46412
|
siid: 17,
|
|
46393
46413
|
piid: 1
|
|
46394
46414
|
},
|
|
46415
|
+
timeLeft: {
|
|
46416
|
+
siid: 17,
|
|
46417
|
+
piid: 2,
|
|
46418
|
+
unit: "hours"
|
|
46419
|
+
},
|
|
46395
46420
|
reset: {
|
|
46396
46421
|
siid: 17,
|
|
46397
46422
|
aiid: 1
|
|
@@ -46404,6 +46429,11 @@ var VACUUM_CONSUMABLES = [
|
|
|
46404
46429
|
siid: 18,
|
|
46405
46430
|
piid: 1
|
|
46406
46431
|
},
|
|
46432
|
+
timeLeft: {
|
|
46433
|
+
siid: 18,
|
|
46434
|
+
piid: 2,
|
|
46435
|
+
unit: "hours"
|
|
46436
|
+
},
|
|
46407
46437
|
reset: {
|
|
46408
46438
|
siid: 18,
|
|
46409
46439
|
aiid: 1
|
|
@@ -46416,6 +46446,11 @@ var VACUUM_CONSUMABLES = [
|
|
|
46416
46446
|
siid: 19,
|
|
46417
46447
|
piid: 2
|
|
46418
46448
|
},
|
|
46449
|
+
timeLeft: {
|
|
46450
|
+
siid: 19,
|
|
46451
|
+
piid: 1,
|
|
46452
|
+
unit: "days"
|
|
46453
|
+
},
|
|
46419
46454
|
reset: {
|
|
46420
46455
|
siid: 19,
|
|
46421
46456
|
aiid: 1
|
|
@@ -46428,6 +46463,11 @@ var VACUUM_CONSUMABLES = [
|
|
|
46428
46463
|
siid: 20,
|
|
46429
46464
|
piid: 1
|
|
46430
46465
|
},
|
|
46466
|
+
timeLeft: {
|
|
46467
|
+
siid: 20,
|
|
46468
|
+
piid: 2,
|
|
46469
|
+
unit: "days"
|
|
46470
|
+
},
|
|
46431
46471
|
reset: {
|
|
46432
46472
|
siid: 20,
|
|
46433
46473
|
aiid: 1
|
|
@@ -46440,6 +46480,11 @@ var VACUUM_CONSUMABLES = [
|
|
|
46440
46480
|
siid: 24,
|
|
46441
46481
|
piid: 1
|
|
46442
46482
|
},
|
|
46483
|
+
timeLeft: {
|
|
46484
|
+
siid: 24,
|
|
46485
|
+
piid: 2,
|
|
46486
|
+
unit: "days"
|
|
46487
|
+
},
|
|
46443
46488
|
reset: {
|
|
46444
46489
|
siid: 24,
|
|
46445
46490
|
aiid: 1
|
|
@@ -46452,6 +46497,11 @@ var VACUUM_CONSUMABLES = [
|
|
|
46452
46497
|
siid: 29,
|
|
46453
46498
|
piid: 2
|
|
46454
46499
|
},
|
|
46500
|
+
timeLeft: {
|
|
46501
|
+
siid: 29,
|
|
46502
|
+
piid: 1,
|
|
46503
|
+
unit: "days"
|
|
46504
|
+
},
|
|
46455
46505
|
reset: {
|
|
46456
46506
|
siid: 29,
|
|
46457
46507
|
aiid: 1
|
|
@@ -46459,11 +46509,16 @@ var VACUUM_CONSUMABLES = [
|
|
|
46459
46509
|
},
|
|
46460
46510
|
{
|
|
46461
46511
|
key: "wheel",
|
|
46462
|
-
label: "Wheel",
|
|
46512
|
+
label: "Wheel Dirty",
|
|
46463
46513
|
life: {
|
|
46464
46514
|
siid: 30,
|
|
46465
46515
|
piid: 2
|
|
46466
46516
|
},
|
|
46517
|
+
timeLeft: {
|
|
46518
|
+
siid: 30,
|
|
46519
|
+
piid: 1,
|
|
46520
|
+
unit: "days"
|
|
46521
|
+
},
|
|
46467
46522
|
reset: {
|
|
46468
46523
|
siid: 30,
|
|
46469
46524
|
aiid: 1
|
|
@@ -46476,6 +46531,11 @@ var VACUUM_CONSUMABLES = [
|
|
|
46476
46531
|
siid: 31,
|
|
46477
46532
|
piid: 2
|
|
46478
46533
|
},
|
|
46534
|
+
timeLeft: {
|
|
46535
|
+
siid: 31,
|
|
46536
|
+
piid: 1,
|
|
46537
|
+
unit: "days"
|
|
46538
|
+
},
|
|
46479
46539
|
reset: {
|
|
46480
46540
|
siid: 31,
|
|
46481
46541
|
aiid: 1
|
|
@@ -46488,15 +46548,13 @@ var VACUUM_CONSUMABLES = [
|
|
|
46488
46548
|
siid: 27,
|
|
46489
46549
|
piid: 17
|
|
46490
46550
|
},
|
|
46551
|
+
timeLeft: null,
|
|
46491
46552
|
reset: null
|
|
46492
46553
|
}
|
|
46493
46554
|
];
|
|
46494
46555
|
function consumableSpec(key2) {
|
|
46495
46556
|
return VACUUM_CONSUMABLES.find((c) => c.key === key2);
|
|
46496
46557
|
}
|
|
46497
|
-
function isDreameConsumableKey(key2) {
|
|
46498
|
-
return VACUUM_CONSUMABLES.some((c) => c.key === key2);
|
|
46499
|
-
}
|
|
46500
46558
|
var X50_AI_FEATURES = [
|
|
46501
46559
|
"obstacleDetection",
|
|
46502
46560
|
"petDetection",
|
|
@@ -49573,6 +49631,15 @@ var OssFetcher = class {
|
|
|
49573
49631
|
this.cache.clear();
|
|
49574
49632
|
}
|
|
49575
49633
|
};
|
|
49634
|
+
function consumableProps(spec) {
|
|
49635
|
+
return spec.timeLeft === null ? [spec.life] : [spec.life, {
|
|
49636
|
+
siid: spec.timeLeft.siid,
|
|
49637
|
+
piid: spec.timeLeft.piid
|
|
49638
|
+
}];
|
|
49639
|
+
}
|
|
49640
|
+
function asError(err) {
|
|
49641
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
49642
|
+
}
|
|
49576
49643
|
var SUCTION = enumLookup([
|
|
49577
49644
|
0,
|
|
49578
49645
|
1,
|
|
@@ -49731,23 +49798,82 @@ var VacuumDevice = class _VacuumDevice extends BaseDevice$1 {
|
|
|
49731
49798
|
});
|
|
49732
49799
|
}
|
|
49733
49800
|
/**
|
|
49734
|
-
*
|
|
49735
|
-
* {@link
|
|
49736
|
-
*
|
|
49737
|
-
|
|
49801
|
+
* Time left before a consumable needs attention, in the unit the robot counts
|
|
49802
|
+
* it in (see {@link ConsumableSpec.timeLeft}), or `null` when the model does
|
|
49803
|
+
* not report it or the key has no time-left twin.
|
|
49804
|
+
*/
|
|
49805
|
+
consumableTimeLeft(key2) {
|
|
49806
|
+
const spec = consumableSpec(key2);
|
|
49807
|
+
if (spec === void 0 || spec.timeLeft === null) return null;
|
|
49808
|
+
const value = this.#num(spec.timeLeft.siid, spec.timeLeft.piid);
|
|
49809
|
+
return value === null ? null : {
|
|
49810
|
+
value,
|
|
49811
|
+
unit: spec.timeLeft.unit
|
|
49812
|
+
};
|
|
49813
|
+
}
|
|
49814
|
+
/**
|
|
49815
|
+
* Seed EVERY consumable life property AND its time-left twin from the cloud
|
|
49816
|
+
* shadow (no robot wake), in one call, so {@link supportedConsumables} and
|
|
49817
|
+
* {@link consumableTimeLeft} reflect the full set on a docked robot. The
|
|
49818
|
+
* robot rarely re-pushes these over MQTT, so a consumer that wants them fresh
|
|
49819
|
+
* must call this again — the library does not poll them.
|
|
49738
49820
|
*/
|
|
49739
49821
|
async refreshConsumables() {
|
|
49740
|
-
await this.refreshCachedProperties(VACUUM_CONSUMABLES.
|
|
49822
|
+
await this.refreshCachedProperties(VACUUM_CONSUMABLES.flatMap(consumableProps));
|
|
49741
49823
|
}
|
|
49742
49824
|
/**
|
|
49743
49825
|
* Reset (mark replaced → life back to 100%) one consumable via its MIoT reset
|
|
49744
|
-
* action
|
|
49745
|
-
*
|
|
49826
|
+
* action, then re-read that consumable's life % and time left ONCE so the
|
|
49827
|
+
* cache — and every getter after this resolves — reflects the reset. The
|
|
49828
|
+
* re-read is live; if the robot does not answer it falls back to the cloud
|
|
49829
|
+
* shadow (which may still hold the pre-reset value until the robot reports),
|
|
49830
|
+
* and `refreshedFrom` says which one answered.
|
|
49831
|
+
*
|
|
49832
|
+
* Rejects when the model exposes no reset action for the key (e.g.
|
|
49833
|
+
* `dust-bag`), the key is unknown, or the action itself fails. A failed
|
|
49834
|
+
* RE-READ does not reject: the reset happened, and the result says the values
|
|
49835
|
+
* are unknown (`refreshedFrom: null`).
|
|
49746
49836
|
*/
|
|
49747
49837
|
async resetConsumable(key2) {
|
|
49748
49838
|
const spec = consumableSpec(key2);
|
|
49749
49839
|
if (spec === void 0 || spec.reset === null) throw new DreameError(`resetConsumable: no reset action for consumable "${key2}"`);
|
|
49750
|
-
|
|
49840
|
+
const actionResult = await this.callAction(spec.reset.siid, spec.reset.aiid, []);
|
|
49841
|
+
const refresh2 = await this.#rereadConsumable(spec);
|
|
49842
|
+
const fresh = refresh2.refreshedFrom !== null;
|
|
49843
|
+
return {
|
|
49844
|
+
key: key2,
|
|
49845
|
+
actionResult,
|
|
49846
|
+
refreshedFrom: refresh2.refreshedFrom,
|
|
49847
|
+
leftPct: fresh ? this.consumableLeftPct(key2) : null,
|
|
49848
|
+
timeLeft: fresh ? this.consumableTimeLeft(key2) : null,
|
|
49849
|
+
refreshError: refresh2.error
|
|
49850
|
+
};
|
|
49851
|
+
}
|
|
49852
|
+
/** One targeted re-read of a consumable's properties: live, else the cloud shadow. */
|
|
49853
|
+
async #rereadConsumable(spec) {
|
|
49854
|
+
const props = consumableProps(spec);
|
|
49855
|
+
let liveError;
|
|
49856
|
+
try {
|
|
49857
|
+
await this.refreshProperties(props);
|
|
49858
|
+
return {
|
|
49859
|
+
refreshedFrom: "device",
|
|
49860
|
+
error: null
|
|
49861
|
+
};
|
|
49862
|
+
} catch (err) {
|
|
49863
|
+
liveError = asError(err);
|
|
49864
|
+
}
|
|
49865
|
+
try {
|
|
49866
|
+
await this.refreshCachedProperties(props);
|
|
49867
|
+
return {
|
|
49868
|
+
refreshedFrom: "cloud-shadow",
|
|
49869
|
+
error: liveError
|
|
49870
|
+
};
|
|
49871
|
+
} catch (err) {
|
|
49872
|
+
return {
|
|
49873
|
+
refreshedFrom: null,
|
|
49874
|
+
error: asError(err)
|
|
49875
|
+
};
|
|
49876
|
+
}
|
|
49751
49877
|
}
|
|
49752
49878
|
/**
|
|
49753
49879
|
* The raw `AI_DETECTION` value (siid 4 piid 22) — an int bitmask or a JSON
|
|
@@ -58137,7 +58263,7 @@ var ZodIssueCode = {
|
|
|
58137
58263
|
var ZodFirstPartyTypeKind;
|
|
58138
58264
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
58139
58265
|
//#endregion
|
|
58140
|
-
//#region ../types/dist/sleep-
|
|
58266
|
+
//#region ../types/dist/sleep-DU7mu4wb.mjs
|
|
58141
58267
|
/**
|
|
58142
58268
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
58143
58269
|
* window to float samples (D455).
|
|
@@ -60118,11 +60244,17 @@ var RawStateResultSchema = object({
|
|
|
60118
60244
|
method(object({ deviceId: number() }), array(StreamSourceEntrySchema)), method(object({ deviceId: number() }), array(ConfigEntrySchema$1)), method(object({
|
|
60119
60245
|
deviceId: number(),
|
|
60120
60246
|
values: record(string(), unknown())
|
|
60121
|
-
}), _void(), {
|
|
60247
|
+
}), _void(), {
|
|
60248
|
+
kind: "mutation",
|
|
60249
|
+
auth: "admin"
|
|
60250
|
+
}), method(object({
|
|
60122
60251
|
deviceId: number(),
|
|
60123
60252
|
action: string().min(1),
|
|
60124
60253
|
input: unknown()
|
|
60125
|
-
}), unknown(), { kind: "mutation" }), method(object({ deviceId: number() }), _void(), {
|
|
60254
|
+
}), unknown(), { kind: "mutation" }), method(object({ deviceId: number() }), _void(), {
|
|
60255
|
+
kind: "mutation",
|
|
60256
|
+
auth: "admin"
|
|
60257
|
+
}), method(object({ deviceId: number() }), unknown().nullable()), method(object({ deviceId: number() }), RawStateResultSchema.nullable(), { auth: "protected" });
|
|
60126
60258
|
//#endregion
|
|
60127
60259
|
//#region ../types/dist/err-msg-IQTHeDzc.mjs
|
|
60128
60260
|
/**
|
|
@@ -64358,7 +64490,7 @@ method(ListInputSchema, array(BrokerInfoSchema$1)), method(GetInputSchema, Broke
|
|
|
64358
64490
|
}), method(UnsubscribeInputSchema, _void(), {
|
|
64359
64491
|
kind: "mutation",
|
|
64360
64492
|
auth: "admin"
|
|
64361
|
-
}), method(GetStateInputSchema, unknown().nullable()), method(_void(), RegistryStatusSchema);
|
|
64493
|
+
}), method(GetStateInputSchema, unknown().nullable(), { auth: "admin" }), method(_void(), RegistryStatusSchema);
|
|
64362
64494
|
DeviceType.Camera;
|
|
64363
64495
|
/** Stream delivery format. (Relocated from the retired `streaming-engine` cap.) */
|
|
64364
64496
|
var StreamFormatSchema = _enum([
|
|
@@ -65541,17 +65673,20 @@ method(object({
|
|
|
65541
65673
|
namespace: string().optional(),
|
|
65542
65674
|
collection: string(),
|
|
65543
65675
|
key: string()
|
|
65544
|
-
}), unknown()), method(object({
|
|
65676
|
+
}), unknown(), { auth: "admin" }), method(object({
|
|
65545
65677
|
namespace: string().optional(),
|
|
65546
65678
|
collection: string(),
|
|
65547
65679
|
key: string(),
|
|
65548
65680
|
value: unknown()
|
|
65549
|
-
}), _void(), {
|
|
65681
|
+
}), _void(), {
|
|
65682
|
+
kind: "mutation",
|
|
65683
|
+
auth: "admin"
|
|
65684
|
+
}), method(object({
|
|
65550
65685
|
namespace: string().optional(),
|
|
65551
65686
|
collection: string(),
|
|
65552
65687
|
filter: QueryFilterSchema.optional(),
|
|
65553
65688
|
columns: array(string()).readonly().optional()
|
|
65554
|
-
}), array(SettingsRecordSchema).readonly()), method(object({
|
|
65689
|
+
}), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
|
|
65555
65690
|
namespace: string().optional(),
|
|
65556
65691
|
collection: string(),
|
|
65557
65692
|
/** Declared columns identifying the slot; must be covered by a UNIQUE
|
|
@@ -65561,7 +65696,10 @@ method(object({
|
|
|
65561
65696
|
}), object({
|
|
65562
65697
|
/** The id the row HAS: assigned by SQLite on an insert, or the existing
|
|
65563
65698
|
* row's own id on an update. */
|
|
65564
|
-
id: union([string(), number()]) }), {
|
|
65699
|
+
id: union([string(), number()]) }), {
|
|
65700
|
+
kind: "mutation",
|
|
65701
|
+
auth: "admin"
|
|
65702
|
+
}), method(object({
|
|
65565
65703
|
namespace: string().optional(),
|
|
65566
65704
|
collection: string(),
|
|
65567
65705
|
record: object({
|
|
@@ -65574,38 +65712,56 @@ id: union([string(), number()]) }), { kind: "mutation" }), method(object({
|
|
|
65574
65712
|
* for an absent one, or the ROWID SQLite assigned on an `INTEGER`
|
|
65575
65713
|
* primary key — which is the only place an auto key is knowable.
|
|
65576
65714
|
*/
|
|
65577
|
-
id: union([string(), number()]) }), {
|
|
65715
|
+
id: union([string(), number()]) }), {
|
|
65716
|
+
kind: "mutation",
|
|
65717
|
+
auth: "admin"
|
|
65718
|
+
}), method(object({
|
|
65578
65719
|
namespace: string().optional(),
|
|
65579
65720
|
collection: string(),
|
|
65580
65721
|
records: array(BulkRecordSchema).readonly()
|
|
65581
|
-
}), object({ inserted: number().int() }), {
|
|
65722
|
+
}), object({ inserted: number().int() }), {
|
|
65723
|
+
kind: "mutation",
|
|
65724
|
+
auth: "admin"
|
|
65725
|
+
}), method(object({
|
|
65582
65726
|
namespace: string().optional(),
|
|
65583
65727
|
collection: string(),
|
|
65584
65728
|
id: string(),
|
|
65585
65729
|
data: record(string(), unknown())
|
|
65586
|
-
}), _void(), {
|
|
65730
|
+
}), _void(), {
|
|
65731
|
+
kind: "mutation",
|
|
65732
|
+
auth: "admin"
|
|
65733
|
+
}), method(object({
|
|
65587
65734
|
namespace: string().optional(),
|
|
65588
65735
|
collection: string(),
|
|
65589
65736
|
key: string()
|
|
65590
|
-
}), _void(), {
|
|
65737
|
+
}), _void(), {
|
|
65738
|
+
kind: "mutation",
|
|
65739
|
+
auth: "admin"
|
|
65740
|
+
}), method(object({
|
|
65591
65741
|
namespace: string().optional(),
|
|
65592
65742
|
collection: string(),
|
|
65593
65743
|
filter: MutationFilterSchema
|
|
65594
|
-
}), object({ deleted: number().int() }), {
|
|
65744
|
+
}), object({ deleted: number().int() }), {
|
|
65745
|
+
kind: "mutation",
|
|
65746
|
+
auth: "admin"
|
|
65747
|
+
}), method(object({
|
|
65595
65748
|
namespace: string().optional(),
|
|
65596
65749
|
collection: string(),
|
|
65597
65750
|
filter: MutationFilterSchema,
|
|
65598
65751
|
data: record(string(), unknown())
|
|
65599
|
-
}), object({ updated: number().int() }), {
|
|
65752
|
+
}), object({ updated: number().int() }), {
|
|
65753
|
+
kind: "mutation",
|
|
65754
|
+
auth: "admin"
|
|
65755
|
+
}), method(object({
|
|
65600
65756
|
namespace: string().optional(),
|
|
65601
65757
|
collection: string(),
|
|
65602
65758
|
filter: QueryFilterSchema.optional()
|
|
65603
|
-
}), number()), method(object({
|
|
65759
|
+
}), number(), { auth: "admin" }), method(object({
|
|
65604
65760
|
namespace: string().optional(),
|
|
65605
65761
|
collection: string(),
|
|
65606
65762
|
fields: array(AggregateFieldSchema).readonly(),
|
|
65607
65763
|
filter: QueryFilterSchema.optional()
|
|
65608
|
-
}), AggregateResultSchema), method(object({
|
|
65764
|
+
}), AggregateResultSchema, { auth: "admin" }), method(object({
|
|
65609
65765
|
namespace: string().optional(),
|
|
65610
65766
|
collection: string(),
|
|
65611
65767
|
field: string(),
|
|
@@ -65615,15 +65771,18 @@ id: union([string(), number()]) }), { kind: "mutation" }), method(object({
|
|
|
65615
65771
|
}), array(object({
|
|
65616
65772
|
bucket: number().int(),
|
|
65617
65773
|
count: number().int()
|
|
65618
|
-
})).readonly()), method(object({
|
|
65774
|
+
})).readonly(), { auth: "admin" }), method(object({
|
|
65619
65775
|
namespace: string().optional(),
|
|
65620
65776
|
collection: string()
|
|
65621
|
-
}), boolean()), method(object({
|
|
65777
|
+
}), boolean(), { auth: "admin" }), method(object({
|
|
65622
65778
|
namespace: string().optional(),
|
|
65623
65779
|
collection: string(),
|
|
65624
65780
|
columns: array(CollectionColumnSchema).readonly(),
|
|
65625
65781
|
indexes: array(CollectionIndexSchema).readonly().optional()
|
|
65626
|
-
}), _void(), {
|
|
65782
|
+
}), _void(), {
|
|
65783
|
+
kind: "mutation",
|
|
65784
|
+
auth: "admin"
|
|
65785
|
+
});
|
|
65627
65786
|
/**
|
|
65628
65787
|
* What one engine says about itself. The orchestrator uses `kind` to pick
|
|
65629
65788
|
* a registrant for a collection; `engineId` is what a log line names when
|
|
@@ -66431,7 +66590,13 @@ var ExposeInputSchema = object({
|
|
|
66431
66590
|
capabilities: array(string()).optional()
|
|
66432
66591
|
});
|
|
66433
66592
|
var UnexposeInputSchema = object({ deviceId: string() });
|
|
66434
|
-
method(_void(), DeviceExportStatusSchema), method(_void(), array(DeviceKindSchema)), method(_void(), array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
|
|
66593
|
+
method(_void(), DeviceExportStatusSchema), method(_void(), array(DeviceKindSchema)), method(_void(), array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
|
|
66594
|
+
kind: "mutation",
|
|
66595
|
+
auth: "admin"
|
|
66596
|
+
}), method(UnexposeInputSchema, _void(), {
|
|
66597
|
+
kind: "mutation",
|
|
66598
|
+
auth: "admin"
|
|
66599
|
+
});
|
|
66435
66600
|
/** Wire/storage schema for one icon id. */
|
|
66436
66601
|
var LocationIconIdSchema = _enum([
|
|
66437
66602
|
"map-pin",
|
|
@@ -67016,7 +67181,10 @@ method(object({
|
|
|
67016
67181
|
kind: "mutation",
|
|
67017
67182
|
auth: "admin",
|
|
67018
67183
|
timeoutMs: 12 * 6e4
|
|
67019
|
-
}), method(DeviceRegisterPayloadSchema, _void(), { kind: "mutation" }), method(DeviceRemovePayloadSchema, _void(), { kind: "mutation" }), method(DevicePersistConfigPayloadSchema, _void(), {
|
|
67184
|
+
}), method(DeviceRegisterPayloadSchema, _void(), { kind: "mutation" }), method(DeviceRemovePayloadSchema, _void(), { kind: "mutation" }), method(DevicePersistConfigPayloadSchema, _void(), {
|
|
67185
|
+
kind: "mutation",
|
|
67186
|
+
auth: "admin"
|
|
67187
|
+
}), method(object({ deviceId: number() }), record(string(), unknown()), { auth: "admin" }), method(object({ deviceId: number() }), record(string(), unknown()), { auth: "admin" }), method(object({ deviceId: number() }), DeviceMetaSchema.nullable()), method(object({
|
|
67020
67188
|
deviceId: number(),
|
|
67021
67189
|
name: string()
|
|
67022
67190
|
}), _void(), {
|
|
@@ -67221,7 +67389,7 @@ method(object({
|
|
|
67221
67389
|
}), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
|
|
67222
67390
|
kind: "mutation",
|
|
67223
67391
|
auth: "admin"
|
|
67224
|
-
}), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({
|
|
67392
|
+
}), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({ capNames: array(string().min(1)).min(1).max(16) }), array(number())), method(object({
|
|
67225
67393
|
deviceId: number(),
|
|
67226
67394
|
capName: string(),
|
|
67227
67395
|
wrapperAddonId: string(),
|
|
@@ -67576,18 +67744,30 @@ var LlmErrorCodeSchema = _enum([
|
|
|
67576
67744
|
"budget-exceeded",
|
|
67577
67745
|
"adapter-error"
|
|
67578
67746
|
]);
|
|
67579
|
-
var
|
|
67747
|
+
var LlmGenerateOkSchema = object({
|
|
67580
67748
|
ok: literal(true),
|
|
67581
67749
|
text: string(),
|
|
67582
67750
|
model: string(),
|
|
67583
67751
|
usage: LlmUsageSchema,
|
|
67584
67752
|
truncated: boolean(),
|
|
67585
67753
|
latencyMs: number()
|
|
67586
|
-
})
|
|
67754
|
+
});
|
|
67755
|
+
/**
|
|
67756
|
+
* WHO refused, on a `refusal`. `guardrail` = a safety filter in front of the
|
|
67757
|
+
* model rejected the request before it answered (Apple Foundation Models
|
|
67758
|
+
* refuses surveillance-framed prompts in 0.1 s); `model` = the model itself
|
|
67759
|
+
* declined. They are different faults: a guardrail refusal is a property of
|
|
67760
|
+
* the BACKEND, so the provider falls back to the next profile; a model refusal
|
|
67761
|
+
* is an answer. Absent on every other code, and on a refusal a wire cannot
|
|
67762
|
+
* attribute.
|
|
67763
|
+
*/
|
|
67764
|
+
var LlmRefusalSourceSchema = _enum(["guardrail", "model"]);
|
|
67765
|
+
var LlmGenerateResultSchema = discriminatedUnion("ok", [LlmGenerateOkSchema, object({
|
|
67587
67766
|
ok: literal(false),
|
|
67588
67767
|
code: LlmErrorCodeSchema,
|
|
67589
67768
|
message: string(),
|
|
67590
|
-
retryAfterMs: number().optional()
|
|
67769
|
+
retryAfterMs: number().optional(),
|
|
67770
|
+
refusalSource: LlmRefusalSourceSchema.optional()
|
|
67591
67771
|
})]);
|
|
67592
67772
|
/**
|
|
67593
67773
|
* `Uint8Array` is the sanctioned binary convention — superjson + the UDS
|
|
@@ -67817,12 +67997,78 @@ var LlmRuntimeDiskUsageSchema = object({
|
|
|
67817
67997
|
modelsBytes: number(),
|
|
67818
67998
|
freeBytes: number().optional()
|
|
67819
67999
|
});
|
|
67820
|
-
|
|
68000
|
+
var LlmRuntimeCompleteInputSchema = LlmGenerateBaseInputSchema.extend({
|
|
67821
68001
|
images: array(LlmImageSchema).optional(),
|
|
67822
68002
|
runtime: ManagedRuntimeConfigSchema,
|
|
67823
68003
|
/** The managed profile's timeout, threaded by the hub provider. */
|
|
67824
68004
|
timeoutMs: number().int().positive().optional()
|
|
67825
|
-
})
|
|
68005
|
+
});
|
|
68006
|
+
/**
|
|
68007
|
+
* Why Apple Foundation Models cannot run on a node that has it in principle.
|
|
68008
|
+
* The first three are Apple's own `UnavailableReason`s, verbatim; the rest are
|
|
68009
|
+
* ours: the OS predates the framework (macOS < 26), the helper binary is not
|
|
68010
|
+
* in the installed addon, or it crashed past the respawn bound.
|
|
68011
|
+
*/
|
|
68012
|
+
var AppleFmUnavailableReasonSchema = _enum([
|
|
68013
|
+
"deviceNotEligible",
|
|
68014
|
+
"appleIntelligenceNotEnabled",
|
|
68015
|
+
"modelNotReady",
|
|
68016
|
+
"osTooOld",
|
|
68017
|
+
"helperMissing",
|
|
68018
|
+
"helperCrashed"
|
|
68019
|
+
]);
|
|
68020
|
+
/**
|
|
68021
|
+
* Apple Foundation Models on ONE node — four states, never a boolean (D315).
|
|
68022
|
+
*
|
|
68023
|
+
* - `available` — the helper probed it; what it CAN do is reported
|
|
68024
|
+
* here (`imageInput`, `contextSize`, languages),
|
|
68025
|
+
* never typed by an operator.
|
|
68026
|
+
* - `unavailable` — a known absence, with the reason to act on.
|
|
68027
|
+
* - `unsupported-platform` — not a Mac; there is nothing to enable.
|
|
68028
|
+
* - `unknown` — the probe did not answer (not yet run, timed
|
|
68029
|
+
* out, helper crashed mid-probe). A failed
|
|
68030
|
+
* measurement is NEVER `unavailable` (D393).
|
|
68031
|
+
*/
|
|
68032
|
+
var AppleFmAvailabilitySchema = discriminatedUnion("state", [
|
|
68033
|
+
object({
|
|
68034
|
+
state: literal("available"),
|
|
68035
|
+
osVersion: string(),
|
|
68036
|
+
/** Tokens, as the framework reports it. Read at runtime, never assumed. */
|
|
68037
|
+
contextSize: number().int().positive(),
|
|
68038
|
+
/** BCP-47-ish tags (`it-IT`) of the languages the model supports. */
|
|
68039
|
+
languages: array(string()),
|
|
68040
|
+
/** Whether prompts may carry images. `false` on a macOS 26 build. */
|
|
68041
|
+
imageInput: boolean()
|
|
68042
|
+
}),
|
|
68043
|
+
object({
|
|
68044
|
+
state: literal("unavailable"),
|
|
68045
|
+
reason: AppleFmUnavailableReasonSchema,
|
|
68046
|
+
osVersion: string().nullable(),
|
|
68047
|
+
detail: string()
|
|
68048
|
+
}),
|
|
68049
|
+
object({
|
|
68050
|
+
state: literal("unsupported-platform"),
|
|
68051
|
+
platform: string()
|
|
68052
|
+
}),
|
|
68053
|
+
object({
|
|
68054
|
+
state: literal("unknown"),
|
|
68055
|
+
error: string()
|
|
68056
|
+
})
|
|
68057
|
+
]);
|
|
68058
|
+
/**
|
|
68059
|
+
* A generation on the node's Apple Foundation Models helper. A separate verb
|
|
68060
|
+
* from `complete`, whose `runtime` is a llama.cpp config: overloading it would
|
|
68061
|
+
* drag a GGUF model reference into a backend that has none. `images` is part
|
|
68062
|
+
* of the contract so vision needs no cap change later; a node whose model has
|
|
68063
|
+
* no image input REFUSES them (`unavailable`), never drops them.
|
|
68064
|
+
*/
|
|
68065
|
+
var LlmRuntimeAppleCompleteInputSchema = LlmGenerateBaseInputSchema.extend({
|
|
68066
|
+
images: array(LlmImageSchema).optional(),
|
|
68067
|
+
/** The profile's timeout, threaded by the hub provider. */
|
|
68068
|
+
timeoutMs: number().int().positive().optional()
|
|
68069
|
+
});
|
|
68070
|
+
var LlmRuntimeCancelInputSchema = object({ requestId: string() });
|
|
68071
|
+
method(LlmRuntimeCompleteInputSchema, LlmGenerateResultSchema, {
|
|
67826
68072
|
kind: "mutation",
|
|
67827
68073
|
auth: "admin",
|
|
67828
68074
|
timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
|
|
@@ -67840,7 +68086,14 @@ method(LlmGenerateBaseInputSchema.extend({
|
|
|
67840
68086
|
}), method(object({ file: string() }), _void(), {
|
|
67841
68087
|
kind: "mutation",
|
|
67842
68088
|
auth: "admin"
|
|
67843
|
-
}), method(object({}), array(LlmNodeModelSchema), { auth: "admin" }), method(object({}), LlmRuntimeDiskUsageSchema, { auth: "admin" })
|
|
68089
|
+
}), method(object({}), array(LlmNodeModelSchema), { auth: "admin" }), method(object({}), LlmRuntimeDiskUsageSchema, { auth: "admin" }), method(LlmRuntimeCancelInputSchema, _void(), {
|
|
68090
|
+
kind: "mutation",
|
|
68091
|
+
auth: "admin"
|
|
68092
|
+
}), method(object({}), AppleFmAvailabilitySchema, { auth: "admin" }), method(LlmRuntimeAppleCompleteInputSchema, LlmGenerateResultSchema, {
|
|
68093
|
+
kind: "mutation",
|
|
68094
|
+
auth: "admin",
|
|
68095
|
+
timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
|
|
68096
|
+
});
|
|
67844
68097
|
/**
|
|
67845
68098
|
* `llm` — consumer-facing LLM surface (spec §1-§3). Collection-mode: array
|
|
67846
68099
|
* methods concat-fan across providers; single-row methods route to ONE
|
|
@@ -67857,7 +68110,8 @@ var LlmProfileKindSchema = _enum([
|
|
|
67857
68110
|
"openai",
|
|
67858
68111
|
"anthropic",
|
|
67859
68112
|
"google",
|
|
67860
|
-
"managed-local"
|
|
68113
|
+
"managed-local",
|
|
68114
|
+
"apple-foundation"
|
|
67861
68115
|
]);
|
|
67862
68116
|
var LlmProfileSchema = object({
|
|
67863
68117
|
id: string(),
|
|
@@ -67915,7 +68169,14 @@ var LlmProfileSchema = object({
|
|
|
67915
68169
|
toolsEnabled: boolean().default(false),
|
|
67916
68170
|
extraHeaders: record(string(), string()).optional(),
|
|
67917
68171
|
/** kind === 'managed-local' only (spec §4). */
|
|
67918
|
-
runtime: ManagedRuntimeConfigSchema.optional()
|
|
68172
|
+
runtime: ManagedRuntimeConfigSchema.optional(),
|
|
68173
|
+
/**
|
|
68174
|
+
* kind === 'apple-foundation' only: WHERE it runs — the only thing there is
|
|
68175
|
+
* to configure (no key, no URL, no download; `model` is `apple-system`).
|
|
68176
|
+
* `supportsVision` on such a row is stamped by the provider from the node's
|
|
68177
|
+
* reported `imageInput`, never taken from the operator (D62).
|
|
68178
|
+
*/
|
|
68179
|
+
appleFoundation: object({ nodeId: string() }).optional()
|
|
67919
68180
|
});
|
|
67920
68181
|
/** ConfigUISchema tree passed through untyped on the wire (the
|
|
67921
68182
|
* notification-output `ConfigSchemaPassthrough` precedent at
|
|
@@ -67997,7 +68258,9 @@ var LlmRuntimeNodeSchema = object({
|
|
|
67997
68258
|
reachable: boolean(),
|
|
67998
68259
|
status: LlmRuntimeStatusSchema.optional(),
|
|
67999
68260
|
disk: LlmRuntimeDiskUsageSchema.optional(),
|
|
68000
|
-
error: string().optional()
|
|
68261
|
+
error: string().optional(),
|
|
68262
|
+
/** Absent = the node did not answer the question (render as unknown). */
|
|
68263
|
+
apple: AppleFmAvailabilitySchema.optional()
|
|
68001
68264
|
});
|
|
68002
68265
|
var GenerateVisionInputSchema = LlmGenerateBaseInputSchema.extend({ images: array(LlmImageSchema).min(1) });
|
|
68003
68266
|
var ProfileRefInputSchema = object({
|
|
@@ -68768,7 +69031,22 @@ var StatusSchema = object({
|
|
|
68768
69031
|
brokerCount: number(),
|
|
68769
69032
|
embeddedRunning: boolean()
|
|
68770
69033
|
});
|
|
68771
|
-
method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema
|
|
69034
|
+
method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema, { auth: "admin" }), method(AddBrokerInputSchema, AddBrokerResultSchema, {
|
|
69035
|
+
kind: "mutation",
|
|
69036
|
+
auth: "admin"
|
|
69037
|
+
}), method(IdInputSchema, _void(), {
|
|
69038
|
+
kind: "mutation",
|
|
69039
|
+
auth: "admin"
|
|
69040
|
+
}), method(IdInputSchema, TestResultSchema$1, {
|
|
69041
|
+
kind: "mutation",
|
|
69042
|
+
auth: "admin"
|
|
69043
|
+
}), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, {
|
|
69044
|
+
kind: "mutation",
|
|
69045
|
+
auth: "admin"
|
|
69046
|
+
}), method(IdInputSchema, _void(), {
|
|
69047
|
+
kind: "mutation",
|
|
69048
|
+
auth: "admin"
|
|
69049
|
+
}), method(_void(), StatusSchema);
|
|
68772
69050
|
var NetworkEndpointSchema = object({
|
|
68773
69051
|
url: string(),
|
|
68774
69052
|
hostname: string(),
|
|
@@ -69063,6 +69341,18 @@ var TargetKindSchema = object({
|
|
|
69063
69341
|
*/
|
|
69064
69342
|
iconMediaType: string().optional(),
|
|
69065
69343
|
configSchema: ConfigSchemaPassthrough,
|
|
69344
|
+
/**
|
|
69345
|
+
* The config keys that decide WHERE this kind's secrets are sent (server,
|
|
69346
|
+
* base URL, instance, chat, recipient). A save that changes any of them
|
|
69347
|
+
* while leaving a secret redacted is refused by the provider (D634), so an
|
|
69348
|
+
* editor uses this to ask for the secret on exactly those edits instead of
|
|
69349
|
+
* on every edit.
|
|
69350
|
+
*
|
|
69351
|
+
* Optional: absent from a provider that holds no secret (the Home Assistant
|
|
69352
|
+
* kinds) and from a provider older than this field — an editor that finds
|
|
69353
|
+
* it absent must assume ANY edit may need the secret.
|
|
69354
|
+
*/
|
|
69355
|
+
destinationKeys: array(string()).optional(),
|
|
69066
69356
|
supportsDiscovery: boolean(),
|
|
69067
69357
|
caps: TargetKindCapsSchema
|
|
69068
69358
|
});
|
|
@@ -69101,19 +69391,34 @@ var SendResultSchema = object({
|
|
|
69101
69391
|
});
|
|
69102
69392
|
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
69103
69393
|
var TestResultSchema = SendResultSchema;
|
|
69104
|
-
method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema)), method(object({
|
|
69394
|
+
method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema), { auth: "admin" }), method(object({
|
|
69105
69395
|
kind: string(),
|
|
69106
69396
|
config: record(string(), unknown()).optional()
|
|
69107
|
-
}), array(DiscoveredTargetSchema)), method(object({
|
|
69397
|
+
}), array(DiscoveredTargetSchema), { auth: "admin" }), method(object({
|
|
69108
69398
|
targetId: string(),
|
|
69109
69399
|
notification: NotificationSchema
|
|
69110
|
-
}), SendResultSchema, {
|
|
69400
|
+
}), SendResultSchema, {
|
|
69401
|
+
kind: "mutation",
|
|
69402
|
+
auth: "admin"
|
|
69403
|
+
}), method(object({
|
|
69111
69404
|
targetId: string(),
|
|
69112
69405
|
sample: NotificationSchema.optional()
|
|
69113
|
-
}), TestResultSchema, {
|
|
69406
|
+
}), TestResultSchema, {
|
|
69407
|
+
kind: "mutation",
|
|
69408
|
+
auth: "admin"
|
|
69409
|
+
}), method(object({ target: TargetSchema }), TargetSchema, {
|
|
69410
|
+
kind: "mutation",
|
|
69411
|
+
auth: "admin"
|
|
69412
|
+
}), method(object({ targetId: string() }), _void(), {
|
|
69413
|
+
kind: "mutation",
|
|
69414
|
+
auth: "admin"
|
|
69415
|
+
}), method(object({
|
|
69114
69416
|
targetId: string(),
|
|
69115
69417
|
enabled: boolean()
|
|
69116
|
-
}), _void(), {
|
|
69418
|
+
}), _void(), {
|
|
69419
|
+
kind: "mutation",
|
|
69420
|
+
auth: "admin"
|
|
69421
|
+
});
|
|
69117
69422
|
new Set([
|
|
69118
69423
|
{
|
|
69119
69424
|
id: "person",
|
|
@@ -69634,6 +69939,7 @@ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
|
|
|
69634
69939
|
sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
|
|
69635
69940
|
sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
|
|
69636
69941
|
sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
|
|
69942
|
+
sub("cover", "control", "control", "#0ea5e9", "door", "Cover");
|
|
69637
69943
|
sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
|
|
69638
69944
|
sub("siren", "control", "control", "#dc2626", "siren", "Siren");
|
|
69639
69945
|
sub("button", "control", "control", "#10b981", "button", "Button");
|
|
@@ -69937,6 +70243,9 @@ var NcSystemEventKindSchema = _enum([
|
|
|
69937
70243
|
"device-battery-normal",
|
|
69938
70244
|
"device-consumable-low",
|
|
69939
70245
|
"device-consumable-normal",
|
|
70246
|
+
"device-tank-low",
|
|
70247
|
+
"device-tank-full",
|
|
70248
|
+
"device-tank-normal",
|
|
69940
70249
|
"stream-online",
|
|
69941
70250
|
"stream-offline",
|
|
69942
70251
|
"node-online",
|
|
@@ -69952,6 +70261,10 @@ var NcSystemEventKindSchema = _enum([
|
|
|
69952
70261
|
"alarm-disarmed",
|
|
69953
70262
|
"alarm-arming",
|
|
69954
70263
|
"alarm-arm-refused",
|
|
70264
|
+
"alarm-pending",
|
|
70265
|
+
"alarm-rearmed",
|
|
70266
|
+
"alarm-sensor-bypass",
|
|
70267
|
+
"alarm-check-failed",
|
|
69955
70268
|
"addon-updated",
|
|
69956
70269
|
"server-updated",
|
|
69957
70270
|
"export-completed",
|
|
@@ -69989,8 +70302,36 @@ var NcSystemEventConditionSchema = object({
|
|
|
69989
70302
|
* does not carry) matches no `deviceTypes` list.
|
|
69990
70303
|
*/
|
|
69991
70304
|
deviceTypes: array(string().min(1)).min(1).optional(),
|
|
70305
|
+
/**
|
|
70306
|
+
* Devices this rule NEVER speaks about, whatever else matched. Scoped to the
|
|
70307
|
+
* same kind family as `deviceIds` (`systemEventFilterApplies(kind,
|
|
70308
|
+
* 'deviceIds')`) and applied AFTER it: include first, then exclude. A
|
|
70309
|
+
* consumable subject (`consumable:<id>:<key>`) is excluded by its device id.
|
|
70310
|
+
*
|
|
70311
|
+
* It never fails closed: a subject that carries no device id is not in the
|
|
70312
|
+
* list, so it is not excluded. "Every battery except the one thermometer that
|
|
70313
|
+
* reads 0 % forever" is the question this answers — expressed as an include
|
|
70314
|
+
* list it would have to name every other battery device, and every device
|
|
70315
|
+
* adopted later would be silently left out (D636).
|
|
70316
|
+
*/
|
|
70317
|
+
excludeDeviceIds: array(number().int()).min(1).optional(),
|
|
69992
70318
|
nodeIds: array(string().min(1)).min(1).optional(),
|
|
69993
|
-
packageNames: array(string().min(1)).min(1).optional()
|
|
70319
|
+
packageNames: array(string().min(1)).min(1).optional(),
|
|
70320
|
+
/**
|
|
70321
|
+
* At or below this remaining-life %, a consumable is LOW for THIS rule
|
|
70322
|
+
* (D642). Absent = {@link NC_CONSUMABLE_LOW_PERCENT_DEFAULT}; at most
|
|
70323
|
+
* {@link NC_CONSUMABLE_LOW_PERCENT_MAX}, so every episode can recover. Scoped to the
|
|
70324
|
+
* consumable kinds (`systemEventFilterApplies`) and to the device-events
|
|
70325
|
+
* family; ignored on every other kind.
|
|
70326
|
+
*
|
|
70327
|
+
* It is a property of the RULE because the operator decided so: a rule at
|
|
70328
|
+
* 25 must be told about a filter at 20 %, a rule at 10 must not. The hub
|
|
70329
|
+
* keeps one episode per (threshold, device, item) — its own hysteresis, its
|
|
70330
|
+
* own reminder — and a rule hears only the episode judged at ITS threshold,
|
|
70331
|
+
* exactly as an occupancy rule hears only the edge of its own count. A
|
|
70332
|
+
* provider's own `replace` verdict is low at every threshold.
|
|
70333
|
+
*/
|
|
70334
|
+
consumableLowPercent: number().int().min(1).max(95).optional()
|
|
69994
70335
|
});
|
|
69995
70336
|
/** Hard ceiling on a window (24h). A snooze that could not expire would be an
|
|
69996
70337
|
* outage the operator asked for once and forgot. */
|
|
@@ -70133,6 +70474,16 @@ var NcCrossingSchema = _enum([
|
|
|
70133
70474
|
"exit",
|
|
70134
70475
|
"any"
|
|
70135
70476
|
]);
|
|
70477
|
+
/**
|
|
70478
|
+
* Which edge of a BOOLEAN sensor fires a device-event rule (D640).
|
|
70479
|
+
* - `raised` — the sensor went high: water detected, smoke, open, tampered.
|
|
70480
|
+
* - `cleared` — it went back: dry, clear, closed.
|
|
70481
|
+
* - absent — both edges, which is what every rule authored before this did.
|
|
70482
|
+
*
|
|
70483
|
+
* There is no third member for "both": absence already says it, and a value
|
|
70484
|
+
* that means the same as absence is a second way to write one rule.
|
|
70485
|
+
*/
|
|
70486
|
+
var NcSensorTransitionSchema = _enum(["raised", "cleared"]);
|
|
70136
70487
|
/** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
|
|
70137
70488
|
var NcZoneConditionSchema = object({
|
|
70138
70489
|
ids: array(string().min(1)).min(1),
|
|
@@ -70322,6 +70673,20 @@ var NcConditionsSchema = object({
|
|
|
70322
70673
|
scene: NcSceneConditionSchema.optional(),
|
|
70323
70674
|
/** Device scope — absent = all devices. */
|
|
70324
70675
|
devices: array(number()).optional(),
|
|
70676
|
+
/**
|
|
70677
|
+
* Device EXCLUSION (D641) — "every device except these". Judged on the SAME
|
|
70678
|
+
* subject ids {@link devices} is judged on (the camera, the sensor that
|
|
70679
|
+
* fired, a linked set's sensor and row cameras), so a sensor fanned out per
|
|
70680
|
+
* linked camera is excluded exactly where an include would have tested it.
|
|
70681
|
+
* `min(1)`: an empty exclusion excludes nothing and is written as absent.
|
|
70682
|
+
*
|
|
70683
|
+
* Not for `system-event` rules — those carry their own
|
|
70684
|
+
* `systemEvent.excludeDeviceIds` (D636), and the hub refuses this key there.
|
|
70685
|
+
* Editors present the two lists as ONE field (All | Only these | All except
|
|
70686
|
+
* these); the engine evaluates each independently, so a legacy rule
|
|
70687
|
+
* carrying both still means "these, minus those".
|
|
70688
|
+
*/
|
|
70689
|
+
devicesExclude: array(number()).min(1).optional(),
|
|
70325
70690
|
/** Detector class names (any overlap with the record's class set). */
|
|
70326
70691
|
classes: array(string().min(1)).optional(),
|
|
70327
70692
|
/** Veto classes — any overlap fails the rule. */
|
|
@@ -70455,6 +70820,16 @@ var NcConditionsSchema = object({
|
|
|
70455
70820
|
*/
|
|
70456
70821
|
sensorKinds: array(string().min(1)).optional(),
|
|
70457
70822
|
/**
|
|
70823
|
+
* DEVICE-EVENT only (D640). Fire on one edge of a boolean sensor —
|
|
70824
|
+
* {@link NcSensorTransitionSchema}. Which field of the slice means "raised"
|
|
70825
|
+
* is `@camstack/types` → `notification/sensor-raised.ts`. A reading the
|
|
70826
|
+
* engine cannot place (a kind with no raise, an unreadable slice) PASSES:
|
|
70827
|
+
* the condition fails open towards notifying, never towards silence. Editors
|
|
70828
|
+
* offer it only when every chosen {@link sensorKinds} member has a raise
|
|
70829
|
+
* (`sensorTransitionOffered`), and the hub refuses it on any other delivery.
|
|
70830
|
+
*/
|
|
70831
|
+
sensorTransition: NcSensorTransitionSchema.optional(),
|
|
70832
|
+
/**
|
|
70458
70833
|
* PACKAGE-EVENT only. Which package phase fires the rule — `delivered`
|
|
70459
70834
|
* (a parked parcel appeared), `picked-up` (it departed), or `both`. Fails
|
|
70460
70835
|
* when the subject's phase does not match (a subject always carries a phase
|
|
@@ -70547,6 +70922,19 @@ var NcMediaFrameSchema = _enum([
|
|
|
70547
70922
|
"full",
|
|
70548
70923
|
"boxed"
|
|
70549
70924
|
]);
|
|
70925
|
+
/**
|
|
70926
|
+
* How a SENSOR notification shows the cameras that link the sensor.
|
|
70927
|
+
*
|
|
70928
|
+
* `mosaic` — one image composed of every linked camera's photograph, taken at
|
|
70929
|
+
* the trigger. `each` — one image per camera where the target takes several
|
|
70930
|
+
* attachments; a target that takes one keeps the first camera's (the degrade
|
|
70931
|
+
* engine drops the rest by order). Either way ONE notification per event.
|
|
70932
|
+
* Absent = `mosaic`, applied by the Notification Center and deliberately NOT
|
|
70933
|
+
* a Zod default (see NcRulePatchSchema: a default materialises on every
|
|
70934
|
+
* partial patch). Meaningless for a rule whose subject is not a sensor, and a
|
|
70935
|
+
* sensor linked to one camera ships that camera's plain snapshot either way.
|
|
70936
|
+
*/
|
|
70937
|
+
var NcLinkedCameraModeSchema = _enum(["mosaic", "each"]);
|
|
70550
70938
|
var NcMediaPolicySchema = object({
|
|
70551
70939
|
attach: _enum([
|
|
70552
70940
|
"best",
|
|
@@ -70592,7 +70980,9 @@ var NcMediaPolicySchema = object({
|
|
|
70592
70980
|
* A profile that is not assigned falls back to the cheapest, and the render
|
|
70593
70981
|
* reports which one actually ran.
|
|
70594
70982
|
*/
|
|
70595
|
-
profile: CamProfileSchema.optional()
|
|
70983
|
+
profile: CamProfileSchema.optional(),
|
|
70984
|
+
/** See {@link NcLinkedCameraModeSchema}. */
|
|
70985
|
+
linkedCameras: NcLinkedCameraModeSchema.optional()
|
|
70596
70986
|
});
|
|
70597
70987
|
/**
|
|
70598
70988
|
* Cooldown GRANULARITY over the subject's class — how much a fired
|
|
@@ -70712,7 +71102,7 @@ var NcRuleInputSchema = object({
|
|
|
70712
71102
|
cooldownSec: 60,
|
|
70713
71103
|
scope: "rule-device"
|
|
70714
71104
|
}),
|
|
70715
|
-
/** `{{var}}`
|
|
71105
|
+
/** `{{var}}` title/body. The variables are declared in `NC_TEMPLATE_VARS` (@camstack/types) and depend on the rule kind — see `templateVarsFor`. */
|
|
70716
71106
|
template: object({
|
|
70717
71107
|
title: string().max(500).optional(),
|
|
70718
71108
|
body: string().max(2e3).optional()
|
|
@@ -70892,6 +71282,22 @@ var NcRulePatchSchema = NcRuleInputSchema.partial().extend({
|
|
|
70892
71282
|
throttle: NcThrottleSchema.optional(),
|
|
70893
71283
|
priority: number().int().min(1).max(5).optional()
|
|
70894
71284
|
});
|
|
71285
|
+
var NcRuleClearableKeySchema = _enum([
|
|
71286
|
+
"template",
|
|
71287
|
+
"schedule",
|
|
71288
|
+
"targetUsers",
|
|
71289
|
+
"snoozeOptions",
|
|
71290
|
+
"snoozeAllowGlobal",
|
|
71291
|
+
"waitForEnhancement",
|
|
71292
|
+
"actions",
|
|
71293
|
+
"confirm",
|
|
71294
|
+
"groupIdleSec"
|
|
71295
|
+
]);
|
|
71296
|
+
var NcRuleUpdateInputSchema = object({
|
|
71297
|
+
ruleId: string(),
|
|
71298
|
+
patch: NcRulePatchSchema,
|
|
71299
|
+
clear: array(NcRuleClearableKeySchema).optional()
|
|
71300
|
+
});
|
|
70895
71301
|
/** A persisted rule. */
|
|
70896
71302
|
var NcRuleSchema = NcRuleInputSchema.extend({
|
|
70897
71303
|
id: string(),
|
|
@@ -70923,6 +71329,105 @@ var NcTestResultSchema = object({
|
|
|
70923
71329
|
className: string().optional(),
|
|
70924
71330
|
label: string().optional()
|
|
70925
71331
|
});
|
|
71332
|
+
/** The rule kinds, as data. `NcRuleKind` in `notification/rule-kinds.ts` is the
|
|
71333
|
+
* same union; the template-vars spec pins the two together. `mixed` is DERIVED
|
|
71334
|
+
* (a stored system rule whose kinds span families, D637) and never authored. */
|
|
71335
|
+
var NcRuleKindSchema = _enum([
|
|
71336
|
+
"detection",
|
|
71337
|
+
"sensor",
|
|
71338
|
+
"occupancy",
|
|
71339
|
+
"sound",
|
|
71340
|
+
"device-events",
|
|
71341
|
+
"system",
|
|
71342
|
+
"alarm",
|
|
71343
|
+
"mixed"
|
|
71344
|
+
]);
|
|
71345
|
+
/**
|
|
71346
|
+
* The kinds a SERVED template-variable descriptor may name: the five that
|
|
71347
|
+
* existed before D637, frozen. `getTemplateCatalog` ships `kinds` to every
|
|
71348
|
+
* installed viewer, and a viewer at `@camstack/types` 1.2.25x parses them with a
|
|
71349
|
+
* strict five-member enum — one `device-events` in one descriptor fails the
|
|
71350
|
+
* whole catalog parse. So the WIRE says `system`, and the MATCH widens it:
|
|
71351
|
+
* `templateVarKindApplies` reads a `system` descriptor as applying to every
|
|
71352
|
+
* system rule kind (`device-events`, `system`, `alarm`, `mixed`).
|
|
71353
|
+
*/
|
|
71354
|
+
var NcTemplateVarKindSchema = _enum([
|
|
71355
|
+
"detection",
|
|
71356
|
+
"sensor",
|
|
71357
|
+
"occupancy",
|
|
71358
|
+
"sound",
|
|
71359
|
+
"system"
|
|
71360
|
+
]);
|
|
71361
|
+
/** Which producer renders a template: an ordinary rule, a timelapse, a digest. */
|
|
71362
|
+
var NcTemplateFamilySchema = _enum([
|
|
71363
|
+
"rule",
|
|
71364
|
+
"timelapse",
|
|
71365
|
+
"summary"
|
|
71366
|
+
]);
|
|
71367
|
+
/** Which text field of that producer. `previewText` is the timelapse frame
|
|
71368
|
+
* caption, `captionText` the digest mosaic caption — both see fewer vars. */
|
|
71369
|
+
var NcTemplateFieldSchema = _enum([
|
|
71370
|
+
"title",
|
|
71371
|
+
"body",
|
|
71372
|
+
"previewText",
|
|
71373
|
+
"captionText"
|
|
71374
|
+
]);
|
|
71375
|
+
var NcTemplateVarGroupSchema = _enum([
|
|
71376
|
+
"subject",
|
|
71377
|
+
"place",
|
|
71378
|
+
"time",
|
|
71379
|
+
"rule",
|
|
71380
|
+
"occupancy",
|
|
71381
|
+
"sound",
|
|
71382
|
+
"sensor",
|
|
71383
|
+
"system",
|
|
71384
|
+
"digest",
|
|
71385
|
+
"ai"
|
|
71386
|
+
]);
|
|
71387
|
+
/**
|
|
71388
|
+
* ONE `{{var}}` a notification template may name.
|
|
71389
|
+
*
|
|
71390
|
+
* `families` / `fields` / `kinds` / `deliveries` / `systemEventKinds` say WHERE
|
|
71391
|
+
* it has a value; absent = no restriction on that axis. `systemEventKinds` is
|
|
71392
|
+
* read only for a system-event rule (any family, or `mixed`). `dynamic` declares a FAMILY of names
|
|
71393
|
+
* (`count_<class>`): the descriptor's `name` is the example member.
|
|
71394
|
+
*/
|
|
71395
|
+
var NcTemplateVarDescriptorSchema = object({
|
|
71396
|
+
name: string().regex(/^\w+$/),
|
|
71397
|
+
label: string(),
|
|
71398
|
+
description: string().optional(),
|
|
71399
|
+
example: string(),
|
|
71400
|
+
group: NcTemplateVarGroupSchema,
|
|
71401
|
+
families: array(NcTemplateFamilySchema).min(1),
|
|
71402
|
+
fields: array(NcTemplateFieldSchema).optional(),
|
|
71403
|
+
kinds: array(NcTemplateVarKindSchema).optional(),
|
|
71404
|
+
deliveries: array(NcDeliverySchema).optional(),
|
|
71405
|
+
systemEventKinds: array(NcSystemEventKindSchema).optional(),
|
|
71406
|
+
dynamic: object({
|
|
71407
|
+
prefix: literal("count_"),
|
|
71408
|
+
from: literal("classes")
|
|
71409
|
+
}).optional()
|
|
71410
|
+
});
|
|
71411
|
+
/** Input to `previewTemplate` — the editor's own context, plus the draft text. */
|
|
71412
|
+
var NcTemplatePreviewInputSchema = object({
|
|
71413
|
+
context: object({
|
|
71414
|
+
family: NcTemplateFamilySchema,
|
|
71415
|
+
field: NcTemplateFieldSchema,
|
|
71416
|
+
kind: NcRuleKindSchema.optional(),
|
|
71417
|
+
delivery: NcDeliverySchema.optional(),
|
|
71418
|
+
systemEventKinds: array(NcSystemEventKindSchema).optional()
|
|
71419
|
+
}),
|
|
71420
|
+
template: object({
|
|
71421
|
+
title: string().max(500).optional(),
|
|
71422
|
+
body: string().max(2e3).optional()
|
|
71423
|
+
})
|
|
71424
|
+
});
|
|
71425
|
+
var NcTemplatePreviewSchema = object({
|
|
71426
|
+
title: string().nullable(),
|
|
71427
|
+
body: string().nullable(),
|
|
71428
|
+
/** Names the template uses that this context never fills — they render empty. */
|
|
71429
|
+
unknown: array(string())
|
|
71430
|
+
});
|
|
70926
71431
|
var NcConditionDescriptorSchema = object({
|
|
70927
71432
|
/** Field id inside `NcConditions` (or `'schedule'` for the rule-level group). */
|
|
70928
71433
|
id: string(),
|
|
@@ -70943,6 +71448,7 @@ var NcConditionDescriptorSchema = object({
|
|
|
70943
71448
|
/** Editor widget the UI renders — never hardcode per-condition forms. */
|
|
70944
71449
|
valueType: _enum([
|
|
70945
71450
|
"deviceIdList",
|
|
71451
|
+
"deviceIdExcludeList",
|
|
70946
71452
|
"stringList",
|
|
70947
71453
|
"number01",
|
|
70948
71454
|
"number",
|
|
@@ -70953,6 +71459,7 @@ var NcConditionDescriptorSchema = object({
|
|
|
70953
71459
|
"plateMatcher",
|
|
70954
71460
|
"packagePhase",
|
|
70955
71461
|
"crossingSelect",
|
|
71462
|
+
"sensorTransition",
|
|
70956
71463
|
"polygonDraw",
|
|
70957
71464
|
"occupancy",
|
|
70958
71465
|
"audio",
|
|
@@ -71103,6 +71610,28 @@ var NcHistoryFilterSchema = object({
|
|
|
71103
71610
|
limit: number().int().min(1).max(500).default(100)
|
|
71104
71611
|
});
|
|
71105
71612
|
/**
|
|
71613
|
+
* Every rule's last run, in one answer.
|
|
71614
|
+
*
|
|
71615
|
+
* A rule ABSENT from `runs` produced no delivery inside `horizonMs` — how far
|
|
71616
|
+
* back this answer looked, in ms before the hub's now. It is the outbox prune
|
|
71617
|
+
* window (7 days by default, operator-configurable), NARROWED when the read hit
|
|
71618
|
+
* its row bound: the answer never claims a window it did not read. `null` means
|
|
71619
|
+
* the operator keeps the outbox forever AND the read reached its end, so absent
|
|
71620
|
+
* means "never on record". A UI derives its "not in the last N days" from this
|
|
71621
|
+
* value, never from a constant — and only when the call ANSWERED (D315).
|
|
71622
|
+
*/
|
|
71623
|
+
var NcRuleLastRunsSchema = object({
|
|
71624
|
+
runs: array(object({
|
|
71625
|
+
ruleId: string(),
|
|
71626
|
+
/** Fire time of the newest row (`createdAt`, epoch ms). */
|
|
71627
|
+
lastAt: number(),
|
|
71628
|
+
status: NcHistoryStatusSchema,
|
|
71629
|
+
/** The camera/device of that row (a digest row files under its first camera). */
|
|
71630
|
+
deviceId: number()
|
|
71631
|
+
})),
|
|
71632
|
+
horizonMs: number().nonnegative().nullable()
|
|
71633
|
+
});
|
|
71634
|
+
/**
|
|
71106
71635
|
* What a snooze covers. Broader scopes win when several overlap, so one window
|
|
71107
71636
|
* leaves ONE digest rather than a rule snooze and a whole-feed snooze both
|
|
71108
71637
|
* summarising the same silence.
|
|
@@ -71188,8 +71717,17 @@ object({
|
|
|
71188
71717
|
lastAt: number()
|
|
71189
71718
|
});
|
|
71190
71719
|
/**
|
|
71191
|
-
* The
|
|
71192
|
-
*
|
|
71720
|
+
* The per-mode lists, at most one per mode. Two lists for one mode have no
|
|
71721
|
+
* meaning a reader could agree on (union? the last one?), so they are refused
|
|
71722
|
+
* at the boundary rather than interpreted.
|
|
71723
|
+
*/
|
|
71724
|
+
var NcAlarmNonBlockingListSchema = array(object({
|
|
71725
|
+
mode: AlarmArmModeSchema,
|
|
71726
|
+
deviceIds: array(number().int()).max(200)
|
|
71727
|
+
})).max(8).refine((lists) => new Set(lists.map((l) => l.mode)).size === lists.length, { message: "nonBlocking: at most one list per arm mode" });
|
|
71728
|
+
/**
|
|
71729
|
+
* The three durations the panel's state machine runs on, who hears about its
|
|
71730
|
+
* transitions, and which openings each mode tolerates.
|
|
71193
71731
|
*
|
|
71194
71732
|
* They live on the NOTIFICATION-RULES cap, not on `alarm-panel`, on purpose:
|
|
71195
71733
|
* `alarm-panel` is `deviceNative` and its other provider mirrors somebody
|
|
@@ -71208,21 +71746,39 @@ var NcAlarmSettingsSchema = object({
|
|
|
71208
71746
|
* existed, and therefore what an untouched install keeps doing.
|
|
71209
71747
|
*/
|
|
71210
71748
|
triggeredDurationSec: number().int().min(0).max(3600),
|
|
71211
|
-
/** Send a notification when a mode takes effect. */
|
|
71212
|
-
announceArm: boolean(),
|
|
71213
71749
|
/**
|
|
71214
|
-
*
|
|
71750
|
+
* Deprecated by D630 — every transition is announced. Parsed, never read;
|
|
71751
|
+
* remove after one release. Optional so an older admin's patch (which still
|
|
71752
|
+
* sends it) parses rather than failing the whole save.
|
|
71753
|
+
*/
|
|
71754
|
+
announceArm: boolean().optional(),
|
|
71755
|
+
/**
|
|
71756
|
+
* Target ids from `notification-output`: every alarm transition goes here.
|
|
71215
71757
|
*
|
|
71216
|
-
* Explicit rather than "everyone": an
|
|
71758
|
+
* Explicit rather than "everyone": an alarm announcement is a household
|
|
71217
71759
|
* message, and broadcasting it to every configured endpoint (including a
|
|
71218
71760
|
* webhook wired to something else) is not a default anybody would choose.
|
|
71219
|
-
* Empty
|
|
71220
|
-
*
|
|
71761
|
+
* Empty sends nothing, and the server logs that — silence must be
|
|
71762
|
+
* attributable.
|
|
71763
|
+
*/
|
|
71764
|
+
announceTargets: array(string().min(1)).max(16),
|
|
71765
|
+
/**
|
|
71766
|
+
* Per arm mode, the covered devices whose being OPEN does not refuse the
|
|
71767
|
+
* arm (a window left ajar for the cat under `home`, say). At most one entry
|
|
71768
|
+
* per mode is meaningful. Defaulted to `[]` so a blob stored before D630
|
|
71769
|
+
* parses as "every opening blocks" — the behaviour it was written under.
|
|
71221
71770
|
*/
|
|
71222
|
-
|
|
71771
|
+
nonBlocking: NcAlarmNonBlockingListSchema.default([])
|
|
71223
71772
|
});
|
|
71224
|
-
/**
|
|
71225
|
-
|
|
71773
|
+
/**
|
|
71774
|
+
* Every field optional — a tab edits one control at a time.
|
|
71775
|
+
*
|
|
71776
|
+
* `nonBlocking` is re-declared WITHOUT its default: `.partial()` does not
|
|
71777
|
+
* remove an inner `.default()`, so a patch that never named the field would
|
|
71778
|
+
* parse to `nonBlocking: []` and wipe every mode's list on an unrelated edit
|
|
71779
|
+
* (the same trap {@link NcRulePatchSchema} documents).
|
|
71780
|
+
*/
|
|
71781
|
+
var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({ nonBlocking: NcAlarmNonBlockingListSchema }).partial();
|
|
71226
71782
|
/**
|
|
71227
71783
|
* What one arm mode actually arms, DERIVED from the enabled rules gated on it.
|
|
71228
71784
|
* Never authored, never stored — see `alarm-mode-coverage.ts` for why a stored
|
|
@@ -71256,6 +71812,14 @@ var NcAlarmModeCoverageSchema = object({
|
|
|
71256
71812
|
/** Ids named by the covering rules. A SUBSET when `allDevices` is true. */
|
|
71257
71813
|
deviceIds: array(number().int()),
|
|
71258
71814
|
/**
|
|
71815
|
+
* D641: the ids NOT covered although `allDevices` is true — every unscoped
|
|
71816
|
+
* covering rule EXCLUDES them (`conditions.devicesExclude`) and no named rule
|
|
71817
|
+
* covers them. Always `[]` when `allDevices` is false. Excluded by the rules,
|
|
71818
|
+
* which is not the same as skipped by a switch: never in `skippedDevices`.
|
|
71819
|
+
* Defaulted so an answer computed before this field parses as "none".
|
|
71820
|
+
*/
|
|
71821
|
+
allDevicesExcept: array(number().int()).default([]),
|
|
71822
|
+
/**
|
|
71259
71823
|
* Devices this mode NAMES but cannot actually arm, each with the switch that
|
|
71260
71824
|
* excludes it.
|
|
71261
71825
|
*
|
|
@@ -71267,6 +71831,36 @@ var NcAlarmModeCoverageSchema = object({
|
|
|
71267
71831
|
*/
|
|
71268
71832
|
skippedDevices: array(NcAlarmSkippedDeviceSchema).default([])
|
|
71269
71833
|
});
|
|
71834
|
+
/** One covered device that reads open, and whether that refuses the arm. */
|
|
71835
|
+
var NcAlarmOpeningSchema = object({
|
|
71836
|
+
deviceId: number().int(),
|
|
71837
|
+
name: string(),
|
|
71838
|
+
/** The device's state word as read (`open`, `unlocked`, …). */
|
|
71839
|
+
state: string(),
|
|
71840
|
+
/** False when the mode's non-blocking list names this device. */
|
|
71841
|
+
blocking: boolean()
|
|
71842
|
+
});
|
|
71843
|
+
/** A device the panel is currently ignoring, and which side of it it is on. */
|
|
71844
|
+
var NcAlarmExclusionViewSchema = object({
|
|
71845
|
+
deviceId: number().int(),
|
|
71846
|
+
name: string(),
|
|
71847
|
+
phase: _enum(["open", "closed"])
|
|
71848
|
+
});
|
|
71849
|
+
/** One mode's openings: devices read open, plus those whose state is unknown. */
|
|
71850
|
+
var NcAlarmModeOpeningsSchema = object({
|
|
71851
|
+
mode: AlarmArmModeSchema,
|
|
71852
|
+
devices: array(NcAlarmOpeningSchema),
|
|
71853
|
+
/** Covered devices whose state could not be read (D49): neither open nor closed. */
|
|
71854
|
+
unknown: array(number().int())
|
|
71855
|
+
});
|
|
71856
|
+
var NcAlarmLiveSchema = object({
|
|
71857
|
+
state: AlarmStateSchema,
|
|
71858
|
+
/** The mode being armed into (exit delay) or held; null when disarmed. */
|
|
71859
|
+
targetMode: AlarmArmModeSchema.nullable(),
|
|
71860
|
+
availableModes: array(AlarmArmModeSchema),
|
|
71861
|
+
openings: array(NcAlarmModeOpeningsSchema),
|
|
71862
|
+
exclusions: array(NcAlarmExclusionViewSchema)
|
|
71863
|
+
});
|
|
71270
71864
|
var NcAlarmConfigSchema = object({
|
|
71271
71865
|
/**
|
|
71272
71866
|
* The panel's device id, or null when this install has no panel (the ensure
|
|
@@ -71276,7 +71870,13 @@ var NcAlarmConfigSchema = object({
|
|
|
71276
71870
|
*/
|
|
71277
71871
|
deviceId: number().int().nullable(),
|
|
71278
71872
|
settings: NcAlarmSettingsSchema,
|
|
71279
|
-
coverage: array(NcAlarmModeCoverageSchema)
|
|
71873
|
+
coverage: array(NcAlarmModeCoverageSchema),
|
|
71874
|
+
/**
|
|
71875
|
+
* The panel as it is NOW — state, what each mode would find open, what is
|
|
71876
|
+
* excluded. Optional: a panel-less hub answers without it, and a client
|
|
71877
|
+
* must not read its absence as "nothing is open".
|
|
71878
|
+
*/
|
|
71879
|
+
live: NcAlarmLiveSchema.optional()
|
|
71280
71880
|
});
|
|
71281
71881
|
/**
|
|
71282
71882
|
* ONE rule's demand on ONE camera's clip ring.
|
|
@@ -71324,10 +71924,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
|
|
|
71324
71924
|
kind: "mutation",
|
|
71325
71925
|
auth: "admin",
|
|
71326
71926
|
caller: "required"
|
|
71327
|
-
}), method(object({
|
|
71328
|
-
ruleId: string(),
|
|
71329
|
-
patch: NcRulePatchSchema
|
|
71330
|
-
}), object({ rule: NcRuleSchema }), {
|
|
71927
|
+
}), method(NcRuleUpdateInputSchema, object({ rule: NcRuleSchema }), {
|
|
71331
71928
|
kind: "mutation",
|
|
71332
71929
|
auth: "admin",
|
|
71333
71930
|
caller: "required"
|
|
@@ -71355,7 +71952,10 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
|
|
|
71355
71952
|
}), method(object({}), object({
|
|
71356
71953
|
catalog: array(NcConditionDescriptorSchema),
|
|
71357
71954
|
taxonomy: NcTaxonomySchema.optional()
|
|
71358
|
-
})), method(object({
|
|
71955
|
+
})), method(object({}), object({ vars: array(NcTemplateVarDescriptorSchema) })), method(NcTemplatePreviewInputSchema, NcTemplatePreviewSchema, {
|
|
71956
|
+
kind: "mutation",
|
|
71957
|
+
access: "view"
|
|
71958
|
+
}), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" }), method(object({}), NcRuleLastRunsSchema, { auth: "admin" }), method(object({ artifactId: string().min(1) }), object({ url: string().nullable() }), { auth: "admin" }), method(object({}), object({ snoozes: array(NcSnoozeSchema) }), { caller: "required" }), method(object({ snooze: NcSnoozeInputSchema }), object({ snooze: NcSnoozeSchema }), {
|
|
71359
71959
|
kind: "mutation",
|
|
71360
71960
|
caller: "required"
|
|
71361
71961
|
}), method(object({ snoozeId: string() }), object({ success: literal(true) }), {
|
|
@@ -75828,6 +76428,7 @@ var CAP_TO_KIND = {
|
|
|
75828
76428
|
"enum-sensor": "enum-sensor",
|
|
75829
76429
|
"event-emitter": "device-event",
|
|
75830
76430
|
"lock-control": "lock",
|
|
76431
|
+
cover: "cover",
|
|
75831
76432
|
switch: "switch",
|
|
75832
76433
|
button: "button",
|
|
75833
76434
|
doorbell: "doorbell"
|
|
@@ -75840,7 +76441,11 @@ function buildDescriptor(capName, kind) {
|
|
|
75840
76441
|
icon: legacyIcon(t.iconId)
|
|
75841
76442
|
};
|
|
75842
76443
|
}
|
|
75843
|
-
|
|
76444
|
+
/**
|
|
76445
|
+
* Sensor / control cap name → static event-kind descriptor. A linked device
|
|
76446
|
+
* contributes one entry per bound cap present in this map.
|
|
76447
|
+
*/
|
|
76448
|
+
var EVENT_KIND_BY_CAP = Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
|
|
75844
76449
|
/**
|
|
75845
76450
|
* server-management — per-NODE singleton capability for a node's ROOT
|
|
75846
76451
|
* package lifecycle (runtime-updatable node packages).
|
|
@@ -76388,41 +76993,65 @@ var ProviderListEntrySchema = discriminatedUnion("shouldSaveDiskSpace", [object(
|
|
|
76388
76993
|
method(object({
|
|
76389
76994
|
location: StorageLocationRefSchema,
|
|
76390
76995
|
relativePath: string()
|
|
76391
|
-
}), string()), method(object({
|
|
76996
|
+
}), string(), { auth: "admin" }), method(object({
|
|
76392
76997
|
location: StorageLocationRefSchema,
|
|
76393
76998
|
relativePath: string(),
|
|
76394
76999
|
data: _instanceof(Uint8Array)
|
|
76395
|
-
}), _void(), {
|
|
77000
|
+
}), _void(), {
|
|
77001
|
+
kind: "mutation",
|
|
77002
|
+
auth: "admin"
|
|
77003
|
+
}), method(object({
|
|
76396
77004
|
location: StorageLocationRefSchema,
|
|
76397
77005
|
relativePath: string()
|
|
76398
|
-
}), _instanceof(Uint8Array)), method(object({
|
|
77006
|
+
}), _instanceof(Uint8Array), { auth: "admin" }), method(object({
|
|
76399
77007
|
location: StorageLocationRefSchema,
|
|
76400
77008
|
relativePath: string()
|
|
76401
|
-
}), boolean()), method(object({
|
|
77009
|
+
}), boolean(), { auth: "admin" }), method(object({
|
|
76402
77010
|
location: StorageLocationRefSchema,
|
|
76403
77011
|
prefix: string().optional()
|
|
76404
|
-
}), array(string()).readonly()), method(object({
|
|
77012
|
+
}), array(string()).readonly(), { auth: "admin" }), method(object({
|
|
76405
77013
|
location: StorageLocationRefSchema,
|
|
76406
77014
|
relativePath: string()
|
|
76407
|
-
}), _void(), {
|
|
77015
|
+
}), _void(), {
|
|
77016
|
+
kind: "mutation",
|
|
77017
|
+
auth: "admin"
|
|
77018
|
+
}), method(object({ location: StorageLocationRefSchema }), number().nullable()), method(object({
|
|
76408
77019
|
location: StorageLocationRefSchema,
|
|
76409
77020
|
relativePath: string(),
|
|
76410
77021
|
sizeBytes: number().optional()
|
|
76411
|
-
}), object({ uploadId: string() }), {
|
|
77022
|
+
}), object({ uploadId: string() }), {
|
|
77023
|
+
kind: "mutation",
|
|
77024
|
+
auth: "admin"
|
|
77025
|
+
}), method(object({
|
|
76412
77026
|
uploadId: string(),
|
|
76413
77027
|
offset: number(),
|
|
76414
77028
|
data: _instanceof(Uint8Array)
|
|
76415
|
-
}), _void(), {
|
|
77029
|
+
}), _void(), {
|
|
77030
|
+
kind: "mutation",
|
|
77031
|
+
auth: "admin"
|
|
77032
|
+
}), method(object({ uploadId: string() }), _void(), {
|
|
77033
|
+
kind: "mutation",
|
|
77034
|
+
auth: "admin"
|
|
77035
|
+
}), method(object({ uploadId: string() }), _void(), {
|
|
77036
|
+
kind: "mutation",
|
|
77037
|
+
auth: "admin"
|
|
77038
|
+
}), method(object({
|
|
76416
77039
|
location: StorageLocationRefSchema,
|
|
76417
77040
|
relativePath: string()
|
|
76418
77041
|
}), object({
|
|
76419
77042
|
downloadId: string(),
|
|
76420
77043
|
sizeBytes: number()
|
|
76421
|
-
}), {
|
|
77044
|
+
}), {
|
|
77045
|
+
kind: "mutation",
|
|
77046
|
+
auth: "admin"
|
|
77047
|
+
}), method(object({
|
|
76422
77048
|
downloadId: string(),
|
|
76423
77049
|
offset: number(),
|
|
76424
77050
|
length: number()
|
|
76425
|
-
}), _instanceof(Uint8Array)
|
|
77051
|
+
}), _instanceof(Uint8Array), { auth: "admin" }), method(object({ downloadId: string() }), _void(), {
|
|
77052
|
+
kind: "mutation",
|
|
77053
|
+
auth: "admin"
|
|
77054
|
+
}), method(object({ type: StorageLocationTypeSchema.optional() }), array(StorageLocationSchema).readonly()), method(_void(), array(StorageLocationDeclarationSchema).readonly()), method(StorageLocationSchema.omit({
|
|
76426
77055
|
createdAt: true,
|
|
76427
77056
|
updatedAt: true
|
|
76428
77057
|
}), StorageLocationSchema, {
|
|
@@ -78027,7 +78656,67 @@ var WebrtcLadderDecisionSchema = object({
|
|
|
78027
78656
|
serverSending: boolean().nullable(),
|
|
78028
78657
|
/** Epoch ms. The overlay prints its age; a decision from 40 minutes ago
|
|
78029
78658
|
* must not read like one from this second. */
|
|
78030
|
-
at: number()
|
|
78659
|
+
at: number(),
|
|
78660
|
+
/** The broker's own cause when it is finer than `cause` can carry, e.g.
|
|
78661
|
+
* `trial-revert` (reported as `stall`/`loss` for older consumers). Optional:
|
|
78662
|
+
* an older server strips it instead of rejecting the answer. */
|
|
78663
|
+
causeDetail: string().optional()
|
|
78664
|
+
});
|
|
78665
|
+
var WebrtcClimbHoldSchema = object({
|
|
78666
|
+
/** `stall-building` — the client receives ≪ what is sent; `delivery` — the
|
|
78667
|
+
* current rung is not landing whole; `no-evidence` — no receiver feedback;
|
|
78668
|
+
* `warming` — healthy, accumulating the streak; `cooldown` — too soon after
|
|
78669
|
+
* a change; `capacity` — a MEASURED probe below the next rung's bar;
|
|
78670
|
+
* `try-unclean` — no capacity evidence, and the path is not clean enough to
|
|
78671
|
+
* try; `try-backoff` — a try reverted, waiting out its backoff; `trial` —
|
|
78672
|
+
* this rung IS a try, reverting if it does not land; `suspended` —
|
|
78673
|
+
* recorded playback on the track. */
|
|
78674
|
+
code: string(),
|
|
78675
|
+
/** The rung a climb would go to. */
|
|
78676
|
+
nextTier: CamProfileSchema,
|
|
78677
|
+
/** Bytes received ÷ bytes sent over the delivery window (≥ one GOP). */
|
|
78678
|
+
deliveryRatio: number().nullable(),
|
|
78679
|
+
deliveryWindowMs: number().nullable(),
|
|
78680
|
+
/** The ratio a climb requires. */
|
|
78681
|
+
climbDeliveryRatio: number(),
|
|
78682
|
+
/** The receiver's bandwidth estimate (REMB), kbps. Capped by what is sent. */
|
|
78683
|
+
capacityKbps: number().nullable(),
|
|
78684
|
+
/** The viewer's downlink probe for this session, kbps. */
|
|
78685
|
+
probeKbps: number().nullable(),
|
|
78686
|
+
/** The next rung's measured bitrate, kbps. */
|
|
78687
|
+
nextTierKbps: number().nullable(),
|
|
78688
|
+
capacityMargin: number(),
|
|
78689
|
+
/** The capacity a climb to `nextTier` needs, kbps (bitrate × margin, or the
|
|
78690
|
+
* client ladder threshold when the rung has measured nothing). */
|
|
78691
|
+
neededKbps: number().nullable(),
|
|
78692
|
+
/** For `try-unclean`: which clean-path condition failed (a `WebrtcTryBlocker`,
|
|
78693
|
+
* carried as a string for the same reason as `code`). */
|
|
78694
|
+
tryBlockedBy: string().nullable(),
|
|
78695
|
+
/** The delivery ratio and the loss ratio a try requires / must stay under.
|
|
78696
|
+
* Optional, like every field added to this schema after it first shipped:
|
|
78697
|
+
* the router validates output, so a REQUIRED new field makes a newer server
|
|
78698
|
+
* reject an older broker's whole answer (review m1). */
|
|
78699
|
+
tryDeliveryRatio: number().optional(),
|
|
78700
|
+
tryMaxLossRatio: number().optional(),
|
|
78701
|
+
/** For `try-backoff`: time until the next try, ms. */
|
|
78702
|
+
retryInMs: number().nullable(),
|
|
78703
|
+
/** The last try that reverted in this viewer session. */
|
|
78704
|
+
lastRevert: object({
|
|
78705
|
+
tier: CamProfileSchema,
|
|
78706
|
+
deliveryRatio: number().nullable(),
|
|
78707
|
+
stalled: boolean(),
|
|
78708
|
+
at: number()
|
|
78709
|
+
}).nullable(),
|
|
78710
|
+
/** For `trial`: how long this rung has been on trial, and for how long it must hold. */
|
|
78711
|
+
trialForMs: number().nullable(),
|
|
78712
|
+
trialMs: number().nullable(),
|
|
78713
|
+
healthyForMs: number().nullable(),
|
|
78714
|
+
requiredHealthyMs: number().nullable(),
|
|
78715
|
+
/** Epoch ms this hold's code began. */
|
|
78716
|
+
since: number(),
|
|
78717
|
+
/** The same one-line account the broker logs, e.g.
|
|
78718
|
+
* `held: delivery 0.97 over 4.0s ok, REMB 480 kbps < mid 1800 kbps × 1.2`. */
|
|
78719
|
+
summary: string()
|
|
78031
78720
|
});
|
|
78032
78721
|
/** Per-tier count of MEASURED downgrades this viewer session has suffered. */
|
|
78033
78722
|
var WebrtcTierFailureSchema = object({
|
|
@@ -78091,7 +78780,12 @@ var WebrtcSessionDebugSchema = object({
|
|
|
78091
78780
|
tierFailures: array(WebrtcTierFailureSchema),
|
|
78092
78781
|
/** Successful promotions in this viewer session (drives the progressive
|
|
78093
78782
|
* healthy timer). Null when no ladder is running. */
|
|
78094
|
-
upgradeCount: number().nullable()
|
|
78783
|
+
upgradeCount: number().nullable(),
|
|
78784
|
+
/** Why the ladder is not climbing right now. Null when no ladder runs, it
|
|
78785
|
+
* is at the top rung, or it just moved. Optional because this output is
|
|
78786
|
+
* validated by the router: a server carrying this schema must still accept
|
|
78787
|
+
* the answer of a stream-broker deployed before the field existed. */
|
|
78788
|
+
climbHold: WebrtcClimbHoldSchema.nullable().optional()
|
|
78095
78789
|
});
|
|
78096
78790
|
DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), array(WebrtcStreamChoiceSchema)), method(object({
|
|
78097
78791
|
deviceId: number().int().nonnegative(),
|
|
@@ -79911,12 +80605,20 @@ reset: method(object({
|
|
|
79911
80605
|
},
|
|
79912
80606
|
runtimeState: ConsumablesStatusSchema.extend({ lastFetchedAt: number() }),
|
|
79913
80607
|
/**
|
|
79914
|
-
* Runtime-state durability: **
|
|
80608
|
+
* Runtime-state durability: **restored** — nothing else holds the value
|
|
80609
|
+
* (D642). A consumable can often be read only in a device's wake window (the
|
|
80610
|
+
* Dreame mower's CMS counters answer only while it charges), so a restart
|
|
80611
|
+
* that wiped the slice left the panel empty and the alerts blind until the
|
|
80612
|
+
* next wake — 2026-09-26, mower 503 idle with `items: []`. A restored item is
|
|
80613
|
+
* the previous observation, not a claim about now; the provider overwrites it
|
|
80614
|
+
* the moment it reads the device again.
|
|
79915
80615
|
*
|
|
79916
80616
|
* See `RuntimeStateDurability`. Enforced by
|
|
79917
80617
|
* `scripts/check-runtime-state-durability.ts`.
|
|
79918
80618
|
*/
|
|
79919
|
-
durability: "
|
|
80619
|
+
durability: "restored",
|
|
80620
|
+
/** Clocks, not content: a re-read that changed no item costs no commit. */
|
|
80621
|
+
volatileStateFields: ["lastChangedAt", "lastFetchedAt"]
|
|
79920
80622
|
};
|
|
79921
80623
|
/**
|
|
79922
80624
|
* Door / window / opening / garage / valve contact sensor. Boolean
|
|
@@ -83460,6 +84162,11 @@ var ClientNetworkStatsSchema = object({
|
|
|
83460
84162
|
jitterMs: number(),
|
|
83461
84163
|
estimatedBandwidthKbps: number(),
|
|
83462
84164
|
packetLossPercent: number().min(0).max(100),
|
|
84165
|
+
/** The viewer's latest `/adaptive-probe` reading, Mbps — whatever it read
|
|
84166
|
+
* (D635 I4). Absent when it never probed or the probe failed (unknown). */
|
|
84167
|
+
downlinkMbps: number().optional(),
|
|
84168
|
+
/** Age of that reading when this report was sent, ms. */
|
|
84169
|
+
downlinkAgeMs: number().optional(),
|
|
83463
84170
|
lastUpdated: number()
|
|
83464
84171
|
});
|
|
83465
84172
|
var DeviceNetworkStatsSchema = object({
|
|
@@ -83472,7 +84179,11 @@ method(object({ deviceId: number() }), DeviceNetworkStatsSchema.nullable()), met
|
|
|
83472
84179
|
rttMs: number().min(0).max(6e4),
|
|
83473
84180
|
jitterMs: number().min(0).max(1e4),
|
|
83474
84181
|
estimatedBandwidthKbps: number().min(0).max(1e6),
|
|
83475
|
-
packetLossPercent: number().min(0).max(100)
|
|
84182
|
+
packetLossPercent: number().min(0).max(100),
|
|
84183
|
+
/** Latest downlink probe, Mbps, whatever it read; absent = never / failed. */
|
|
84184
|
+
downlinkMbps: number().min(0).max(1e5).optional(),
|
|
84185
|
+
/** Age of that probe when this report is sent, ms. */
|
|
84186
|
+
downlinkAgeMs: number().min(0).max(864e5).optional()
|
|
83476
84187
|
}), _void(), { kind: "mutation" });
|
|
83477
84188
|
/**
|
|
83478
84189
|
* nodes — system-scoped singleton capability for cluster topology
|
|
@@ -83591,7 +84302,30 @@ var RenameNodeResultSchema = object({
|
|
|
83591
84302
|
nodeId: string(),
|
|
83592
84303
|
name: string()
|
|
83593
84304
|
});
|
|
83594
|
-
|
|
84305
|
+
/**
|
|
84306
|
+
* The cheap node list. A consumer's rule for a STORED node id (a rule's
|
|
84307
|
+
* `nodeIds`) that is missing from `nodes`, whatever `complete` says: render it
|
|
84308
|
+
* as a removable chip labelled unknown, never drop it (D49). Even a complete
|
|
84309
|
+
* list omits a node that joined and left between two history captures, so a
|
|
84310
|
+
* missing id means "not known to the hub", never "removed". Only the operator
|
|
84311
|
+
* removes it.
|
|
84312
|
+
*
|
|
84313
|
+
* `complete` changes only the WORDING: `false` = no history read has ever
|
|
84314
|
+
* answered, so the list may be missing offline nodes the hub does know (D315).
|
|
84315
|
+
*
|
|
84316
|
+
* `name` is the history row's name (≤ 30 s behind a rename), or the id when
|
|
84317
|
+
* the history has none.
|
|
84318
|
+
*/
|
|
84319
|
+
var NodeDirectorySchema = object({
|
|
84320
|
+
nodes: array(object({
|
|
84321
|
+
id: string(),
|
|
84322
|
+
name: string(),
|
|
84323
|
+
isHub: boolean(),
|
|
84324
|
+
isOnline: boolean()
|
|
84325
|
+
})).readonly(),
|
|
84326
|
+
complete: boolean()
|
|
84327
|
+
});
|
|
84328
|
+
method(_void(), array(TopologyNodeSchema).readonly(), { auth: "admin" }), method(_void(), NodeDirectorySchema, { auth: "admin" }), method(NodeAddonInputSchema, SuccessSchema, {
|
|
83595
84329
|
kind: "mutation",
|
|
83596
84330
|
auth: "admin"
|
|
83597
84331
|
}), method(NodeAddonInputSchema, SuccessSchema, {
|
|
@@ -89091,6 +89825,42 @@ var zoneRulesCapability = {
|
|
|
89091
89825
|
durability: "restored"
|
|
89092
89826
|
};
|
|
89093
89827
|
/**
|
|
89828
|
+
* LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
|
|
89829
|
+
* valutatore puro del suo FRONTE.
|
|
89830
|
+
*
|
|
89831
|
+
* Viveva dentro il builtin virtual-doorbell
|
|
89832
|
+
* (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
|
|
89833
|
+
* predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
|
|
89834
|
+
* `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
|
|
89835
|
+
* tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
|
|
89836
|
+
* fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
|
|
89837
|
+
* SPOSTA qui e il doorbell la ri-esporta.
|
|
89838
|
+
*
|
|
89839
|
+
* ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
|
|
89840
|
+
* due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
|
|
89841
|
+
* regola" (e include `presence`, `cover`, `alarm-panel`), questa a "qual è il
|
|
89842
|
+
* booleano il cui FRONTE conta". Vocabolari deliberatamente diversi.
|
|
89843
|
+
*/
|
|
89844
|
+
/**
|
|
89845
|
+
* Known binary / switch source caps → the boolean slice field whose
|
|
89846
|
+
* false→true rise counts as ACTIVE. Every entry is "fire on active".
|
|
89847
|
+
* Sensors whose "active" reading is not a plain boolean (presence's string
|
|
89848
|
+
* state, connectivity's connected flag) are deliberately excluded — a
|
|
89849
|
+
* reconnect is not a doorbell press, and it is not a recording either.
|
|
89850
|
+
*/
|
|
89851
|
+
var SOURCE_CAP_ACTIVE_FIELD = {
|
|
89852
|
+
contact: "entryOpen",
|
|
89853
|
+
binary: "on",
|
|
89854
|
+
switch: "on",
|
|
89855
|
+
motion: "detected",
|
|
89856
|
+
flood: "flooded",
|
|
89857
|
+
gas: "detected",
|
|
89858
|
+
smoke: "detected",
|
|
89859
|
+
"carbon-monoxide": "detected",
|
|
89860
|
+
vibration: "detected",
|
|
89861
|
+
tamper: "tampered"
|
|
89862
|
+
};
|
|
89863
|
+
/**
|
|
89094
89864
|
* Accessory device helpers — shared across drivers.
|
|
89095
89865
|
*
|
|
89096
89866
|
* Many vendor-specific drivers register accessory child devices on
|
|
@@ -93618,6 +94388,12 @@ Object.freeze({
|
|
|
93618
94388
|
addonId: null,
|
|
93619
94389
|
access: "view"
|
|
93620
94390
|
},
|
|
94391
|
+
"deviceManager.listDeviceIdsByCaps": {
|
|
94392
|
+
capName: "device-manager",
|
|
94393
|
+
capScope: "system",
|
|
94394
|
+
addonId: null,
|
|
94395
|
+
access: "view"
|
|
94396
|
+
},
|
|
93621
94397
|
"deviceManager.listLocationDescriptions": {
|
|
93622
94398
|
capName: "device-manager",
|
|
93623
94399
|
capScope: "system",
|
|
@@ -94578,12 +95354,30 @@ Object.freeze({
|
|
|
94578
95354
|
addonId: null,
|
|
94579
95355
|
access: "create"
|
|
94580
95356
|
},
|
|
95357
|
+
"llmRuntime.appleAvailability": {
|
|
95358
|
+
capName: "llm-runtime",
|
|
95359
|
+
capScope: "system",
|
|
95360
|
+
addonId: null,
|
|
95361
|
+
access: "view"
|
|
95362
|
+
},
|
|
95363
|
+
"llmRuntime.cancel": {
|
|
95364
|
+
capName: "llm-runtime",
|
|
95365
|
+
capScope: "system",
|
|
95366
|
+
addonId: null,
|
|
95367
|
+
access: "create"
|
|
95368
|
+
},
|
|
94581
95369
|
"llmRuntime.complete": {
|
|
94582
95370
|
capName: "llm-runtime",
|
|
94583
95371
|
capScope: "system",
|
|
94584
95372
|
addonId: null,
|
|
94585
95373
|
access: "create"
|
|
94586
95374
|
},
|
|
95375
|
+
"llmRuntime.completeApple": {
|
|
95376
|
+
capName: "llm-runtime",
|
|
95377
|
+
capScope: "system",
|
|
95378
|
+
addonId: null,
|
|
95379
|
+
access: "create"
|
|
95380
|
+
},
|
|
94587
95381
|
"llmRuntime.deleteModel": {
|
|
94588
95382
|
capName: "llm-runtime",
|
|
94589
95383
|
capScope: "system",
|
|
@@ -95256,6 +96050,12 @@ Object.freeze({
|
|
|
95256
96050
|
addonId: null,
|
|
95257
96051
|
access: "create"
|
|
95258
96052
|
},
|
|
96053
|
+
"nodes.directory": {
|
|
96054
|
+
capName: "nodes",
|
|
96055
|
+
capScope: "system",
|
|
96056
|
+
addonId: null,
|
|
96057
|
+
access: "view"
|
|
96058
|
+
},
|
|
95259
96059
|
"nodes.executeQuery": {
|
|
95260
96060
|
capName: "nodes",
|
|
95261
96061
|
capScope: "system",
|
|
@@ -95424,6 +96224,18 @@ Object.freeze({
|
|
|
95424
96224
|
addonId: null,
|
|
95425
96225
|
access: "view"
|
|
95426
96226
|
},
|
|
96227
|
+
"notificationRules.getRuleLastRuns": {
|
|
96228
|
+
capName: "notification-rules",
|
|
96229
|
+
capScope: "system",
|
|
96230
|
+
addonId: null,
|
|
96231
|
+
access: "view"
|
|
96232
|
+
},
|
|
96233
|
+
"notificationRules.getTemplateCatalog": {
|
|
96234
|
+
capName: "notification-rules",
|
|
96235
|
+
capScope: "system",
|
|
96236
|
+
addonId: null,
|
|
96237
|
+
access: "view"
|
|
96238
|
+
},
|
|
95427
96239
|
"notificationRules.listDeviceMutes": {
|
|
95428
96240
|
capName: "notification-rules",
|
|
95429
96241
|
capScope: "system",
|
|
@@ -95442,6 +96254,12 @@ Object.freeze({
|
|
|
95442
96254
|
addonId: null,
|
|
95443
96255
|
access: "view"
|
|
95444
96256
|
},
|
|
96257
|
+
"notificationRules.previewTemplate": {
|
|
96258
|
+
capName: "notification-rules",
|
|
96259
|
+
capScope: "system",
|
|
96260
|
+
addonId: null,
|
|
96261
|
+
access: "view"
|
|
96262
|
+
},
|
|
95445
96263
|
"notificationRules.resolveArtifactUrl": {
|
|
95446
96264
|
capName: "notification-rules",
|
|
95447
96265
|
capScope: "system",
|
|
@@ -101008,8 +101826,173 @@ var NC_AUDIO_DEFAULTS = {
|
|
|
101008
101826
|
samplingSeconds: 10
|
|
101009
101827
|
};
|
|
101010
101828
|
new Set(["devices", "classes"]);
|
|
101829
|
+
/**
|
|
101830
|
+
* WHICH narrowing filter speaks about WHICH system-event kind — one predicate,
|
|
101831
|
+
* consumed by the rule ENGINE and by every rule EDITOR.
|
|
101832
|
+
*
|
|
101833
|
+
* ── The failure this file exists for ───────────────────────────────────────
|
|
101834
|
+
* `NcSystemEventConditionSchema` carries four optional narrowing lists
|
|
101835
|
+
* (`deviceIds`, `deviceTypes`, `nodeIds`, `packageNames`) beside a required
|
|
101836
|
+
* `kinds` gate. They are not four filters over one vocabulary: each belongs to
|
|
101837
|
+
* a FAMILY of kinds and is meaningless outside it. A node going away has no
|
|
101838
|
+
* device type; a completed export has no package; `stream-offline` deliberately
|
|
101839
|
+
* carries no device type at all (a stream belongs to a camera by construction).
|
|
101840
|
+
*
|
|
101841
|
+
* The engine applied all four to every kind, so a filter from one family failed
|
|
101842
|
+
* a kind from another family CLOSED. The operator's live rule "Eventi di
|
|
101843
|
+
* sistema" selected eleven kinds and set `deviceTypes: ['camera']` — the only
|
|
101844
|
+
* way to say "only my cameras" about the two liveness kinds — and NINE of the
|
|
101845
|
+
* eleven silently stopped being deliverable. Four days of history contained
|
|
101846
|
+
* `device-online` and `device-offline` and nothing else, across a window in
|
|
101847
|
+
* which agents were restarted and addons were updated.
|
|
101848
|
+
*
|
|
101849
|
+
* Both editors already computed this scoping, to decide which filter boxes to
|
|
101850
|
+
* render; the cap's own docblock already promised it ("Narrow a `device-*` kind
|
|
101851
|
+
* to these device TYPES"). Only the engine disagreed, and it disagreed in the
|
|
101852
|
+
* destructive direction. So the rule is stated ONCE, here, and the engine and
|
|
101853
|
+
* the editors are readers of it rather than three independent authors.
|
|
101854
|
+
*
|
|
101855
|
+
* ── The direction ─────────────────────────────────────────────────────────
|
|
101856
|
+
* Out of family ⇒ the filter is IGNORED, never fail-closed. An editor that
|
|
101857
|
+
* hides a box has already told the operator this filter does not speak about
|
|
101858
|
+
* these kinds, so honouring it as an exclusion contradicts the only
|
|
101859
|
+
* explanation the operator was ever given. Inside its family the filter still
|
|
101860
|
+
* narrows, and still fails CLOSED on a subject that cannot produce the identity
|
|
101861
|
+
* it tests (a device the mirror cannot name is not evidence of a camera).
|
|
101862
|
+
*/
|
|
101863
|
+
/**
|
|
101864
|
+
* Every filter, for the callers that must cover all of them (guards, tests).
|
|
101865
|
+
*
|
|
101866
|
+
* `consumableLowPercent` (D642) is not a list but it is a filter in exactly
|
|
101867
|
+
* this sense: it belongs to a family of kinds (the consumable pair) and the
|
|
101868
|
+
* engine consults it only there. It differs in one way the readers must know —
|
|
101869
|
+
* its ABSENCE is not "no narrowing" but the default threshold, because a
|
|
101870
|
+
* consumable episode is always judged at SOME threshold.
|
|
101871
|
+
*/
|
|
101872
|
+
var NC_SYSTEM_EVENT_FILTER_KEYS = [
|
|
101873
|
+
"deviceIds",
|
|
101874
|
+
"deviceTypes",
|
|
101875
|
+
"nodeIds",
|
|
101876
|
+
"packageNames",
|
|
101877
|
+
"consumableLowPercent"
|
|
101878
|
+
];
|
|
101879
|
+
new Map(Object.entries({
|
|
101880
|
+
"device-online": "device-events",
|
|
101881
|
+
"device-offline": "device-events",
|
|
101882
|
+
"device-disabled": "device-events",
|
|
101883
|
+
"device-enabled": "device-events",
|
|
101884
|
+
"device-battery-low": "device-events",
|
|
101885
|
+
"device-battery-normal": "device-events",
|
|
101886
|
+
"device-consumable-low": "device-events",
|
|
101887
|
+
"device-consumable-normal": "device-events",
|
|
101888
|
+
"device-tank-low": "device-events",
|
|
101889
|
+
"device-tank-full": "device-events",
|
|
101890
|
+
"device-tank-normal": "device-events",
|
|
101891
|
+
"stream-online": "device-events",
|
|
101892
|
+
"stream-offline": "device-events",
|
|
101893
|
+
"detection-blind": "device-events",
|
|
101894
|
+
"node-online": "system",
|
|
101895
|
+
"node-offline": "system",
|
|
101896
|
+
"node-inference-unavailable": "system",
|
|
101897
|
+
"addon-crash-loop": "system",
|
|
101898
|
+
"addon-update-available": "system",
|
|
101899
|
+
"addon-updated": "system",
|
|
101900
|
+
"server-update-available": "system",
|
|
101901
|
+
"server-updated": "system",
|
|
101902
|
+
"wrapper-update-available": "system",
|
|
101903
|
+
"backup-completed": "system",
|
|
101904
|
+
"export-completed": "system",
|
|
101905
|
+
"alarm-triggered": "alarm",
|
|
101906
|
+
"alarm-armed": "alarm",
|
|
101907
|
+
"alarm-disarmed": "alarm",
|
|
101908
|
+
"alarm-arming": "alarm",
|
|
101909
|
+
"alarm-arm-refused": "alarm",
|
|
101910
|
+
"alarm-pending": "alarm",
|
|
101911
|
+
"alarm-rearmed": "alarm",
|
|
101912
|
+
"alarm-sensor-bypass": "alarm",
|
|
101913
|
+
"alarm-check-failed": "alarm",
|
|
101914
|
+
"camera-online": null,
|
|
101915
|
+
"camera-offline": null,
|
|
101916
|
+
"camera-disabled": null,
|
|
101917
|
+
"camera-enabled": null
|
|
101918
|
+
}));
|
|
101919
|
+
[...NC_SYSTEM_EVENT_FILTER_KEYS];
|
|
101011
101920
|
NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
|
|
101012
101921
|
/**
|
|
101922
|
+
* WHICH WAY A BOOLEAN SENSOR MOVED — read off the one slice a sensor event
|
|
101923
|
+
* persists (D640 "a sensor rule fires on the raise, the clear, or both").
|
|
101924
|
+
*
|
|
101925
|
+
* A device-event rule over a flood sensor notified on BOTH edges: water
|
|
101926
|
+
* detected, and back to dry. `NcConditions.sensorTransition` lets the rule
|
|
101927
|
+
* name one edge; this file answers, for one `SensorEvent`, which edge it is.
|
|
101928
|
+
*
|
|
101929
|
+
* NOT A SECOND TABLE. The "which boolean of this cap counts as HIGH" question
|
|
101930
|
+
* already has one answer, `SOURCE_CAP_ACTIVE_FIELD`
|
|
101931
|
+
* (`catalogs/sensor-active-state.ts`, the doorbell's and the recorder's edge
|
|
101932
|
+
* table), and a sensor event names its kind, not its cap. So the table here is
|
|
101933
|
+
* DERIVED: event kind → cap (`EVENT_KIND_BY_CAP`) → that cap's active field.
|
|
101934
|
+
* Only kinds whose taxonomy category is `sensor` take part — a switch has an
|
|
101935
|
+
* `on` boolean too, but "it triggers / it clears" is not a sentence about a
|
|
101936
|
+
* light, and offering it there would be a knob with a misleading label.
|
|
101937
|
+
* `presence` is a WORD (`home` / `not_home` / a zone), not a boolean, and is
|
|
101938
|
+
* absent from the source table, so it is absent here.
|
|
101939
|
+
*
|
|
101940
|
+
* `undefined` = never guessed: a kind with no raise (a doorbell press, a
|
|
101941
|
+
* button, a cover), or a slice whose field is missing or not a boolean. The
|
|
101942
|
+
* engine then does NOT filter — a flood alert must not go silent because one
|
|
101943
|
+
* read came back unreadable (the same stance as the alarm's `sensorEdgeOf`).
|
|
101944
|
+
*
|
|
101945
|
+
* Pure, RN-safe: the viewer imports the support rule from here.
|
|
101946
|
+
*/
|
|
101947
|
+
/** Event kind → the boolean field of its cap slice that means "raised". */
|
|
101948
|
+
var NC_SENSOR_RAISED_FIELD_BY_KIND = Object.freeze(Object.fromEntries(Object.entries(EVENT_KIND_BY_CAP).flatMap(([capName, descriptor]) => {
|
|
101949
|
+
const field = SOURCE_CAP_ACTIVE_FIELD[capName];
|
|
101950
|
+
if (field === void 0 || descriptor.category !== "sensor") return [];
|
|
101951
|
+
return [[descriptor.kind, field]];
|
|
101952
|
+
})));
|
|
101953
|
+
Object.freeze(Object.keys(NC_SENSOR_RAISED_FIELD_BY_KIND));
|
|
101954
|
+
var RULE_BODY_ONLY = ["body"];
|
|
101955
|
+
var RULE_TEXT = ["title", "body"];
|
|
101956
|
+
var BATTERY = ["device-battery-low", "device-battery-normal"];
|
|
101957
|
+
var CONSUMABLE = ["device-consumable-low", "device-consumable-normal"];
|
|
101958
|
+
var TANK = [
|
|
101959
|
+
"device-tank-low",
|
|
101960
|
+
"device-tank-full",
|
|
101961
|
+
"device-tank-normal"
|
|
101962
|
+
];
|
|
101963
|
+
var UPDATES = [
|
|
101964
|
+
"addon-update-available",
|
|
101965
|
+
"server-update-available",
|
|
101966
|
+
"wrapper-update-available"
|
|
101967
|
+
];
|
|
101968
|
+
var PACKAGE_UPDATES = ["addon-update-available", "server-update-available"];
|
|
101969
|
+
var ALARM = [
|
|
101970
|
+
"alarm-triggered",
|
|
101971
|
+
"alarm-armed",
|
|
101972
|
+
"alarm-disarmed",
|
|
101973
|
+
"alarm-arming",
|
|
101974
|
+
"alarm-arm-refused",
|
|
101975
|
+
"alarm-pending",
|
|
101976
|
+
"alarm-rearmed",
|
|
101977
|
+
"alarm-sensor-bypass",
|
|
101978
|
+
"alarm-check-failed"
|
|
101979
|
+
];
|
|
101980
|
+
var COMBINED_ALARM = ["alarm-triggered"];
|
|
101981
|
+
var DEVICE_EVENTS = [
|
|
101982
|
+
"device-online",
|
|
101983
|
+
"device-offline",
|
|
101984
|
+
"device-disabled",
|
|
101985
|
+
"device-enabled",
|
|
101986
|
+
...BATTERY,
|
|
101987
|
+
...CONSUMABLE,
|
|
101988
|
+
...TANK,
|
|
101989
|
+
"stream-online",
|
|
101990
|
+
"stream-offline",
|
|
101991
|
+
"detection-blind",
|
|
101992
|
+
...ALARM
|
|
101993
|
+
];
|
|
101994
|
+
[...DEVICE_EVENTS], [...RULE_TEXT], [...COMBINED_ALARM], [...RULE_TEXT], [...COMBINED_ALARM], [...RULE_TEXT], [...RULE_TEXT], [...COMBINED_ALARM], [...RULE_TEXT], [...PACKAGE_UPDATES], [...RULE_TEXT], [...RULE_TEXT], [...COMBINED_ALARM], [...RULE_TEXT], [...COMBINED_ALARM], [...RULE_TEXT], [...COMBINED_ALARM], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_BODY_ONLY], [...RULE_TEXT], [...RULE_TEXT], [...DEVICE_EVENTS], [...RULE_TEXT], [...DEVICE_EVENTS], [...RULE_TEXT], [...BATTERY, ...CONSUMABLE], [...RULE_TEXT], [...CONSUMABLE], [...RULE_TEXT], [...TANK], [...RULE_TEXT], [...RULE_TEXT], [...UPDATES], [...RULE_TEXT], [...UPDATES], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...UPDATES], [...RULE_TEXT], [...PACKAGE_UPDATES], [...RULE_TEXT], [...PACKAGE_UPDATES], [...RULE_TEXT], [...UPDATES], [...RULE_TEXT], [...UPDATES], [...RULE_TEXT], [...PACKAGE_UPDATES], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT], [...RULE_TEXT];
|
|
101995
|
+
/**
|
|
101013
101996
|
* TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
|
|
101014
101997
|
*
|
|
101015
101998
|
* Spec: `docs/superpowers/specs/2026-07-24-nc-occupancy-timelapse-design.md`
|
|
@@ -103261,10 +104244,13 @@ var DreameChildDevice = class extends BaseDevice {
|
|
|
103261
104244
|
try {
|
|
103262
104245
|
this.recomputeSlice();
|
|
103263
104246
|
} catch (err) {
|
|
103264
|
-
this.ctx.logger.warn("dreame child: stateChanged handler error", {
|
|
103265
|
-
|
|
103266
|
-
|
|
103267
|
-
|
|
104247
|
+
this.ctx.logger.warn("dreame child: stateChanged handler error", {
|
|
104248
|
+
tags: { deviceId: this.id },
|
|
104249
|
+
meta: {
|
|
104250
|
+
dreameDeviceId: this.dreameDeviceId,
|
|
104251
|
+
error: errMsg(err)
|
|
104252
|
+
}
|
|
104253
|
+
});
|
|
103268
104254
|
}
|
|
103269
104255
|
};
|
|
103270
104256
|
this.stateChangedUnsub = dreameFacades.onStateChanged(this.brokerId, this.dreameDeviceId, onState);
|
|
@@ -103709,6 +104695,40 @@ function clampPercent$1(value) {
|
|
|
103709
104695
|
return Math.max(0, Math.min(100, value));
|
|
103710
104696
|
}
|
|
103711
104697
|
/**
|
|
104698
|
+
* Where each {@link VacuumTankProps} field lives (siid/piid). Property ids and
|
|
104699
|
+
* enum values are the HA Dreame integration's (Tasshack `dreame-vacuum`
|
|
104700
|
+
* `types.py`, `DreameVacuumProperty` → siid/piid map; same on `dev` and
|
|
104701
|
+
* v2.0.0b25, checked 2026-09-26). 27/1 and 27/4 were already read here; 27/2 and
|
|
104702
|
+
* 27/3 are their neighbours on the same station service. The robot pushes them
|
|
104703
|
+
* on change; the consumables refresh also re-reads them from the shadow.
|
|
104704
|
+
*/
|
|
104705
|
+
var VACUUM_TANK_PROPS = {
|
|
104706
|
+
cleanWaterTankStatus: {
|
|
104707
|
+
siid: 27,
|
|
104708
|
+
piid: 1
|
|
104709
|
+
},
|
|
104710
|
+
waterTank: {
|
|
104711
|
+
siid: 4,
|
|
104712
|
+
piid: 6
|
|
104713
|
+
},
|
|
104714
|
+
detergentStatus: {
|
|
104715
|
+
siid: 27,
|
|
104716
|
+
piid: 4
|
|
104717
|
+
},
|
|
104718
|
+
dustCollection: {
|
|
104719
|
+
siid: 15,
|
|
104720
|
+
piid: 3
|
|
104721
|
+
},
|
|
104722
|
+
dirtyWaterTankStatus: {
|
|
104723
|
+
siid: 27,
|
|
104724
|
+
piid: 2
|
|
104725
|
+
},
|
|
104726
|
+
dustBagStatus: {
|
|
104727
|
+
siid: 27,
|
|
104728
|
+
piid: 3
|
|
104729
|
+
}
|
|
104730
|
+
};
|
|
104731
|
+
/**
|
|
103712
104732
|
* Clean-water tank `status`: prefer the richer clean-water-tank-status (27/1),
|
|
103713
104733
|
* fall back to the coarser water-tank presence (4/6). `null` when neither maps
|
|
103714
104734
|
* to a known good/low reading (NOT_INSTALLED / CHECKING / unknown). Pure.
|
|
@@ -103728,10 +104748,32 @@ function detergentTankStatus(value) {
|
|
|
103728
104748
|
if (value === 2) return "low";
|
|
103729
104749
|
return null;
|
|
103730
104750
|
}
|
|
103731
|
-
/**
|
|
103732
|
-
|
|
103733
|
-
|
|
103734
|
-
|
|
104751
|
+
/**
|
|
104752
|
+
* Dust-bin `status` from dust-collection (15/3); OVER_USE reads as `full`. A dust
|
|
104753
|
+
* bag that is missing or asks to be checked (27/3 NOT_INSTALLED / CHECK) is never
|
|
104754
|
+
* `ok` — the enum does not say "full" either, so it is unknown. Pure.
|
|
104755
|
+
*/
|
|
104756
|
+
function dustBinTankStatus(dustCollection, dustBag) {
|
|
104757
|
+
if (dustCollection === 2) return "full";
|
|
104758
|
+
if (dustBag !== null && dustBag !== 0) return null;
|
|
104759
|
+
if (dustCollection === 1) return "ok";
|
|
104760
|
+
return null;
|
|
104761
|
+
}
|
|
104762
|
+
/**
|
|
104763
|
+
* Dirty-water tank `status` from dirty-water-tank-status (27/2). The robot has
|
|
104764
|
+
* ONE value for "not installed or full" and cannot tell them apart; lifting the
|
|
104765
|
+
* tank out to EMPTY it reads exactly like a full one, so that value is unknown
|
|
104766
|
+
* (`null`) — never `full`, which would raise a false "tank full" at the moment
|
|
104767
|
+
* someone is fixing it (controller ruling 2026-09-26). Absent or any other
|
|
104768
|
+
* value is unknown too, never `ok`.
|
|
104769
|
+
*
|
|
104770
|
+
* The unambiguous "full" is the robot's own error `MiotError.DirtyWaterTankFull`
|
|
104771
|
+
* (86, the library's `errorCode`, siid 2 piid 2): while it is raised the tank is
|
|
104772
|
+
* `full` whatever 27/2 says; once it clears, 27/2 decides again. Pure.
|
|
104773
|
+
*/
|
|
104774
|
+
function dirtyWaterTankStatus(value, errorCode) {
|
|
104775
|
+
if (errorCode === MiotError.DirtyWaterTankFull) return "full";
|
|
104776
|
+
if (value === 0) return "ok";
|
|
103735
104777
|
return null;
|
|
103736
104778
|
}
|
|
103737
104779
|
/**
|
|
@@ -103755,16 +104797,16 @@ function dustBinTankStatus(value) {
|
|
|
103755
104797
|
* - detergent ← `hasDetergentReservoir`
|
|
103756
104798
|
* - dust-bin ← `canAutoEmpty` (an auto-empty robot has an onboard dust bin)
|
|
103757
104799
|
*/
|
|
103758
|
-
function decodeVacuumTanks(caps, props) {
|
|
104800
|
+
function decodeVacuumTanks(caps, props, errorCode = null) {
|
|
103759
104801
|
const tank = (present, status) => present ? {
|
|
103760
104802
|
level: null,
|
|
103761
104803
|
status
|
|
103762
104804
|
} : null;
|
|
103763
104805
|
return {
|
|
103764
104806
|
cleanWater: tank(Boolean(caps?.canMop), props ? cleanWaterTankStatus(props) : null),
|
|
103765
|
-
dirtyWater: tank(Boolean(caps?.canMopWash), null),
|
|
104807
|
+
dirtyWater: tank(Boolean(caps?.canMopWash), dirtyWaterTankStatus(props?.dirtyWaterTankStatus ?? null, errorCode)),
|
|
103766
104808
|
detergent: tank(Boolean(caps?.hasDetergentReservoir), props ? detergentTankStatus(props.detergentStatus) : null),
|
|
103767
|
-
dustBin: tank(Boolean(caps?.canAutoEmpty), props ? dustBinTankStatus(props.dustCollection) : null)
|
|
104809
|
+
dustBin: tank(Boolean(caps?.canAutoEmpty), props ? dustBinTankStatus(props.dustCollection, props.dustBagStatus) : null)
|
|
103768
104810
|
};
|
|
103769
104811
|
}
|
|
103770
104812
|
/**
|
|
@@ -103778,19 +104820,18 @@ function clampConsumablePct(value) {
|
|
|
103778
104820
|
return Math.max(0, Math.min(100, value));
|
|
103779
104821
|
}
|
|
103780
104822
|
/**
|
|
103781
|
-
*
|
|
103782
|
-
* handle. The returned object satisfies the cap's `runtimeState` schema
|
|
103783
|
-
* (`ConsumablesStatusSchema.extend({ lastFetchedAt: z.number() })`).
|
|
104823
|
+
* The `consumables` cap items for the consumables a vacuum REPORTS.
|
|
103784
104824
|
*
|
|
103785
104825
|
* PRESENCE-driven (mirrors HA): the library's `supportedConsumables` enumerates
|
|
103786
|
-
* ONLY the consumables this model reports (a present MIoT life property)
|
|
103787
|
-
*
|
|
103788
|
-
*
|
|
103789
|
-
*
|
|
103790
|
-
*
|
|
103791
|
-
|
|
103792
|
-
|
|
103793
|
-
|
|
104826
|
+
* ONLY the consumables this model reports (a present MIoT life property), each
|
|
104827
|
+
* with its remaining-life % and whether a reset action exists. No hardcoded
|
|
104828
|
+
* per-model list: a consumable the device does not expose never appears.
|
|
104829
|
+
*
|
|
104830
|
+
* `status` is `null` — the provider reports the level, the notification rule
|
|
104831
|
+
* decides what is low. The slice clocks are `nextConsumablesSlice`'s job. Pure.
|
|
104832
|
+
*/
|
|
104833
|
+
function buildVacuumConsumableItems(readings) {
|
|
104834
|
+
return readings.map((c) => ({
|
|
103794
104835
|
key: c.key,
|
|
103795
104836
|
label: c.label,
|
|
103796
104837
|
level: clampConsumablePct(c.leftPct),
|
|
@@ -103798,12 +104839,26 @@ function buildVacuumConsumables(vacuum) {
|
|
|
103798
104839
|
lastResetAt: null,
|
|
103799
104840
|
resettable: c.resettable
|
|
103800
104841
|
}));
|
|
103801
|
-
|
|
103802
|
-
|
|
103803
|
-
|
|
103804
|
-
|
|
103805
|
-
|
|
103806
|
-
|
|
104842
|
+
}
|
|
104843
|
+
/** Structural equality of two JSON-shaped values, key order ignored. */
|
|
104844
|
+
function jsonEqual(a, b) {
|
|
104845
|
+
if (a === b) return true;
|
|
104846
|
+
if (Array.isArray(a) || Array.isArray(b)) return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((v, i) => jsonEqual(v, b[i]));
|
|
104847
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
|
|
104848
|
+
const ka = Object.keys(a);
|
|
104849
|
+
const kb = Object.keys(b);
|
|
104850
|
+
return ka.length === kb.length && ka.every((k) => jsonEqual(Reflect.get(a, k), Reflect.get(b, k)));
|
|
104851
|
+
}
|
|
104852
|
+
/**
|
|
104853
|
+
* The `vacuum-control` slice to write, or `null` when only the clock moved.
|
|
104854
|
+
* `recomputeSlice` runs on EVERY push; without this each one was a write (and a
|
|
104855
|
+
* `device.state-changed`) even when nothing the robot reported had changed. Pure.
|
|
104856
|
+
*/
|
|
104857
|
+
function nextVacuumControlSlice(prev, next) {
|
|
104858
|
+
if (prev === void 0) return next;
|
|
104859
|
+
const { lastChangedAt: _prevAt, ...prevContent } = prev;
|
|
104860
|
+
const { lastChangedAt: _nextAt, ...nextContent } = next;
|
|
104861
|
+
return jsonEqual(prevContent, nextContent) ? null : next;
|
|
103807
104862
|
}
|
|
103808
104863
|
//#endregion
|
|
103809
104864
|
//#region src/devices/dreame-camera-stream.ts
|
|
@@ -150357,7 +151412,7 @@ var DreameCameraDevice = class extends DreameChildDevice {
|
|
|
150357
151412
|
});
|
|
150358
151413
|
const rfc = await startRfc4571Server({
|
|
150359
151414
|
stream,
|
|
150360
|
-
logger: this.ctx.logger,
|
|
151415
|
+
logger: this.ctx.logger.withTags({ deviceId: this.id }),
|
|
150361
151416
|
keyframeTimeoutMs: WAKE_TIMEOUT_MS,
|
|
150362
151417
|
onConsumerDisconnect: () => {
|
|
150363
151418
|
this.closeStream("idle");
|
|
@@ -150832,10 +151887,13 @@ var DreameMowerActionDevice = class extends DreameChildDevice {
|
|
|
150832
151887
|
try {
|
|
150833
151888
|
await mower.getMap();
|
|
150834
151889
|
} catch (err) {
|
|
150835
|
-
this.ctx.logger.debug("dreame mower action: map fetch failed (asleep?)", {
|
|
150836
|
-
|
|
150837
|
-
|
|
150838
|
-
|
|
151890
|
+
this.ctx.logger.debug("dreame mower action: map fetch failed (asleep?)", {
|
|
151891
|
+
tags: { deviceId: this.id },
|
|
151892
|
+
meta: {
|
|
151893
|
+
dreameDeviceId: this.dreameDeviceId,
|
|
151894
|
+
error: errMsg(err)
|
|
151895
|
+
}
|
|
151896
|
+
});
|
|
150839
151897
|
} finally {
|
|
150840
151898
|
this.#inflight = false;
|
|
150841
151899
|
this.#publishSlice();
|
|
@@ -150966,45 +152024,293 @@ var MOWER_CONSUMABLE_LABEL = {
|
|
|
150966
152024
|
function isMowerConsumableKey(key) {
|
|
150967
152025
|
return key === "blade" || key === "brush" || key === "maintenance";
|
|
150968
152026
|
}
|
|
150969
|
-
/** Below this remaining-life %, an item is flagged `replace` (mirrors the
|
|
150970
|
-
* donor's low-consumable warning threshold). */
|
|
150971
|
-
var MOWER_CONSUMABLE_REPLACE_PCT = 10;
|
|
150972
152027
|
/**
|
|
150973
152028
|
* Map the library's CMS consumable readings (blade/brush/maintenance, remaining
|
|
150974
152029
|
* %) to the `consumables` cap items. All three drive a real CMS reset, so every
|
|
150975
|
-
* item is `resettable`.
|
|
150976
|
-
*
|
|
152030
|
+
* item is `resettable`.
|
|
152031
|
+
*
|
|
152032
|
+
* `status` is `null`: the provider reports the LEVEL and gives no verdict. When
|
|
152033
|
+
* a level is "low" is the notification rule's threshold (operator, per rule);
|
|
152034
|
+
* a provider constant here used to flag `replace` at 10 % and override it.
|
|
150977
152035
|
*/
|
|
150978
152036
|
function buildMowerConsumables(readings) {
|
|
150979
152037
|
return readings.map((r) => ({
|
|
150980
152038
|
key: r.key,
|
|
150981
152039
|
label: MOWER_CONSUMABLE_LABEL[r.key],
|
|
150982
152040
|
level: r.remainingPercent,
|
|
150983
|
-
status:
|
|
152041
|
+
status: null,
|
|
150984
152042
|
lastResetAt: null,
|
|
150985
152043
|
resettable: true
|
|
150986
152044
|
}));
|
|
150987
152045
|
}
|
|
150988
152046
|
//#endregion
|
|
152047
|
+
//#region src/devices/dreame-consumables.ts
|
|
152048
|
+
/**
|
|
152049
|
+
* The `consumables` slice as both Dreame devices write it, and the one
|
|
152050
|
+
* refresher that re-reads it.
|
|
152051
|
+
*
|
|
152052
|
+
* - A slice is written only when something changed: an item (lastChangedAt), or
|
|
152053
|
+
* a refresh that LANDED (lastFetchedAt, which the cap declares volatile).
|
|
152054
|
+
* - An empty list is never written. "The read said nothing" is not "this device
|
|
152055
|
+
* has no consumables" — and the slice is `restored` across restarts (D642),
|
|
152056
|
+
* so an empty write would erase the last real observation.
|
|
152057
|
+
* - A refresh never overlaps itself, and a failed one changes nothing (D49): the
|
|
152058
|
+
* last good values stay, and the failure is logged with the device tag.
|
|
152059
|
+
*/
|
|
152060
|
+
var CONSUMABLES_CAP_NAME = "consumables";
|
|
152061
|
+
var CONSUMABLES_COLD_START = {
|
|
152062
|
+
items: [],
|
|
152063
|
+
lastChangedAt: 0,
|
|
152064
|
+
lastFetchedAt: 0
|
|
152065
|
+
};
|
|
152066
|
+
function itemEqual(a, b) {
|
|
152067
|
+
return a.key === b.key && a.label === b.label && a.level === b.level && a.status === b.status && a.lastResetAt === b.lastResetAt && a.resettable === b.resettable;
|
|
152068
|
+
}
|
|
152069
|
+
function itemsEqual(a, b) {
|
|
152070
|
+
return a.length === b.length && a.every((item, i) => {
|
|
152071
|
+
const other = b[i];
|
|
152072
|
+
return other !== void 0 && itemEqual(item, other);
|
|
152073
|
+
});
|
|
152074
|
+
}
|
|
152075
|
+
/**
|
|
152076
|
+
* The slice to write, or `null` for "write nothing".
|
|
152077
|
+
*
|
|
152078
|
+
* `landed` = these items come from a refresh that just answered (not from a
|
|
152079
|
+
* replay of the cache on a push), so `lastFetchedAt` may move.
|
|
152080
|
+
*/
|
|
152081
|
+
function nextConsumablesSlice(prev, items, now, landed) {
|
|
152082
|
+
if (items.length === 0) return null;
|
|
152083
|
+
if (prev !== void 0 && itemsEqual(prev.items, items)) return landed ? {
|
|
152084
|
+
...prev,
|
|
152085
|
+
items: [...prev.items],
|
|
152086
|
+
lastFetchedAt: now
|
|
152087
|
+
} : null;
|
|
152088
|
+
return {
|
|
152089
|
+
items: [...items],
|
|
152090
|
+
lastChangedAt: now,
|
|
152091
|
+
lastFetchedAt: landed ? now : prev?.lastFetchedAt ?? 0
|
|
152092
|
+
};
|
|
152093
|
+
}
|
|
152094
|
+
/** Write {@link nextConsumablesSlice}'s answer, if it has one. */
|
|
152095
|
+
function writeConsumables(store, items, now, landed) {
|
|
152096
|
+
const next = nextConsumablesSlice(store.getCapState(CONSUMABLES_CAP_NAME), items, now, landed);
|
|
152097
|
+
if (next !== null) store.setCapState(CONSUMABLES_CAP_NAME, next);
|
|
152098
|
+
}
|
|
152099
|
+
/** Seed the cold start ONLY when nothing was restored — never wipe the last reading. */
|
|
152100
|
+
function seedConsumablesColdStart(store) {
|
|
152101
|
+
if (store.getCapState("consumables") === void 0) store.setCapState(CONSUMABLES_CAP_NAME, CONSUMABLES_COLD_START);
|
|
152102
|
+
}
|
|
152103
|
+
/**
|
|
152104
|
+
* Narrow a cap-supplied reset key to one THIS device reports and can reset.
|
|
152105
|
+
* A real key of another model (e.g. `mop-pad` on a robot without one) would
|
|
152106
|
+
* otherwise send a reset action to a service the robot lacks.
|
|
152107
|
+
*/
|
|
152108
|
+
function resolveResettableKey(key, present) {
|
|
152109
|
+
const hit = present.find((c) => c.key === key);
|
|
152110
|
+
if (hit === void 0) throw new Error(`this device does not report "${key}"`);
|
|
152111
|
+
if (!hit.resettable) throw new Error(`"${key}" has no reset action on this device`);
|
|
152112
|
+
return hit.key;
|
|
152113
|
+
}
|
|
152114
|
+
var DEFAULT_FOLLOW_UP_MS = 6e4;
|
|
152115
|
+
var DreameConsumablesRefresher = class {
|
|
152116
|
+
#deps;
|
|
152117
|
+
#interval = null;
|
|
152118
|
+
#followUp = null;
|
|
152119
|
+
#running = null;
|
|
152120
|
+
/** At most ONE run waits behind the one in flight. */
|
|
152121
|
+
#queued = null;
|
|
152122
|
+
#stopped = false;
|
|
152123
|
+
constructor(deps) {
|
|
152124
|
+
this.#deps = deps;
|
|
152125
|
+
}
|
|
152126
|
+
/** False once stopped — the device is gone, nothing may be written for it. */
|
|
152127
|
+
live = () => !this.#stopped;
|
|
152128
|
+
/** Arm the periodic re-read (the caller triggers the first read itself). */
|
|
152129
|
+
start() {
|
|
152130
|
+
this.#stopped = false;
|
|
152131
|
+
const { intervalMs } = this.#deps;
|
|
152132
|
+
if (intervalMs === void 0 || this.#interval !== null) return;
|
|
152133
|
+
this.#interval = setInterval(() => {
|
|
152134
|
+
this.trigger("interval");
|
|
152135
|
+
}, intervalMs);
|
|
152136
|
+
}
|
|
152137
|
+
stop() {
|
|
152138
|
+
this.#stopped = true;
|
|
152139
|
+
if (this.#interval !== null) clearInterval(this.#interval);
|
|
152140
|
+
if (this.#followUp !== null) clearTimeout(this.#followUp);
|
|
152141
|
+
this.#interval = null;
|
|
152142
|
+
this.#followUp = null;
|
|
152143
|
+
this.#queued = null;
|
|
152144
|
+
}
|
|
152145
|
+
/**
|
|
152146
|
+
* Re-read now. Resolves `true` when the read landed. A trigger while one is in
|
|
152147
|
+
* flight does not start a second read: it queues one more run after it
|
|
152148
|
+
* (coalesced) and shares its promise.
|
|
152149
|
+
*/
|
|
152150
|
+
trigger(reason) {
|
|
152151
|
+
if (this.#stopped) return Promise.resolve(false);
|
|
152152
|
+
if (this.#running !== null) {
|
|
152153
|
+
this.#queued = reason;
|
|
152154
|
+
return this.#running;
|
|
152155
|
+
}
|
|
152156
|
+
const run = this.#drain(reason).finally(() => {
|
|
152157
|
+
this.#running = null;
|
|
152158
|
+
});
|
|
152159
|
+
this.#running = run;
|
|
152160
|
+
return run;
|
|
152161
|
+
}
|
|
152162
|
+
/** One re-read after `followUpMs`; a second call replaces the first. */
|
|
152163
|
+
scheduleFollowUp(reason) {
|
|
152164
|
+
if (this.#stopped) return;
|
|
152165
|
+
if (this.#followUp !== null) clearTimeout(this.#followUp);
|
|
152166
|
+
this.#followUp = setTimeout(() => {
|
|
152167
|
+
this.#followUp = null;
|
|
152168
|
+
if (!this.#stopped) this.trigger(reason);
|
|
152169
|
+
}, this.#deps.followUpMs ?? DEFAULT_FOLLOW_UP_MS);
|
|
152170
|
+
}
|
|
152171
|
+
async #drain(first) {
|
|
152172
|
+
let landed = await this.#once(first);
|
|
152173
|
+
while (this.#queued !== null && !this.#stopped) {
|
|
152174
|
+
const next = this.#queued;
|
|
152175
|
+
this.#queued = null;
|
|
152176
|
+
landed = await this.#once(next);
|
|
152177
|
+
}
|
|
152178
|
+
return landed;
|
|
152179
|
+
}
|
|
152180
|
+
async #once(reason) {
|
|
152181
|
+
const { deviceId, logger, subject, read } = this.#deps;
|
|
152182
|
+
try {
|
|
152183
|
+
await read(reason, this.live);
|
|
152184
|
+
if (this.#stopped) return false;
|
|
152185
|
+
logger.debug(`${subject}: consumables refreshed`, {
|
|
152186
|
+
tags: { deviceId },
|
|
152187
|
+
meta: { reason }
|
|
152188
|
+
});
|
|
152189
|
+
return true;
|
|
152190
|
+
} catch (err) {
|
|
152191
|
+
if (this.#stopped) return false;
|
|
152192
|
+
logger.warn(`${subject}: consumables refresh failed — keeping the last values`, {
|
|
152193
|
+
tags: { deviceId },
|
|
152194
|
+
meta: {
|
|
152195
|
+
reason,
|
|
152196
|
+
error: errMsg(err)
|
|
152197
|
+
}
|
|
152198
|
+
});
|
|
152199
|
+
return false;
|
|
152200
|
+
}
|
|
152201
|
+
}
|
|
152202
|
+
};
|
|
152203
|
+
//#endregion
|
|
152204
|
+
//#region src/devices/dreame-mower-consumables.ts
|
|
152205
|
+
/**
|
|
152206
|
+
* The mower's `consumables` slice (blade / brush / maintenance).
|
|
152207
|
+
*
|
|
152208
|
+
* The CMS counters are not cached MIoT props: they are read by a LIVE custom
|
|
152209
|
+
* action that answers only while the mower is awake — the instant it docks and
|
|
152210
|
+
* starts charging. A sleeping mower never answers, so there is no periodic
|
|
152211
|
+
* read: the triggers are activation (in case it is awake), the edge into
|
|
152212
|
+
* charging, and a reset.
|
|
152213
|
+
*
|
|
152214
|
+
* The slice is `restored` across restarts (D642), so the last reading is what
|
|
152215
|
+
* the panel shows while the mower sleeps. Nothing here may overwrite it with an
|
|
152216
|
+
* empty list: a failed, empty or missing answer is "nothing learned".
|
|
152217
|
+
*/
|
|
152218
|
+
/** Retries to ride the brief wake window when the mower transitions to charging. */
|
|
152219
|
+
var CONSUMABLES_WAKE_RETRIES = 2;
|
|
152220
|
+
/** Spacing between wake-window retries. */
|
|
152221
|
+
var CONSUMABLES_RETRY_DELAY_MS = 1500;
|
|
152222
|
+
var SUBJECT$1 = "dreame mower";
|
|
152223
|
+
var DreameMowerConsumables = class {
|
|
152224
|
+
#deps;
|
|
152225
|
+
#refresher;
|
|
152226
|
+
constructor(deps) {
|
|
152227
|
+
this.#deps = deps;
|
|
152228
|
+
this.#refresher = new DreameConsumablesRefresher({
|
|
152229
|
+
deviceId: deps.deviceId,
|
|
152230
|
+
logger: deps.logger,
|
|
152231
|
+
subject: SUBJECT$1,
|
|
152232
|
+
read: (_reason, live) => this.#read(live)
|
|
152233
|
+
});
|
|
152234
|
+
}
|
|
152235
|
+
/** Cold start only when nothing was restored. */
|
|
152236
|
+
seed() {
|
|
152237
|
+
seedConsumablesColdStart(this.#deps.store);
|
|
152238
|
+
}
|
|
152239
|
+
refresh(reason) {
|
|
152240
|
+
return this.#refresher.trigger(reason);
|
|
152241
|
+
}
|
|
152242
|
+
stop() {
|
|
152243
|
+
this.#refresher.stop();
|
|
152244
|
+
}
|
|
152245
|
+
/** Reset one counter, then re-read it; a failed re-read is logged by the refresher. */
|
|
152246
|
+
async reset(key) {
|
|
152247
|
+
const { deviceId, resolveMower } = this.#deps;
|
|
152248
|
+
if (!isMowerConsumableKey(key)) throw new Error(`${SUBJECT$1}: this device does not report "${key}" (device=${deviceId})`);
|
|
152249
|
+
const mower = resolveMower();
|
|
152250
|
+
if (mower === null) throw new Error(`${SUBJECT$1}: handle unavailable (device=${deviceId})`);
|
|
152251
|
+
await mower.resetConsumable(key);
|
|
152252
|
+
await this.#refresher.trigger("reset");
|
|
152253
|
+
}
|
|
152254
|
+
async #read(live) {
|
|
152255
|
+
const { resolveMower, store, now } = this.#deps;
|
|
152256
|
+
const retries = this.#deps.retries ?? CONSUMABLES_WAKE_RETRIES;
|
|
152257
|
+
const delayMs = this.#deps.retryDelayMs ?? CONSUMABLES_RETRY_DELAY_MS;
|
|
152258
|
+
let lastError = null;
|
|
152259
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
152260
|
+
if (attempt > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
152261
|
+
if (!live()) return;
|
|
152262
|
+
const mower = resolveMower();
|
|
152263
|
+
if (mower === null) throw new Error("mower handle unavailable");
|
|
152264
|
+
try {
|
|
152265
|
+
const readings = await mower.getConsumables();
|
|
152266
|
+
if (!live()) return;
|
|
152267
|
+
const items = readings === null ? [] : buildMowerConsumables(readings);
|
|
152268
|
+
if (items.length === 0) {
|
|
152269
|
+
lastError = /* @__PURE__ */ new Error("the CMS action returned no reading");
|
|
152270
|
+
continue;
|
|
152271
|
+
}
|
|
152272
|
+
writeConsumables(store, items, (now ?? Date.now)(), true);
|
|
152273
|
+
return;
|
|
152274
|
+
} catch (err) {
|
|
152275
|
+
lastError = err;
|
|
152276
|
+
}
|
|
152277
|
+
}
|
|
152278
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
152279
|
+
}
|
|
152280
|
+
};
|
|
152281
|
+
/**
|
|
152282
|
+
* The edge INTO the awake-and-charging window — `status == Charging` OR
|
|
152283
|
+
* `chargingStatus == Charging`, either marks it (mirrors the donor
|
|
152284
|
+
* integration). The first observation after boot is not an edge: activation
|
|
152285
|
+
* already reads, and a mower found charging would otherwise be read twice.
|
|
152286
|
+
*/
|
|
152287
|
+
var MowerChargingEdge = class {
|
|
152288
|
+
#observed = false;
|
|
152289
|
+
#prevStatus = null;
|
|
152290
|
+
#prevCharging = null;
|
|
152291
|
+
observe(status, charging) {
|
|
152292
|
+
const edge = this.#observed && (status === MowerStatus.Charging && this.#prevStatus !== MowerStatus.Charging || charging === MowerChargingStatus.Charging && this.#prevCharging !== MowerChargingStatus.Charging);
|
|
152293
|
+
this.#observed = true;
|
|
152294
|
+
this.#prevStatus = status;
|
|
152295
|
+
this.#prevCharging = charging;
|
|
152296
|
+
return edge;
|
|
152297
|
+
}
|
|
152298
|
+
};
|
|
152299
|
+
//#endregion
|
|
150989
152300
|
//#region src/devices/dreame-mower-device.ts
|
|
150990
152301
|
var CAP_NAME$9 = "lawn-mower-control";
|
|
150991
|
-
var CONSUMABLES_CAP_NAME$1 = "consumables";
|
|
150992
152302
|
/**
|
|
150993
152303
|
* The CMS consumable custom-action only answers while the mower is momentarily
|
|
150994
152304
|
* awake and ACTIVELY charging — which it is the instant it docks with a depleted
|
|
150995
152305
|
* battery. We ride that push (mirrors the donor `dreame-mower`
|
|
150996
|
-
* `_handle_device_update`: STATUS==CHARGING → refresh consumables)
|
|
150997
|
-
*
|
|
150998
|
-
*
|
|
150999
|
-
*
|
|
151000
|
-
*
|
|
151001
|
-
*
|
|
151002
|
-
*
|
|
152306
|
+
* `_handle_device_update`: STATUS==CHARGING → refresh consumables). A
|
|
152307
|
+
* fully-charged / "charging complete" / sleeping mower at base does NOT answer
|
|
152308
|
+
* synchronous cloud commands (verified: every cloud action 80001s "device
|
|
152309
|
+
* offline", even with patient backoff), so a periodic poll never works — it is
|
|
152310
|
+
* intentionally NOT used here. We trigger on the transition into EITHER
|
|
152311
|
+
* `status == Charging` OR `chargingStatus == Charging`, since both mark the
|
|
152312
|
+
* awake-and-charging window, to maximise the capture chance.
|
|
151003
152313
|
*/
|
|
151004
|
-
/** Retries to ride the brief wake window when the mower transitions to charging. */
|
|
151005
|
-
var CONSUMABLES_WAKE_RETRIES = 2;
|
|
151006
|
-
/** Spacing between wake-window retries. */
|
|
151007
|
-
var CONSUMABLES_RETRY_DELAY_MS = 1500;
|
|
151008
152314
|
/**
|
|
151009
152315
|
* Cloud-shadow (no-wake) refresh cadence for the mower's battery / charging /
|
|
151010
152316
|
* status props. The shadow read (`refreshFromCache` → `getCachedProperties`)
|
|
@@ -151023,11 +152329,6 @@ var COLD_START = {
|
|
|
151023
152329
|
severity: "info",
|
|
151024
152330
|
lastChangedAt: 0
|
|
151025
152331
|
};
|
|
151026
|
-
var CONSUMABLES_COLD_START$1 = {
|
|
151027
|
-
items: [],
|
|
151028
|
-
lastChangedAt: 0,
|
|
151029
|
-
lastFetchedAt: 0
|
|
151030
|
-
};
|
|
151031
152332
|
/**
|
|
151032
152333
|
* Dreame robotic lawn-mower device. Registers the `lawn-mower-control` and
|
|
151033
152334
|
* `consumables` caps and maps the library `MowerDevice` getters → the cap slice
|
|
@@ -151041,20 +152342,26 @@ var CONSUMABLES_COLD_START$1 = {
|
|
|
151041
152342
|
* Consumables (blade / brush / maintenance) are NOT cached MIoT props — they are
|
|
151042
152343
|
* read via a LIVE CMS custom-action (`getConsumables()`) that only answers while
|
|
151043
152344
|
* the mower is awake. We fetch best-effort on activate, on the charging push
|
|
151044
|
-
* transition (the wake window — see {@link recomputeSlice}), and after a
|
|
151045
|
-
*
|
|
151046
|
-
* mower sleeps
|
|
152345
|
+
* transition (the wake window — see {@link recomputeSlice}), and after a reset.
|
|
152346
|
+
* The `consumables` slice is `restored` across restarts, so the last good
|
|
152347
|
+
* reading is shown while the mower sleeps; a failed or empty read never
|
|
152348
|
+
* overwrites it (see `DreameMowerConsumables`).
|
|
151047
152349
|
*
|
|
151048
152350
|
* TODO: map data (`getMap()`/`mapSvg()`) is deferred — there is no camstack map
|
|
151049
152351
|
* cap, and the mower `getMap()` even requires an injected batch fetcher.
|
|
151050
152352
|
*/
|
|
151051
152353
|
var DreameMowerDevice = class extends DreameChildDevice {
|
|
151052
|
-
/**
|
|
151053
|
-
#
|
|
151054
|
-
/** Last seen `chargingStatus` value — second awake-and-charging trigger. */
|
|
151055
|
-
#prevCharging = null;
|
|
152354
|
+
/** The edge into the charging wake window (the first push after boot is not one). */
|
|
152355
|
+
#chargingEdge = new MowerChargingEdge();
|
|
151056
152356
|
/** Periodic cloud-shadow refresh timer (battery / charging / status). */
|
|
151057
152357
|
#shadowTimer = null;
|
|
152358
|
+
/** CMS consumables: read on activate / charging edge / reset, never overlapping. */
|
|
152359
|
+
#consumables = new DreameMowerConsumables({
|
|
152360
|
+
deviceId: this.id,
|
|
152361
|
+
logger: this.ctx.logger,
|
|
152362
|
+
store: this.runtimeState,
|
|
152363
|
+
resolveMower: () => this.resolveMower()
|
|
152364
|
+
});
|
|
151058
152365
|
constructor(ctx) {
|
|
151059
152366
|
super(ctx);
|
|
151060
152367
|
}
|
|
@@ -151078,18 +152385,16 @@ var DreameMowerDevice = class extends DreameChildDevice {
|
|
|
151078
152385
|
});
|
|
151079
152386
|
this.runtimeState.setCapState(CAP_NAME$9, COLD_START);
|
|
151080
152387
|
this.ctx.registerNativeCap(consumablesCapability, {
|
|
151081
|
-
getStatus: async () => this.runtimeState.getCapState(
|
|
152388
|
+
getStatus: async () => this.runtimeState.getCapState("consumables") ?? CONSUMABLES_COLD_START,
|
|
151082
152389
|
reset: async ({ key }) => {
|
|
151083
|
-
|
|
151084
|
-
await this.requireMower().resetConsumable(key);
|
|
151085
|
-
await this.refreshConsumables();
|
|
152390
|
+
await this.#consumables.reset(key);
|
|
151086
152391
|
}
|
|
151087
152392
|
});
|
|
151088
|
-
|
|
152393
|
+
this.#consumables.seed();
|
|
151089
152394
|
}
|
|
151090
152395
|
async onActivate() {
|
|
151091
152396
|
await super.onActivate();
|
|
151092
|
-
this.
|
|
152397
|
+
this.#consumables.refresh("activate");
|
|
151093
152398
|
this.#refreshShadow();
|
|
151094
152399
|
this.#shadowTimer = setInterval(() => {
|
|
151095
152400
|
this.#refreshShadow();
|
|
@@ -151100,6 +152405,7 @@ var DreameMowerDevice = class extends DreameChildDevice {
|
|
|
151100
152405
|
clearInterval(this.#shadowTimer);
|
|
151101
152406
|
this.#shadowTimer = null;
|
|
151102
152407
|
}
|
|
152408
|
+
this.#consumables.stop();
|
|
151103
152409
|
await super.removeDevice();
|
|
151104
152410
|
}
|
|
151105
152411
|
/**
|
|
@@ -151114,10 +152420,13 @@ var DreameMowerDevice = class extends DreameChildDevice {
|
|
|
151114
152420
|
try {
|
|
151115
152421
|
await mower.refreshFromCache();
|
|
151116
152422
|
} catch (err) {
|
|
151117
|
-
this.ctx.logger.debug("dreame mower: cloud-shadow refresh failed", {
|
|
151118
|
-
|
|
151119
|
-
|
|
151120
|
-
|
|
152423
|
+
this.ctx.logger.debug("dreame mower: cloud-shadow refresh failed", {
|
|
152424
|
+
tags: { deviceId: this.id },
|
|
152425
|
+
meta: {
|
|
152426
|
+
dreameDeviceId: this.dreameDeviceId,
|
|
152427
|
+
error: errMsg(err)
|
|
152428
|
+
}
|
|
152429
|
+
});
|
|
151121
152430
|
}
|
|
151122
152431
|
}
|
|
151123
152432
|
recomputeSlice() {
|
|
@@ -151137,42 +152446,7 @@ var DreameMowerDevice = class extends DreameChildDevice {
|
|
|
151137
152446
|
lastChangedAt: Date.now()
|
|
151138
152447
|
};
|
|
151139
152448
|
this.runtimeState.setCapState(CAP_NAME$9, slice);
|
|
151140
|
-
|
|
151141
|
-
if (status === MowerStatus.Charging && this.#prevStatus !== MowerStatus.Charging || charging === MowerChargingStatus.Charging && this.#prevCharging !== MowerChargingStatus.Charging) this.refreshConsumables(CONSUMABLES_WAKE_RETRIES);
|
|
151142
|
-
this.#prevStatus = status;
|
|
151143
|
-
this.#prevCharging = charging;
|
|
151144
|
-
}
|
|
151145
|
-
/**
|
|
151146
|
-
* Live-read the CMS consumable counters and write the `consumables` slice.
|
|
151147
|
-
* Best-effort: a sleeping/unreachable mower (or a malformed response) leaves
|
|
151148
|
-
* the previous (persisted) slice untouched and logs at debug. Retries a few
|
|
151149
|
-
* times with a short delay to ride the brief charging-transition wake window.
|
|
151150
|
-
*/
|
|
151151
|
-
async refreshConsumables(retries = 0) {
|
|
151152
|
-
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
151153
|
-
const mower = this.resolveMower();
|
|
151154
|
-
if (mower === null) return;
|
|
151155
|
-
try {
|
|
151156
|
-
const readings = await mower.getConsumables();
|
|
151157
|
-
if (readings === null) return;
|
|
151158
|
-
const slice = {
|
|
151159
|
-
items: [...buildMowerConsumables(readings)],
|
|
151160
|
-
lastChangedAt: Date.now(),
|
|
151161
|
-
lastFetchedAt: Date.now()
|
|
151162
|
-
};
|
|
151163
|
-
this.runtimeState.setCapState(CONSUMABLES_CAP_NAME$1, slice);
|
|
151164
|
-
return;
|
|
151165
|
-
} catch (err) {
|
|
151166
|
-
if (attempt === retries) {
|
|
151167
|
-
this.ctx.logger.debug("dreame mower: consumables fetch failed (asleep?)", { meta: {
|
|
151168
|
-
dreameDeviceId: this.dreameDeviceId,
|
|
151169
|
-
error: errMsg(err)
|
|
151170
|
-
} });
|
|
151171
|
-
return;
|
|
151172
|
-
}
|
|
151173
|
-
await new Promise((resolve) => setTimeout(resolve, CONSUMABLES_RETRY_DELAY_MS));
|
|
151174
|
-
}
|
|
151175
|
-
}
|
|
152449
|
+
if (this.#chargingEdge.observe(status, mower.charging)) this.#consumables.refresh("charging");
|
|
151176
152450
|
}
|
|
151177
152451
|
/** The live typed mower handle, or throw a clear error when disconnected. */
|
|
151178
152452
|
requireMower() {
|
|
@@ -151298,10 +152572,13 @@ var DreameMowerMapDevice = class extends DreameChildDevice {
|
|
|
151298
152572
|
const map = await mower.getMap();
|
|
151299
152573
|
this.#publishRender(map);
|
|
151300
152574
|
} catch (err) {
|
|
151301
|
-
this.ctx.logger.debug("dreame mower map: fetch/render failed", {
|
|
151302
|
-
|
|
151303
|
-
|
|
151304
|
-
|
|
152575
|
+
this.ctx.logger.debug("dreame mower map: fetch/render failed", {
|
|
152576
|
+
tags: { deviceId: this.id },
|
|
152577
|
+
meta: {
|
|
152578
|
+
dreameDeviceId: this.dreameDeviceId,
|
|
152579
|
+
error: errMsg(err)
|
|
152580
|
+
}
|
|
152581
|
+
});
|
|
151305
152582
|
} finally {
|
|
151306
152583
|
this.#inflight = false;
|
|
151307
152584
|
}
|
|
@@ -151368,10 +152645,13 @@ var DreameMowerMapSelectDevice = class extends DreameChildDevice {
|
|
|
151368
152645
|
try {
|
|
151369
152646
|
await mower.getMap();
|
|
151370
152647
|
} catch (err) {
|
|
151371
|
-
this.ctx.logger.debug("dreame mower map-select: map fetch failed", {
|
|
151372
|
-
|
|
151373
|
-
|
|
151374
|
-
|
|
152648
|
+
this.ctx.logger.debug("dreame mower map-select: map fetch failed", {
|
|
152649
|
+
tags: { deviceId: this.id },
|
|
152650
|
+
meta: {
|
|
152651
|
+
dreameDeviceId: this.dreameDeviceId,
|
|
152652
|
+
error: errMsg(err)
|
|
152653
|
+
}
|
|
152654
|
+
});
|
|
151375
152655
|
} finally {
|
|
151376
152656
|
this.#inflight = false;
|
|
151377
152657
|
this.#publishSlice();
|
|
@@ -151773,9 +153053,112 @@ var DreameSwitchDevice = class extends DreameChildDevice {
|
|
|
151773
153053
|
}
|
|
151774
153054
|
};
|
|
151775
153055
|
//#endregion
|
|
153056
|
+
//#region src/devices/dreame-vacuum-consumables.ts
|
|
153057
|
+
/** Hourly: a consumable moves by hours of cleaning, and the dock edge covers a run. */
|
|
153058
|
+
var CONSUMABLES_REFRESH_MS = 60 * 6e4;
|
|
153059
|
+
/** Time for the robot to report a reset to the cloud before the shadow is re-read. */
|
|
153060
|
+
var RESET_FOLLOW_UP_MS = 9e4;
|
|
153061
|
+
var SUBJECT = "dreame vacuum";
|
|
153062
|
+
/** The states a run ends from. `drying` is already on the dock. */
|
|
153063
|
+
var DOCK_EDGE_FROM = new Set([
|
|
153064
|
+
"cleaning",
|
|
153065
|
+
"returning",
|
|
153066
|
+
"paused"
|
|
153067
|
+
]);
|
|
153068
|
+
var DreameVacuumConsumables = class {
|
|
153069
|
+
#deps;
|
|
153070
|
+
#refresher;
|
|
153071
|
+
/** Last pushed lifecycle state; `null` until the first push after boot. */
|
|
153072
|
+
#prevState = null;
|
|
153073
|
+
constructor(deps) {
|
|
153074
|
+
this.#deps = deps;
|
|
153075
|
+
this.#refresher = new DreameConsumablesRefresher({
|
|
153076
|
+
deviceId: deps.deviceId,
|
|
153077
|
+
logger: deps.logger,
|
|
153078
|
+
subject: SUBJECT,
|
|
153079
|
+
read: (reason, live) => this.#read(reason, live),
|
|
153080
|
+
intervalMs: CONSUMABLES_REFRESH_MS,
|
|
153081
|
+
followUpMs: RESET_FOLLOW_UP_MS
|
|
153082
|
+
});
|
|
153083
|
+
}
|
|
153084
|
+
start() {
|
|
153085
|
+
this.#refresher.start();
|
|
153086
|
+
this.#refresher.trigger("activate");
|
|
153087
|
+
}
|
|
153088
|
+
stop() {
|
|
153089
|
+
this.#refresher.stop();
|
|
153090
|
+
}
|
|
153091
|
+
/**
|
|
153092
|
+
* Every `stateChanged` push: replay the cache into the slice (written only if
|
|
153093
|
+
* an item changed) and refresh on the edge into `docked`. The first push after
|
|
153094
|
+
* boot is not an edge — activation already reads — and neither is a hop
|
|
153095
|
+
* between station routines (`drying` → `docked`): only the end of a RUN is.
|
|
153096
|
+
*/
|
|
153097
|
+
onPush(state) {
|
|
153098
|
+
this.#apply(false);
|
|
153099
|
+
const enteredDock = this.#prevState !== null && DOCK_EDGE_FROM.has(this.#prevState) && state === "docked";
|
|
153100
|
+
this.#prevState = state;
|
|
153101
|
+
if (enteredDock) this.#refresher.trigger("docked");
|
|
153102
|
+
}
|
|
153103
|
+
async reset(key) {
|
|
153104
|
+
const { deviceId, logger, resolveVacuum } = this.#deps;
|
|
153105
|
+
const vacuum = resolveVacuum();
|
|
153106
|
+
if (vacuum === null) throw new Error(`${SUBJECT}: handle unavailable (device=${deviceId})`);
|
|
153107
|
+
const known = resolveResettableKey(key, vacuum.supportedConsumables);
|
|
153108
|
+
const result = await vacuum.resetConsumable(known);
|
|
153109
|
+
this.#apply(result.refreshedFrom !== null);
|
|
153110
|
+
logger.info(`${SUBJECT}: consumable reset`, {
|
|
153111
|
+
tags: { deviceId },
|
|
153112
|
+
meta: resetMeta(result)
|
|
153113
|
+
});
|
|
153114
|
+
if (result.refreshedFrom === "device") return;
|
|
153115
|
+
if (result.refreshedFrom === null) logger.warn(`${SUBJECT}: consumable reset, but its values could not be re-read`, {
|
|
153116
|
+
tags: { deviceId },
|
|
153117
|
+
meta: resetMeta(result)
|
|
153118
|
+
});
|
|
153119
|
+
this.#refresher.scheduleFollowUp("reset-follow-up");
|
|
153120
|
+
}
|
|
153121
|
+
async #read(reason, live) {
|
|
153122
|
+
const { deviceId, logger, resolveVacuum } = this.#deps;
|
|
153123
|
+
const vacuum = resolveVacuum();
|
|
153124
|
+
if (vacuum === null) throw new Error("vacuum handle unavailable");
|
|
153125
|
+
await vacuum.refreshConsumables();
|
|
153126
|
+
if (!live()) return;
|
|
153127
|
+
if (vacuum.supportedConsumables.length === 0) throw new Error("the shadow reported no consumable");
|
|
153128
|
+
this.#apply(true);
|
|
153129
|
+
try {
|
|
153130
|
+
await vacuum.refreshCachedProperties(Object.values(VACUUM_TANK_PROPS).map((p) => ({ ...p })));
|
|
153131
|
+
} catch (err) {
|
|
153132
|
+
if (!live()) return;
|
|
153133
|
+
logger.warn(`${SUBJECT}: tank-status refresh failed — keeping the last values`, {
|
|
153134
|
+
tags: { deviceId },
|
|
153135
|
+
meta: {
|
|
153136
|
+
reason,
|
|
153137
|
+
error: errMsg(err)
|
|
153138
|
+
}
|
|
153139
|
+
});
|
|
153140
|
+
}
|
|
153141
|
+
}
|
|
153142
|
+
#apply(landed) {
|
|
153143
|
+
if (!this.#refresher.live()) return;
|
|
153144
|
+
const { store, resolveVacuum, now } = this.#deps;
|
|
153145
|
+
const vacuum = resolveVacuum();
|
|
153146
|
+
if (vacuum === null) return;
|
|
153147
|
+
writeConsumables(store, buildVacuumConsumableItems(vacuum.supportedConsumables), (now ?? Date.now)(), landed);
|
|
153148
|
+
}
|
|
153149
|
+
};
|
|
153150
|
+
function resetMeta(result) {
|
|
153151
|
+
return {
|
|
153152
|
+
key: result.key,
|
|
153153
|
+
refreshedFrom: result.refreshedFrom,
|
|
153154
|
+
leftPct: result.leftPct,
|
|
153155
|
+
timeLeft: result.timeLeft,
|
|
153156
|
+
...result.refreshError !== null ? { refreshError: result.refreshError.message } : {}
|
|
153157
|
+
};
|
|
153158
|
+
}
|
|
153159
|
+
//#endregion
|
|
151776
153160
|
//#region src/devices/dreame-vacuum-device.ts
|
|
151777
153161
|
var CAP_NAME$1 = "vacuum-control";
|
|
151778
|
-
var CONSUMABLES_CAP_NAME = "consumables";
|
|
151779
153162
|
var CONTROL_COLD_START = {
|
|
151780
153163
|
state: "idle",
|
|
151781
153164
|
batteryLevel: null,
|
|
@@ -151790,11 +153173,6 @@ var CONTROL_COLD_START = {
|
|
|
151790
153173
|
errorLabel: null,
|
|
151791
153174
|
lastChangedAt: 0
|
|
151792
153175
|
};
|
|
151793
|
-
var CONSUMABLES_COLD_START = {
|
|
151794
|
-
items: [],
|
|
151795
|
-
lastChangedAt: 0,
|
|
151796
|
-
lastFetchedAt: 0
|
|
151797
|
-
};
|
|
151798
153176
|
/**
|
|
151799
153177
|
* Dreame robot-vacuum device. Registers the `vacuum-control` and `consumables`
|
|
151800
153178
|
* caps and maps the library `VacuumDevice` getters → the cap slices on every
|
|
@@ -151806,17 +153184,16 @@ var CONSUMABLES_COLD_START = {
|
|
|
151806
153184
|
* suction levels).
|
|
151807
153185
|
*
|
|
151808
153186
|
* Tank state (cleanWater/dirtyWater/detergent/dustBin): the library exposes NO
|
|
151809
|
-
* tank fill
|
|
151810
|
-
* `
|
|
151811
|
-
*
|
|
151812
|
-
*
|
|
151813
|
-
*
|
|
151814
|
-
*
|
|
151815
|
-
*
|
|
151816
|
-
*
|
|
151817
|
-
*
|
|
151818
|
-
*
|
|
151819
|
-
* no-op that throws.
|
|
153187
|
+
* tank fill LEVEL, so `level` is always null. PRESENCE comes from the model
|
|
153188
|
+
* capability flags and a discrete `status` from the station's tank-status enum
|
|
153189
|
+
* props (`decodeVacuumTanks`, ids in `VACUUM_TANK_PROPS`); an absent tank is
|
|
153190
|
+
* `null`, an unknown status is `null` — never `ok`.
|
|
153191
|
+
*
|
|
153192
|
+
* Consumables: whatever the model REPORTS (the library's presence-driven
|
|
153193
|
+
* `supportedConsumables`), re-read from the cloud shadow on activate, hourly and
|
|
153194
|
+
* on the edge into docked. `reset()` accepts only a key this robot reports and
|
|
153195
|
+
* can reset, drives its MIoT reset action, and rebuilds the slice from the
|
|
153196
|
+
* library's post-reset re-read (see `DreameVacuumConsumables`).
|
|
151820
153197
|
*
|
|
151821
153198
|
* Map data is NOT on this device: the cleaning map is surfaced as a separate
|
|
151822
153199
|
* `image` child (`DreameVacuumMapDevice`) grouped under the same Container, per
|
|
@@ -151834,25 +153211,33 @@ var DreameVacuumDevice = class extends DreameChildDevice {
|
|
|
151834
153211
|
const vacuum = this.resolveVacuum();
|
|
151835
153212
|
if (vacuum !== null) {
|
|
151836
153213
|
vacuum.refreshFromCache().catch((err) => {
|
|
151837
|
-
this.ctx.logger.debug("dreame vacuum: state seed failed", {
|
|
151838
|
-
|
|
151839
|
-
|
|
151840
|
-
|
|
153214
|
+
this.ctx.logger.debug("dreame vacuum: state seed failed", {
|
|
153215
|
+
tags: { deviceId: this.id },
|
|
153216
|
+
meta: {
|
|
153217
|
+
dreameDeviceId: this.dreameDeviceId,
|
|
153218
|
+
error: errMsg(err)
|
|
153219
|
+
}
|
|
153220
|
+
});
|
|
151841
153221
|
});
|
|
151842
153222
|
vacuum.refreshAiDetection().catch((err) => {
|
|
151843
|
-
this.ctx.logger.debug("dreame vacuum: AI_DETECTION seed failed", {
|
|
151844
|
-
|
|
151845
|
-
|
|
151846
|
-
|
|
151847
|
-
|
|
151848
|
-
|
|
151849
|
-
|
|
151850
|
-
dreameDeviceId: this.dreameDeviceId,
|
|
151851
|
-
error: errMsg(err)
|
|
151852
|
-
} });
|
|
153223
|
+
this.ctx.logger.debug("dreame vacuum: AI_DETECTION seed failed", {
|
|
153224
|
+
tags: { deviceId: this.id },
|
|
153225
|
+
meta: {
|
|
153226
|
+
dreameDeviceId: this.dreameDeviceId,
|
|
153227
|
+
error: errMsg(err)
|
|
153228
|
+
}
|
|
153229
|
+
});
|
|
151853
153230
|
});
|
|
151854
153231
|
}
|
|
153232
|
+
this.consumables.start();
|
|
151855
153233
|
}
|
|
153234
|
+
/** Consumables refresh + reset — see `DreameVacuumConsumables`. */
|
|
153235
|
+
consumables = new DreameVacuumConsumables({
|
|
153236
|
+
deviceId: this.id,
|
|
153237
|
+
logger: this.ctx.logger,
|
|
153238
|
+
store: this.runtimeState,
|
|
153239
|
+
resolveVacuum: () => this.resolveVacuum()
|
|
153240
|
+
});
|
|
151856
153241
|
/** Narrow the generic handle to the library's typed {@link DreameVacuum}, or null. */
|
|
151857
153242
|
/** The robot's own link — one reader shared with the camera child. */
|
|
151858
153243
|
networkLink = new DreameNetworkLinkMonitor({
|
|
@@ -151871,6 +153256,7 @@ var DreameVacuumDevice = class extends DreameChildDevice {
|
|
|
151871
153256
|
}
|
|
151872
153257
|
async removeDevice() {
|
|
151873
153258
|
this.networkLink.stop();
|
|
153259
|
+
this.consumables.stop();
|
|
151874
153260
|
await super.removeDevice();
|
|
151875
153261
|
}
|
|
151876
153262
|
resolveVacuum() {
|
|
@@ -151909,30 +153295,23 @@ var DreameVacuumDevice = class extends DreameChildDevice {
|
|
|
151909
153295
|
/**
|
|
151910
153296
|
* Register the `consumables` cap. The provider surfaces remaining life % for
|
|
151911
153297
|
* every consumable the model REPORTS (presence-driven via the library's
|
|
151912
|
-
* `supportedConsumables`)
|
|
151913
|
-
* action
|
|
151914
|
-
*
|
|
153298
|
+
* `supportedConsumables`); `reset()` drives the consumable's real MIoT reset
|
|
153299
|
+
* action. Items whose model has no reset action report `resettable: false`
|
|
153300
|
+
* (the UI suppresses the button, and `reset()` refuses them).
|
|
151915
153301
|
*/
|
|
151916
153302
|
registerConsumablesCap() {
|
|
151917
153303
|
this.ctx.registerNativeCap(consumablesCapability, {
|
|
151918
|
-
getStatus: async () => this.runtimeState.getCapState(
|
|
153304
|
+
getStatus: async () => this.runtimeState.getCapState("consumables") ?? CONSUMABLES_COLD_START,
|
|
151919
153305
|
reset: async ({ key }) => {
|
|
151920
|
-
|
|
151921
|
-
await this.requireVacuum().resetConsumable(key);
|
|
151922
|
-
this.recomputeSlice();
|
|
153306
|
+
await this.consumables.reset(key);
|
|
151923
153307
|
}
|
|
151924
153308
|
});
|
|
151925
|
-
this.runtimeState
|
|
153309
|
+
seedConsumablesColdStart(this.runtimeState);
|
|
151926
153310
|
}
|
|
151927
|
-
/**
|
|
151928
|
-
* `registerControlCap()` (called by `super.onActivate()`) already registers
|
|
151929
|
-
* the consumables cap before the parent seeds the first slice via
|
|
151930
|
-
* `recomputeSlice()`. No additional work needed here; the override is removed.
|
|
151931
|
-
*/
|
|
151932
153311
|
recomputeSlice() {
|
|
151933
153312
|
const vacuum = this.resolveVacuum();
|
|
151934
153313
|
if (vacuum === null) return;
|
|
151935
|
-
const tanks = decodeVacuumTanks(vacuum.vacuumCapabilities, this.readTankProps());
|
|
153314
|
+
const tanks = decodeVacuumTanks(vacuum.vacuumCapabilities, this.readTankProps(), vacuum.errorCode);
|
|
151936
153315
|
const controlSlice = {
|
|
151937
153316
|
state: mapMiotStateToVacuumState(vacuum.status),
|
|
151938
153317
|
batteryLevel: clampBatteryLevel$1(vacuum.battery),
|
|
@@ -151947,27 +153326,29 @@ var DreameVacuumDevice = class extends DreameChildDevice {
|
|
|
151947
153326
|
errorLabel: vacuum.errorCode ? `Error ${vacuum.errorCode}` : null,
|
|
151948
153327
|
lastChangedAt: Date.now()
|
|
151949
153328
|
};
|
|
151950
|
-
this.runtimeState.
|
|
151951
|
-
|
|
151952
|
-
|
|
153329
|
+
const toWrite = nextVacuumControlSlice(this.runtimeState.getCapState(CAP_NAME$1), controlSlice);
|
|
153330
|
+
if (toWrite !== null) this.runtimeState.setCapState(CAP_NAME$1, toWrite);
|
|
153331
|
+
this.consumables.onPush(controlSlice.state);
|
|
151953
153332
|
}
|
|
151954
153333
|
/**
|
|
151955
|
-
* Read the
|
|
151956
|
-
* {@link decodeVacuumTanks}. Each is `null` when the device
|
|
151957
|
-
* property (or a non-numeric value), so a model without these
|
|
151958
|
-
* falls back to presence-only tanks.
|
|
153334
|
+
* Read the tank-status MIoT enum props ({@link VACUUM_TANK_PROPS}) from the
|
|
153335
|
+
* live handle for {@link decodeVacuumTanks}. Each is `null` when the device
|
|
153336
|
+
* reports no such property (or a non-numeric value), so a model without these
|
|
153337
|
+
* props simply falls back to presence-only tanks.
|
|
151959
153338
|
*/
|
|
151960
153339
|
readTankProps() {
|
|
151961
153340
|
const vacuum = this.resolveVacuum();
|
|
151962
|
-
const readEnum = (
|
|
151963
|
-
const value = vacuum?.getProperty(siid, piid)?.value;
|
|
153341
|
+
const readEnum = (prop) => {
|
|
153342
|
+
const value = vacuum?.getProperty(prop.siid, prop.piid)?.value;
|
|
151964
153343
|
return typeof value === "number" ? value : null;
|
|
151965
153344
|
};
|
|
151966
153345
|
return {
|
|
151967
|
-
cleanWaterTankStatus: readEnum(
|
|
151968
|
-
waterTank: readEnum(
|
|
151969
|
-
detergentStatus: readEnum(
|
|
151970
|
-
dustCollection: readEnum(
|
|
153346
|
+
cleanWaterTankStatus: readEnum(VACUUM_TANK_PROPS.cleanWaterTankStatus),
|
|
153347
|
+
waterTank: readEnum(VACUUM_TANK_PROPS.waterTank),
|
|
153348
|
+
detergentStatus: readEnum(VACUUM_TANK_PROPS.detergentStatus),
|
|
153349
|
+
dustCollection: readEnum(VACUUM_TANK_PROPS.dustCollection),
|
|
153350
|
+
dirtyWaterTankStatus: readEnum(VACUUM_TANK_PROPS.dirtyWaterTankStatus),
|
|
153351
|
+
dustBagStatus: readEnum(VACUUM_TANK_PROPS.dustBagStatus)
|
|
151971
153352
|
};
|
|
151972
153353
|
}
|
|
151973
153354
|
/** The live typed vacuum handle, or throw a clear error when disconnected. */
|
|
@@ -152016,10 +153397,13 @@ var DreameVacuumMapDevice = class extends DreameChildDevice {
|
|
|
152016
153397
|
await super.onActivate();
|
|
152017
153398
|
const vacuum = this.resolveVacuum();
|
|
152018
153399
|
if (vacuum !== null) vacuum.refreshSavedMapFilename().catch((err) => {
|
|
152019
|
-
this.ctx.logger.debug("dreame map: saved-map seed failed", {
|
|
152020
|
-
|
|
152021
|
-
|
|
152022
|
-
|
|
153400
|
+
this.ctx.logger.debug("dreame map: saved-map seed failed", {
|
|
153401
|
+
tags: { deviceId: this.id },
|
|
153402
|
+
meta: {
|
|
153403
|
+
dreameDeviceId: this.dreameDeviceId,
|
|
153404
|
+
error: errMsg(err)
|
|
153405
|
+
}
|
|
153406
|
+
});
|
|
152023
153407
|
});
|
|
152024
153408
|
}
|
|
152025
153409
|
/** Narrow the generic handle to the library's typed {@link DreameVacuum}, or null. */
|
|
@@ -152048,10 +153432,13 @@ var DreameVacuumMapDevice = class extends DreameChildDevice {
|
|
|
152048
153432
|
if (map === null) return;
|
|
152049
153433
|
this.#publishRender(map);
|
|
152050
153434
|
} catch (err) {
|
|
152051
|
-
this.ctx.logger.warn("dreame map: fetch/render failed", {
|
|
152052
|
-
|
|
152053
|
-
|
|
152054
|
-
|
|
153435
|
+
this.ctx.logger.warn("dreame map: fetch/render failed", {
|
|
153436
|
+
tags: { deviceId: this.id },
|
|
153437
|
+
meta: {
|
|
153438
|
+
dreameDeviceId: this.dreameDeviceId,
|
|
153439
|
+
error: errMsg(err)
|
|
153440
|
+
}
|
|
153441
|
+
});
|
|
152055
153442
|
} finally {
|
|
152056
153443
|
this.#inflight = false;
|
|
152057
153444
|
}
|
|
@@ -152409,10 +153796,13 @@ var DreameContainerDevice = class extends BaseDevice {
|
|
|
152409
153796
|
getAccessoryChildren() {
|
|
152410
153797
|
const kind = classifyDreameModel(this.model);
|
|
152411
153798
|
if (kind === "unsupported") {
|
|
152412
|
-
this.ctx.logger.warn("DreameContainerDevice: unsupported model; no children", {
|
|
152413
|
-
|
|
152414
|
-
|
|
152415
|
-
|
|
153799
|
+
this.ctx.logger.warn("DreameContainerDevice: unsupported model; no children", {
|
|
153800
|
+
tags: { deviceId: this.id },
|
|
153801
|
+
meta: {
|
|
153802
|
+
model: this.model,
|
|
153803
|
+
dreameDeviceId: this.dreameDeviceId
|
|
153804
|
+
}
|
|
153805
|
+
});
|
|
152416
153806
|
return [];
|
|
152417
153807
|
}
|
|
152418
153808
|
const meta = {
|
|
@@ -152875,10 +154265,13 @@ var DreameContainerDevice = class extends BaseDevice {
|
|
|
152875
154265
|
childLayout
|
|
152876
154266
|
});
|
|
152877
154267
|
} catch (err) {
|
|
152878
|
-
this.ctx.logger.warn("dreame container: failed to persist childLayout", {
|
|
152879
|
-
|
|
152880
|
-
|
|
152881
|
-
|
|
154268
|
+
this.ctx.logger.warn("dreame container: failed to persist childLayout", {
|
|
154269
|
+
tags: { deviceId: this.id },
|
|
154270
|
+
meta: {
|
|
154271
|
+
dreameDeviceId: this.dreameDeviceId,
|
|
154272
|
+
error: errMsg(err)
|
|
154273
|
+
}
|
|
154274
|
+
});
|
|
152882
154275
|
}
|
|
152883
154276
|
}
|
|
152884
154277
|
/** The AI-obstacle features the live model exposes — empty when the handle is
|