@camstack/addon-provider-homeassistant 1.2.78 → 1.2.80
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 +1 -1
- package/dist/addon.mjs +1 -1
- package/dist/{dist-CQzCvoCx.mjs → dist-BpePMREY.mjs} +48 -1
- package/dist/{dist-gv0Gmrm1.js → dist-HZncHwuU.js} +48 -1
- package/dist/ha-export/ha-export.addon.js +62 -29
- package/dist/ha-export/ha-export.addon.mjs +62 -29
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_dist = require("./dist-
|
|
2
|
+
const require_dist = require("./dist-HZncHwuU.js");
|
|
3
3
|
let node_crypto = require("node:crypto");
|
|
4
4
|
let node_zlib = require("node:zlib");
|
|
5
5
|
//#region src/ha-device-source.ts
|
package/dist/addon.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as presenceCapability, A as controlCapability, At as EventCategory, B as humiditySensorCapability, C as buildAddonRouteProvider, Ct as number, D as colorCapability, Dt as union, E as climateControlCapability, Et as string, F as eventEmitterCapability, G as motionCapability, H as lawnMowerControlCapability, I as fanControlCapability, J as notifierCapability, K as normalizeUnit, L as floodCapability, M as deviceAdoptionCapability, O as connectivityCapability, Ot as unknown, P as enumSensorCapability, Q as prepareNotification, R as gasCapability, S as brokerCapability, St as literal, T as carbonMonoxideCapability, Tt as record, U as lockControlCapability, V as imageCapability, W as mediaPlayerCapability, Y as numericSensorCapability, Z as powerMeterCapability, _ as automationControlCapability, _t as createEvent, at as temperatureSensorCapability, b as binaryCapability, bt as array, ct as valveCapability, d as TargetSchema, dt as weatherCapability, et as pressureSensorCapability, f as accessoriesCapability, ft as errMsg, g as ambientLightSensorCapability, gt as DeviceType, h as alarmPanelCapability, ht as DeviceRole, it as tamperCapability, j as coverCapability, k as contactCapability, kt as url, lt as vibrationCapability, m as airQualitySensorCapability, mt as DeviceFeature, n as BaseDevice, nt as smokeCapability, ot as updateCapability, p as addonRoutesCapability, q as notificationOutputCapability, r as BaseDeviceProvider, rt as switchCapability, s as EnumSensorDateTimeFormatSchema, st as vacuumControlCapability, tt as scriptRunnerCapability, ut as waterHeaterCapability, v as batteryCapability, w as buttonCapability, wt as object, x as brightnessCapability, xt as discriminatedUnion, y as bestLocationMatch, yt as _enum, z as humidifierCapability } from "./dist-
|
|
1
|
+
import { $ as presenceCapability, A as controlCapability, At as EventCategory, B as humiditySensorCapability, C as buildAddonRouteProvider, Ct as number, D as colorCapability, Dt as union, E as climateControlCapability, Et as string, F as eventEmitterCapability, G as motionCapability, H as lawnMowerControlCapability, I as fanControlCapability, J as notifierCapability, K as normalizeUnit, L as floodCapability, M as deviceAdoptionCapability, O as connectivityCapability, Ot as unknown, P as enumSensorCapability, Q as prepareNotification, R as gasCapability, S as brokerCapability, St as literal, T as carbonMonoxideCapability, Tt as record, U as lockControlCapability, V as imageCapability, W as mediaPlayerCapability, Y as numericSensorCapability, Z as powerMeterCapability, _ as automationControlCapability, _t as createEvent, at as temperatureSensorCapability, b as binaryCapability, bt as array, ct as valveCapability, d as TargetSchema, dt as weatherCapability, et as pressureSensorCapability, f as accessoriesCapability, ft as errMsg, g as ambientLightSensorCapability, gt as DeviceType, h as alarmPanelCapability, ht as DeviceRole, it as tamperCapability, j as coverCapability, k as contactCapability, kt as url, lt as vibrationCapability, m as airQualitySensorCapability, mt as DeviceFeature, n as BaseDevice, nt as smokeCapability, ot as updateCapability, p as addonRoutesCapability, q as notificationOutputCapability, r as BaseDeviceProvider, rt as switchCapability, s as EnumSensorDateTimeFormatSchema, st as vacuumControlCapability, tt as scriptRunnerCapability, ut as waterHeaterCapability, v as batteryCapability, w as buttonCapability, wt as object, x as brightnessCapability, xt as discriminatedUnion, y as bestLocationMatch, yt as _enum, z as humidifierCapability } from "./dist-BpePMREY.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib";
|
|
4
4
|
//#region src/ha-device-source.ts
|
|
@@ -13285,6 +13285,24 @@ var deviceProviderCapability = {
|
|
|
13285
13285
|
})
|
|
13286
13286
|
}
|
|
13287
13287
|
};
|
|
13288
|
+
/**
|
|
13289
|
+
* Device Manager capability — hub-side singleton that unifies device persistence,
|
|
13290
|
+
* live registry access, and all management operations into a single tRPC surface.
|
|
13291
|
+
*
|
|
13292
|
+
* Replaces:
|
|
13293
|
+
* - `device-persistence` capability (persistence methods absorbed here)
|
|
13294
|
+
* - `device-management.router.ts` (deleted in Phase 2)
|
|
13295
|
+
* - `device-ops.router.ts` (compat layer — deleted; device-provider ops absorbed here)
|
|
13296
|
+
*
|
|
13297
|
+
* All device provider addons (rtsp, onvif, frigate, …) are hub-local: they may
|
|
13298
|
+
* fork into separate processes but never run on remote cluster agents. Therefore:
|
|
13299
|
+
* - No nodeId routing needed — this is a pure hub singleton.
|
|
13300
|
+
* - The hub's DeviceRegistry is the single source of truth for all live devices.
|
|
13301
|
+
* - No shadow registry or cross-node aggregation required.
|
|
13302
|
+
*
|
|
13303
|
+
* Forked workers register devices back to the hub via `ctx.devices`
|
|
13304
|
+
* (DeviceManagerApi → ctx.api.deviceManager.registerDevice), same as today.
|
|
13305
|
+
*/
|
|
13288
13306
|
/** One child-placement directive on a container's `childLayout`. Structurally
|
|
13289
13307
|
* identical to `ChildLayoutEntry` in `device-management.ts` — the cap wire
|
|
13290
13308
|
* shape for the same field. The child is identified by its re-sync-stable
|
|
@@ -13383,7 +13401,12 @@ var ConfigEntrySchema = object({
|
|
|
13383
13401
|
value: unknown(),
|
|
13384
13402
|
description: string().optional()
|
|
13385
13403
|
});
|
|
13386
|
-
|
|
13404
|
+
/**
|
|
13405
|
+
* Only `manual` since D356: the operator picks, nothing links itself. The
|
|
13406
|
+
* field survives on the wire because a viewer already OTA'd parses it —
|
|
13407
|
+
* drop it once no shipped client reads `mode`.
|
|
13408
|
+
*/
|
|
13409
|
+
var LinkedDevicesModeSchema = _enum(["manual"]);
|
|
13387
13410
|
/** One resolved linked device — the compact projection consumers need. */
|
|
13388
13411
|
var LinkedDeviceSchema = object({
|
|
13389
13412
|
deviceId: number(),
|
|
@@ -19041,6 +19064,9 @@ var RetrainStatusSchema = _enum([
|
|
|
19041
19064
|
*
|
|
19042
19065
|
* `debug` does NOT pin; it is attention, not durability.
|
|
19043
19066
|
*
|
|
19067
|
+
* `debugNote` is the operator's own words about WHAT should be checked on this
|
|
19068
|
+
* track, and it lives and dies with `debug` — see {@link MAX_TRACK_DEBUG_NOTE_LEN}.
|
|
19069
|
+
*
|
|
19044
19070
|
* `favourited` IS a BOOLEAN pin (durability bit), not a retrain lifecycle.
|
|
19045
19071
|
* A favourited track is skipped by retention the same way `staging` is, but
|
|
19046
19072
|
* it does not enter `none|staging|trained` and has no staging budget.
|
|
@@ -19051,6 +19077,21 @@ var TrackFlagFields = {
|
|
|
19051
19077
|
markForTrain: boolean().optional(),
|
|
19052
19078
|
/** Operator marked this track for diagnostic attention. */
|
|
19053
19079
|
debug: boolean().optional(),
|
|
19080
|
+
/**
|
|
19081
|
+
* What the operator wants CHECKED on this track, in their own words.
|
|
19082
|
+
*
|
|
19083
|
+
* The flag alone says "look at this" and nothing about what for; a debug set
|
|
19084
|
+
* reviewed hours later is a list of tracks with no question attached. Bound
|
|
19085
|
+
* to `debug` on the WRITE side — the body clears it when `debug` goes off,
|
|
19086
|
+
* and refuses it on a track whose debug is off — so a note can never outlive
|
|
19087
|
+
* the attention it belongs to.
|
|
19088
|
+
*
|
|
19089
|
+
* `''` is a real value ("marked, nothing to add"); the ABSENT key means
|
|
19090
|
+
* "leave whatever is stored alone", which is what lets a surface turn debug
|
|
19091
|
+
* on without a note (a cancelled prompt) and what lets it edit the note
|
|
19092
|
+
* without touching the flag.
|
|
19093
|
+
*/
|
|
19094
|
+
debugNote: string().max(500).optional(),
|
|
19054
19095
|
/** Operator favourited this track. Pins it against pruning. */
|
|
19055
19096
|
favourited: boolean().optional()
|
|
19056
19097
|
};
|
|
@@ -19077,6 +19118,12 @@ var TrackFlagsSchema = object({
|
|
|
19077
19118
|
trackId: string(),
|
|
19078
19119
|
markForTrain: boolean(),
|
|
19079
19120
|
debug: boolean(),
|
|
19121
|
+
/** The note as it STANDS after the write — never absent here (a track with no
|
|
19122
|
+
* note reports `''`), for the same reason the booleans are required: a
|
|
19123
|
+
* surface that has just written must be able to render the note it now owns
|
|
19124
|
+
* without a re-fetch, and an absent key would read as "unchanged" on a
|
|
19125
|
+
* screen that has no previous value to keep. */
|
|
19126
|
+
debugNote: string(),
|
|
19080
19127
|
favourited: boolean(),
|
|
19081
19128
|
/** The lifecycle state the boolean was derived from. Required here (unlike on
|
|
19082
19129
|
* a track row) because this shape is only ever produced by the write body,
|
|
@@ -13285,6 +13285,24 @@ var deviceProviderCapability = {
|
|
|
13285
13285
|
})
|
|
13286
13286
|
}
|
|
13287
13287
|
};
|
|
13288
|
+
/**
|
|
13289
|
+
* Device Manager capability — hub-side singleton that unifies device persistence,
|
|
13290
|
+
* live registry access, and all management operations into a single tRPC surface.
|
|
13291
|
+
*
|
|
13292
|
+
* Replaces:
|
|
13293
|
+
* - `device-persistence` capability (persistence methods absorbed here)
|
|
13294
|
+
* - `device-management.router.ts` (deleted in Phase 2)
|
|
13295
|
+
* - `device-ops.router.ts` (compat layer — deleted; device-provider ops absorbed here)
|
|
13296
|
+
*
|
|
13297
|
+
* All device provider addons (rtsp, onvif, frigate, …) are hub-local: they may
|
|
13298
|
+
* fork into separate processes but never run on remote cluster agents. Therefore:
|
|
13299
|
+
* - No nodeId routing needed — this is a pure hub singleton.
|
|
13300
|
+
* - The hub's DeviceRegistry is the single source of truth for all live devices.
|
|
13301
|
+
* - No shadow registry or cross-node aggregation required.
|
|
13302
|
+
*
|
|
13303
|
+
* Forked workers register devices back to the hub via `ctx.devices`
|
|
13304
|
+
* (DeviceManagerApi → ctx.api.deviceManager.registerDevice), same as today.
|
|
13305
|
+
*/
|
|
13288
13306
|
/** One child-placement directive on a container's `childLayout`. Structurally
|
|
13289
13307
|
* identical to `ChildLayoutEntry` in `device-management.ts` — the cap wire
|
|
13290
13308
|
* shape for the same field. The child is identified by its re-sync-stable
|
|
@@ -13383,7 +13401,12 @@ var ConfigEntrySchema = object({
|
|
|
13383
13401
|
value: unknown(),
|
|
13384
13402
|
description: string().optional()
|
|
13385
13403
|
});
|
|
13386
|
-
|
|
13404
|
+
/**
|
|
13405
|
+
* Only `manual` since D356: the operator picks, nothing links itself. The
|
|
13406
|
+
* field survives on the wire because a viewer already OTA'd parses it —
|
|
13407
|
+
* drop it once no shipped client reads `mode`.
|
|
13408
|
+
*/
|
|
13409
|
+
var LinkedDevicesModeSchema = _enum(["manual"]);
|
|
13387
13410
|
/** One resolved linked device — the compact projection consumers need. */
|
|
13388
13411
|
var LinkedDeviceSchema = object({
|
|
13389
13412
|
deviceId: number(),
|
|
@@ -19041,6 +19064,9 @@ var RetrainStatusSchema = _enum([
|
|
|
19041
19064
|
*
|
|
19042
19065
|
* `debug` does NOT pin; it is attention, not durability.
|
|
19043
19066
|
*
|
|
19067
|
+
* `debugNote` is the operator's own words about WHAT should be checked on this
|
|
19068
|
+
* track, and it lives and dies with `debug` — see {@link MAX_TRACK_DEBUG_NOTE_LEN}.
|
|
19069
|
+
*
|
|
19044
19070
|
* `favourited` IS a BOOLEAN pin (durability bit), not a retrain lifecycle.
|
|
19045
19071
|
* A favourited track is skipped by retention the same way `staging` is, but
|
|
19046
19072
|
* it does not enter `none|staging|trained` and has no staging budget.
|
|
@@ -19051,6 +19077,21 @@ var TrackFlagFields = {
|
|
|
19051
19077
|
markForTrain: boolean().optional(),
|
|
19052
19078
|
/** Operator marked this track for diagnostic attention. */
|
|
19053
19079
|
debug: boolean().optional(),
|
|
19080
|
+
/**
|
|
19081
|
+
* What the operator wants CHECKED on this track, in their own words.
|
|
19082
|
+
*
|
|
19083
|
+
* The flag alone says "look at this" and nothing about what for; a debug set
|
|
19084
|
+
* reviewed hours later is a list of tracks with no question attached. Bound
|
|
19085
|
+
* to `debug` on the WRITE side — the body clears it when `debug` goes off,
|
|
19086
|
+
* and refuses it on a track whose debug is off — so a note can never outlive
|
|
19087
|
+
* the attention it belongs to.
|
|
19088
|
+
*
|
|
19089
|
+
* `''` is a real value ("marked, nothing to add"); the ABSENT key means
|
|
19090
|
+
* "leave whatever is stored alone", which is what lets a surface turn debug
|
|
19091
|
+
* on without a note (a cancelled prompt) and what lets it edit the note
|
|
19092
|
+
* without touching the flag.
|
|
19093
|
+
*/
|
|
19094
|
+
debugNote: string().max(500).optional(),
|
|
19054
19095
|
/** Operator favourited this track. Pins it against pruning. */
|
|
19055
19096
|
favourited: boolean().optional()
|
|
19056
19097
|
};
|
|
@@ -19077,6 +19118,12 @@ var TrackFlagsSchema = object({
|
|
|
19077
19118
|
trackId: string(),
|
|
19078
19119
|
markForTrain: boolean(),
|
|
19079
19120
|
debug: boolean(),
|
|
19121
|
+
/** The note as it STANDS after the write — never absent here (a track with no
|
|
19122
|
+
* note reports `''`), for the same reason the booleans are required: a
|
|
19123
|
+
* surface that has just written must be able to render the note it now owns
|
|
19124
|
+
* without a re-fetch, and an absent key would read as "unchanged" on a
|
|
19125
|
+
* screen that has no previous value to keep. */
|
|
19126
|
+
debugNote: string(),
|
|
19080
19127
|
favourited: boolean(),
|
|
19081
19128
|
/** The lifecycle state the boolean was derived from. Required here (unlike on
|
|
19082
19129
|
* a track row) because this shape is only ever produced by the write body,
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
//#endregion
|
|
6
|
-
const require_dist = require("../dist-
|
|
6
|
+
const require_dist = require("../dist-HZncHwuU.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
//#region src/ha-export/topics.ts
|
|
9
9
|
/**
|
|
@@ -4807,14 +4807,19 @@ function projectCapSlice(deviceKey, capName, slice) {
|
|
|
4807
4807
|
* the second in Home Assistant's registry with nothing arriving on it —
|
|
4808
4808
|
* which is precisely the defect the descriptor table replaced.
|
|
4809
4809
|
*/
|
|
4810
|
+
const missingFields = [];
|
|
4810
4811
|
for (const ref of capEntities(capName)) {
|
|
4811
4812
|
if (ref.descriptor.platform === "button") continue;
|
|
4812
|
-
if (!Object.prototype.hasOwnProperty.call(slice, ref.descriptor.field))
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4813
|
+
if (!Object.prototype.hasOwnProperty.call(slice, ref.descriptor.field)) {
|
|
4814
|
+
if (ref.entity === entity) return {
|
|
4815
|
+
kind: "missing-field",
|
|
4816
|
+
capName,
|
|
4817
|
+
field: ref.descriptor.field,
|
|
4818
|
+
sliceFields: Object.keys(slice)
|
|
4819
|
+
};
|
|
4820
|
+
missingFields.push(ref.descriptor.field);
|
|
4821
|
+
continue;
|
|
4822
|
+
}
|
|
4818
4823
|
const raw = slice[ref.descriptor.field];
|
|
4819
4824
|
if (raw === null || raw === void 0) continue;
|
|
4820
4825
|
const rendered = renderCapValue(ref.descriptor, deriveCapValue(ref.descriptor.derive, raw));
|
|
@@ -4833,7 +4838,12 @@ function projectCapSlice(deviceKey, capName, slice) {
|
|
|
4833
4838
|
kind: "no-entity",
|
|
4834
4839
|
capName
|
|
4835
4840
|
};
|
|
4836
|
-
return {
|
|
4841
|
+
return missingFields.length > 0 ? {
|
|
4842
|
+
kind: "values",
|
|
4843
|
+
entity,
|
|
4844
|
+
values,
|
|
4845
|
+
missingFields
|
|
4846
|
+
} : {
|
|
4837
4847
|
kind: "values",
|
|
4838
4848
|
entity,
|
|
4839
4849
|
values
|
|
@@ -5772,6 +5782,23 @@ function projectSynthetic(input, nowMs) {
|
|
|
5772
5782
|
return values;
|
|
5773
5783
|
}
|
|
5774
5784
|
//#endregion
|
|
5785
|
+
//#region src/ha-export/node-reads.ts
|
|
5786
|
+
var UNREAD = {
|
|
5787
|
+
footageUsedBytes: null,
|
|
5788
|
+
footageFreePercent: null
|
|
5789
|
+
};
|
|
5790
|
+
function footageForNode(nodeId, usage) {
|
|
5791
|
+
if (usage === null || usage.nodeId !== nodeId) return UNREAD;
|
|
5792
|
+
const freePercents = (usage.locations ?? []).filter((loc) => loc.availableBytes !== null && loc.totalBytes !== null && loc.totalBytes > 0).map((loc) => loc.availableBytes / loc.totalBytes * 100);
|
|
5793
|
+
return {
|
|
5794
|
+
footageUsedBytes: usage.totalUsedBytes,
|
|
5795
|
+
footageFreePercent: freePercents.length > 0 ? Math.min(...freePercents) : null
|
|
5796
|
+
};
|
|
5797
|
+
}
|
|
5798
|
+
function shouldReadPackageStatus(node) {
|
|
5799
|
+
return !node.isHub && node.isOnline;
|
|
5800
|
+
}
|
|
5801
|
+
//#endregion
|
|
5775
5802
|
//#region src/ha-export/rule-triggers.ts
|
|
5776
5803
|
function reduceOne(row) {
|
|
5777
5804
|
return {
|
|
@@ -6193,6 +6220,14 @@ var HaExportAddon = class extends require_dist.BaseAddon {
|
|
|
6193
6220
|
switch (projection.kind) {
|
|
6194
6221
|
case "values":
|
|
6195
6222
|
if (projection.values.length > 0) this.push(deviceId, projection.values);
|
|
6223
|
+
for (const field of projection.missingFields ?? []) this.warnOnce(deviceId, capName, field, {
|
|
6224
|
+
message: "ha-export: a derived extra entity reads a field its slice does not carry",
|
|
6225
|
+
meta: {
|
|
6226
|
+
capName,
|
|
6227
|
+
field,
|
|
6228
|
+
sliceFields: Object.keys(slice)
|
|
6229
|
+
}
|
|
6230
|
+
});
|
|
6196
6231
|
return;
|
|
6197
6232
|
case "no-entity": return;
|
|
6198
6233
|
case "missing-field":
|
|
@@ -6803,35 +6838,33 @@ var HaExportAddon = class extends require_dist.BaseAddon {
|
|
|
6803
6838
|
}
|
|
6804
6839
|
}
|
|
6805
6840
|
/**
|
|
6806
|
-
* Per-node disk usage (Task D2) and per-AGENT package status (Task D3)
|
|
6807
|
-
*
|
|
6808
|
-
*
|
|
6841
|
+
* Per-node disk usage (Task D2) and per-AGENT package status (Task D3).
|
|
6842
|
+
* `recording` is a singleton cap, so its usage is read ONCE, unpinned, and
|
|
6843
|
+
* attributed to the node the answer names (`footageForNode`); package
|
|
6844
|
+
* status is asked only of online agents (`shouldReadPackageStatus`). A node
|
|
6845
|
+
* that is not read stays `unknown` — unread, never failed.
|
|
6809
6846
|
*/
|
|
6810
6847
|
async buildSyntheticNodes(topology) {
|
|
6848
|
+
const usage = await this.ctx.api.recording.getStorageUsage.query({}).catch((err) => {
|
|
6849
|
+
this.ctx.logger.warn("ha-export: could not read storage usage, the footage entities go unknown", { meta: { error: errMsg(err) } });
|
|
6850
|
+
return null;
|
|
6851
|
+
});
|
|
6811
6852
|
let oldestFootageMs = null;
|
|
6853
|
+
for (const device of usage?.devices ?? []) {
|
|
6854
|
+
const oldest = device.oldestMs;
|
|
6855
|
+
if (oldest === null || oldest === void 0) continue;
|
|
6856
|
+
if (oldestFootageMs === null || oldest < oldestFootageMs) oldestFootageMs = oldest;
|
|
6857
|
+
}
|
|
6812
6858
|
return {
|
|
6813
6859
|
nodes: await Promise.all(topology.map(async (node) => {
|
|
6814
|
-
const
|
|
6815
|
-
|
|
6816
|
-
nodeId: node.id,
|
|
6817
|
-
error: errMsg(err)
|
|
6818
|
-
} });
|
|
6819
|
-
return null;
|
|
6820
|
-
});
|
|
6821
|
-
const freePercents = (disk?.locations ?? []).filter((loc) => loc.availableBytes !== null && loc.totalBytes !== null && loc.totalBytes > 0).map((loc) => loc.availableBytes / loc.totalBytes * 100);
|
|
6822
|
-
const footageFreePercent = freePercents.length > 0 ? Math.min(...freePercents) : null;
|
|
6823
|
-
for (const device of disk?.devices ?? []) {
|
|
6824
|
-
const oldest = device.oldestMs;
|
|
6825
|
-
if (oldest === null || oldest === void 0) continue;
|
|
6826
|
-
if (oldestFootageMs === null || oldest < oldestFootageMs) oldestFootageMs = oldest;
|
|
6827
|
-
}
|
|
6828
|
-
const packageStatus = node.isHub ? null : await this.ctx.api.serverManagement.getServerPackageStatus.query({ nodeId: node.id }).catch((err) => {
|
|
6860
|
+
const footage = footageForNode(node.id, usage);
|
|
6861
|
+
const packageStatus = shouldReadPackageStatus(node) ? await this.ctx.api.serverManagement.getServerPackageStatus.query({ nodeId: node.id }).catch((err) => {
|
|
6829
6862
|
this.ctx.logger.warn("ha-export: could not read package status for an agent node, its update entities go unknown", { meta: {
|
|
6830
6863
|
nodeId: node.id,
|
|
6831
6864
|
error: errMsg(err)
|
|
6832
6865
|
} });
|
|
6833
6866
|
return null;
|
|
6834
|
-
});
|
|
6867
|
+
}) : null;
|
|
6835
6868
|
return {
|
|
6836
6869
|
id: node.id,
|
|
6837
6870
|
name: node.name,
|
|
@@ -6840,8 +6873,8 @@ var HaExportAddon = class extends require_dist.BaseAddon {
|
|
|
6840
6873
|
memoryPercent: node.memoryPercent,
|
|
6841
6874
|
uptime: node.uptime,
|
|
6842
6875
|
isHub: node.isHub,
|
|
6843
|
-
footageUsedBytes:
|
|
6844
|
-
footageFreePercent,
|
|
6876
|
+
footageUsedBytes: footage.footageUsedBytes,
|
|
6877
|
+
footageFreePercent: footage.footageFreePercent,
|
|
6845
6878
|
rootPackageVersion: node.rootPackage?.version ?? null,
|
|
6846
6879
|
updateAvailable: packageStatus?.updateAvailable ?? null
|
|
6847
6880
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { At as EventCategory, C as buildAddonRouteProvider, Et as string, N as deviceExportCapability, X as oauthIntegrationCapability, a as COCO_TO_MACRO, c as FanDirectionSchema, i as CAMERA_SWITCH_ORDER, l as HvacModeSchema, o as CameraSwitchIdSchema, p as addonRoutesCapability, pt as BaseAddon, t as AlarmArmModeSchema, u as MediaPlayerRepeatSchema, vt as nodePin } from "../dist-
|
|
1
|
+
import { At as EventCategory, C as buildAddonRouteProvider, Et as string, N as deviceExportCapability, X as oauthIntegrationCapability, a as COCO_TO_MACRO, c as FanDirectionSchema, i as CAMERA_SWITCH_ORDER, l as HvacModeSchema, o as CameraSwitchIdSchema, p as addonRoutesCapability, pt as BaseAddon, t as AlarmArmModeSchema, u as MediaPlayerRepeatSchema, vt as nodePin } from "../dist-BpePMREY.mjs";
|
|
2
2
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
3
3
|
//#region src/ha-export/topics.ts
|
|
4
4
|
/**
|
|
@@ -4802,14 +4802,19 @@ function projectCapSlice(deviceKey, capName, slice) {
|
|
|
4802
4802
|
* the second in Home Assistant's registry with nothing arriving on it —
|
|
4803
4803
|
* which is precisely the defect the descriptor table replaced.
|
|
4804
4804
|
*/
|
|
4805
|
+
const missingFields = [];
|
|
4805
4806
|
for (const ref of capEntities(capName)) {
|
|
4806
4807
|
if (ref.descriptor.platform === "button") continue;
|
|
4807
|
-
if (!Object.prototype.hasOwnProperty.call(slice, ref.descriptor.field))
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4808
|
+
if (!Object.prototype.hasOwnProperty.call(slice, ref.descriptor.field)) {
|
|
4809
|
+
if (ref.entity === entity) return {
|
|
4810
|
+
kind: "missing-field",
|
|
4811
|
+
capName,
|
|
4812
|
+
field: ref.descriptor.field,
|
|
4813
|
+
sliceFields: Object.keys(slice)
|
|
4814
|
+
};
|
|
4815
|
+
missingFields.push(ref.descriptor.field);
|
|
4816
|
+
continue;
|
|
4817
|
+
}
|
|
4813
4818
|
const raw = slice[ref.descriptor.field];
|
|
4814
4819
|
if (raw === null || raw === void 0) continue;
|
|
4815
4820
|
const rendered = renderCapValue(ref.descriptor, deriveCapValue(ref.descriptor.derive, raw));
|
|
@@ -4828,7 +4833,12 @@ function projectCapSlice(deviceKey, capName, slice) {
|
|
|
4828
4833
|
kind: "no-entity",
|
|
4829
4834
|
capName
|
|
4830
4835
|
};
|
|
4831
|
-
return {
|
|
4836
|
+
return missingFields.length > 0 ? {
|
|
4837
|
+
kind: "values",
|
|
4838
|
+
entity,
|
|
4839
|
+
values,
|
|
4840
|
+
missingFields
|
|
4841
|
+
} : {
|
|
4832
4842
|
kind: "values",
|
|
4833
4843
|
entity,
|
|
4834
4844
|
values
|
|
@@ -5767,6 +5777,23 @@ function projectSynthetic(input, nowMs) {
|
|
|
5767
5777
|
return values;
|
|
5768
5778
|
}
|
|
5769
5779
|
//#endregion
|
|
5780
|
+
//#region src/ha-export/node-reads.ts
|
|
5781
|
+
var UNREAD = {
|
|
5782
|
+
footageUsedBytes: null,
|
|
5783
|
+
footageFreePercent: null
|
|
5784
|
+
};
|
|
5785
|
+
function footageForNode(nodeId, usage) {
|
|
5786
|
+
if (usage === null || usage.nodeId !== nodeId) return UNREAD;
|
|
5787
|
+
const freePercents = (usage.locations ?? []).filter((loc) => loc.availableBytes !== null && loc.totalBytes !== null && loc.totalBytes > 0).map((loc) => loc.availableBytes / loc.totalBytes * 100);
|
|
5788
|
+
return {
|
|
5789
|
+
footageUsedBytes: usage.totalUsedBytes,
|
|
5790
|
+
footageFreePercent: freePercents.length > 0 ? Math.min(...freePercents) : null
|
|
5791
|
+
};
|
|
5792
|
+
}
|
|
5793
|
+
function shouldReadPackageStatus(node) {
|
|
5794
|
+
return !node.isHub && node.isOnline;
|
|
5795
|
+
}
|
|
5796
|
+
//#endregion
|
|
5770
5797
|
//#region src/ha-export/rule-triggers.ts
|
|
5771
5798
|
function reduceOne(row) {
|
|
5772
5799
|
return {
|
|
@@ -6188,6 +6215,14 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
6188
6215
|
switch (projection.kind) {
|
|
6189
6216
|
case "values":
|
|
6190
6217
|
if (projection.values.length > 0) this.push(deviceId, projection.values);
|
|
6218
|
+
for (const field of projection.missingFields ?? []) this.warnOnce(deviceId, capName, field, {
|
|
6219
|
+
message: "ha-export: a derived extra entity reads a field its slice does not carry",
|
|
6220
|
+
meta: {
|
|
6221
|
+
capName,
|
|
6222
|
+
field,
|
|
6223
|
+
sliceFields: Object.keys(slice)
|
|
6224
|
+
}
|
|
6225
|
+
});
|
|
6191
6226
|
return;
|
|
6192
6227
|
case "no-entity": return;
|
|
6193
6228
|
case "missing-field":
|
|
@@ -6798,35 +6833,33 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
6798
6833
|
}
|
|
6799
6834
|
}
|
|
6800
6835
|
/**
|
|
6801
|
-
* Per-node disk usage (Task D2) and per-AGENT package status (Task D3)
|
|
6802
|
-
*
|
|
6803
|
-
*
|
|
6836
|
+
* Per-node disk usage (Task D2) and per-AGENT package status (Task D3).
|
|
6837
|
+
* `recording` is a singleton cap, so its usage is read ONCE, unpinned, and
|
|
6838
|
+
* attributed to the node the answer names (`footageForNode`); package
|
|
6839
|
+
* status is asked only of online agents (`shouldReadPackageStatus`). A node
|
|
6840
|
+
* that is not read stays `unknown` — unread, never failed.
|
|
6804
6841
|
*/
|
|
6805
6842
|
async buildSyntheticNodes(topology) {
|
|
6843
|
+
const usage = await this.ctx.api.recording.getStorageUsage.query({}).catch((err) => {
|
|
6844
|
+
this.ctx.logger.warn("ha-export: could not read storage usage, the footage entities go unknown", { meta: { error: errMsg(err) } });
|
|
6845
|
+
return null;
|
|
6846
|
+
});
|
|
6806
6847
|
let oldestFootageMs = null;
|
|
6848
|
+
for (const device of usage?.devices ?? []) {
|
|
6849
|
+
const oldest = device.oldestMs;
|
|
6850
|
+
if (oldest === null || oldest === void 0) continue;
|
|
6851
|
+
if (oldestFootageMs === null || oldest < oldestFootageMs) oldestFootageMs = oldest;
|
|
6852
|
+
}
|
|
6807
6853
|
return {
|
|
6808
6854
|
nodes: await Promise.all(topology.map(async (node) => {
|
|
6809
|
-
const
|
|
6810
|
-
|
|
6811
|
-
nodeId: node.id,
|
|
6812
|
-
error: errMsg(err)
|
|
6813
|
-
} });
|
|
6814
|
-
return null;
|
|
6815
|
-
});
|
|
6816
|
-
const freePercents = (disk?.locations ?? []).filter((loc) => loc.availableBytes !== null && loc.totalBytes !== null && loc.totalBytes > 0).map((loc) => loc.availableBytes / loc.totalBytes * 100);
|
|
6817
|
-
const footageFreePercent = freePercents.length > 0 ? Math.min(...freePercents) : null;
|
|
6818
|
-
for (const device of disk?.devices ?? []) {
|
|
6819
|
-
const oldest = device.oldestMs;
|
|
6820
|
-
if (oldest === null || oldest === void 0) continue;
|
|
6821
|
-
if (oldestFootageMs === null || oldest < oldestFootageMs) oldestFootageMs = oldest;
|
|
6822
|
-
}
|
|
6823
|
-
const packageStatus = node.isHub ? null : await this.ctx.api.serverManagement.getServerPackageStatus.query({ nodeId: node.id }).catch((err) => {
|
|
6855
|
+
const footage = footageForNode(node.id, usage);
|
|
6856
|
+
const packageStatus = shouldReadPackageStatus(node) ? await this.ctx.api.serverManagement.getServerPackageStatus.query({ nodeId: node.id }).catch((err) => {
|
|
6824
6857
|
this.ctx.logger.warn("ha-export: could not read package status for an agent node, its update entities go unknown", { meta: {
|
|
6825
6858
|
nodeId: node.id,
|
|
6826
6859
|
error: errMsg(err)
|
|
6827
6860
|
} });
|
|
6828
6861
|
return null;
|
|
6829
|
-
});
|
|
6862
|
+
}) : null;
|
|
6830
6863
|
return {
|
|
6831
6864
|
id: node.id,
|
|
6832
6865
|
name: node.name,
|
|
@@ -6835,8 +6868,8 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
6835
6868
|
memoryPercent: node.memoryPercent,
|
|
6836
6869
|
uptime: node.uptime,
|
|
6837
6870
|
isHub: node.isHub,
|
|
6838
|
-
footageUsedBytes:
|
|
6839
|
-
footageFreePercent,
|
|
6871
|
+
footageUsedBytes: footage.footageUsedBytes,
|
|
6872
|
+
footageFreePercent: footage.footageFreePercent,
|
|
6840
6873
|
rootPackageVersion: node.rootPackage?.version ?? null,
|
|
6841
6874
|
updateAvailable: packageStatus?.updateAvailable ?? null
|
|
6842
6875
|
};
|