@camstack/addon-provider-homeassistant 1.2.77 → 1.2.79
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-gv0Gmrm1.js → dist-BI0EGQ5s.js} +42 -0
- package/dist/{dist-CQzCvoCx.mjs → dist-CXuG5e6V.mjs} +42 -0
- package/dist/ha-export/ha-export.addon.js +85 -33
- package/dist/ha-export/ha-export.addon.mjs +85 -33
- 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-BI0EGQ5s.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-CXuG5e6V.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
|
|
@@ -19041,6 +19059,9 @@ var RetrainStatusSchema = _enum([
|
|
|
19041
19059
|
*
|
|
19042
19060
|
* `debug` does NOT pin; it is attention, not durability.
|
|
19043
19061
|
*
|
|
19062
|
+
* `debugNote` is the operator's own words about WHAT should be checked on this
|
|
19063
|
+
* track, and it lives and dies with `debug` — see {@link MAX_TRACK_DEBUG_NOTE_LEN}.
|
|
19064
|
+
*
|
|
19044
19065
|
* `favourited` IS a BOOLEAN pin (durability bit), not a retrain lifecycle.
|
|
19045
19066
|
* A favourited track is skipped by retention the same way `staging` is, but
|
|
19046
19067
|
* it does not enter `none|staging|trained` and has no staging budget.
|
|
@@ -19051,6 +19072,21 @@ var TrackFlagFields = {
|
|
|
19051
19072
|
markForTrain: boolean().optional(),
|
|
19052
19073
|
/** Operator marked this track for diagnostic attention. */
|
|
19053
19074
|
debug: boolean().optional(),
|
|
19075
|
+
/**
|
|
19076
|
+
* What the operator wants CHECKED on this track, in their own words.
|
|
19077
|
+
*
|
|
19078
|
+
* The flag alone says "look at this" and nothing about what for; a debug set
|
|
19079
|
+
* reviewed hours later is a list of tracks with no question attached. Bound
|
|
19080
|
+
* to `debug` on the WRITE side — the body clears it when `debug` goes off,
|
|
19081
|
+
* and refuses it on a track whose debug is off — so a note can never outlive
|
|
19082
|
+
* the attention it belongs to.
|
|
19083
|
+
*
|
|
19084
|
+
* `''` is a real value ("marked, nothing to add"); the ABSENT key means
|
|
19085
|
+
* "leave whatever is stored alone", which is what lets a surface turn debug
|
|
19086
|
+
* on without a note (a cancelled prompt) and what lets it edit the note
|
|
19087
|
+
* without touching the flag.
|
|
19088
|
+
*/
|
|
19089
|
+
debugNote: string().max(500).optional(),
|
|
19054
19090
|
/** Operator favourited this track. Pins it against pruning. */
|
|
19055
19091
|
favourited: boolean().optional()
|
|
19056
19092
|
};
|
|
@@ -19077,6 +19113,12 @@ var TrackFlagsSchema = object({
|
|
|
19077
19113
|
trackId: string(),
|
|
19078
19114
|
markForTrain: boolean(),
|
|
19079
19115
|
debug: boolean(),
|
|
19116
|
+
/** The note as it STANDS after the write — never absent here (a track with no
|
|
19117
|
+
* note reports `''`), for the same reason the booleans are required: a
|
|
19118
|
+
* surface that has just written must be able to render the note it now owns
|
|
19119
|
+
* without a re-fetch, and an absent key would read as "unchanged" on a
|
|
19120
|
+
* screen that has no previous value to keep. */
|
|
19121
|
+
debugNote: string(),
|
|
19080
19122
|
favourited: boolean(),
|
|
19081
19123
|
/** The lifecycle state the boolean was derived from. Required here (unlike on
|
|
19082
19124
|
* 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
|
|
@@ -19041,6 +19059,9 @@ var RetrainStatusSchema = _enum([
|
|
|
19041
19059
|
*
|
|
19042
19060
|
* `debug` does NOT pin; it is attention, not durability.
|
|
19043
19061
|
*
|
|
19062
|
+
* `debugNote` is the operator's own words about WHAT should be checked on this
|
|
19063
|
+
* track, and it lives and dies with `debug` — see {@link MAX_TRACK_DEBUG_NOTE_LEN}.
|
|
19064
|
+
*
|
|
19044
19065
|
* `favourited` IS a BOOLEAN pin (durability bit), not a retrain lifecycle.
|
|
19045
19066
|
* A favourited track is skipped by retention the same way `staging` is, but
|
|
19046
19067
|
* it does not enter `none|staging|trained` and has no staging budget.
|
|
@@ -19051,6 +19072,21 @@ var TrackFlagFields = {
|
|
|
19051
19072
|
markForTrain: boolean().optional(),
|
|
19052
19073
|
/** Operator marked this track for diagnostic attention. */
|
|
19053
19074
|
debug: boolean().optional(),
|
|
19075
|
+
/**
|
|
19076
|
+
* What the operator wants CHECKED on this track, in their own words.
|
|
19077
|
+
*
|
|
19078
|
+
* The flag alone says "look at this" and nothing about what for; a debug set
|
|
19079
|
+
* reviewed hours later is a list of tracks with no question attached. Bound
|
|
19080
|
+
* to `debug` on the WRITE side — the body clears it when `debug` goes off,
|
|
19081
|
+
* and refuses it on a track whose debug is off — so a note can never outlive
|
|
19082
|
+
* the attention it belongs to.
|
|
19083
|
+
*
|
|
19084
|
+
* `''` is a real value ("marked, nothing to add"); the ABSENT key means
|
|
19085
|
+
* "leave whatever is stored alone", which is what lets a surface turn debug
|
|
19086
|
+
* on without a note (a cancelled prompt) and what lets it edit the note
|
|
19087
|
+
* without touching the flag.
|
|
19088
|
+
*/
|
|
19089
|
+
debugNote: string().max(500).optional(),
|
|
19054
19090
|
/** Operator favourited this track. Pins it against pruning. */
|
|
19055
19091
|
favourited: boolean().optional()
|
|
19056
19092
|
};
|
|
@@ -19077,6 +19113,12 @@ var TrackFlagsSchema = object({
|
|
|
19077
19113
|
trackId: string(),
|
|
19078
19114
|
markForTrain: boolean(),
|
|
19079
19115
|
debug: boolean(),
|
|
19116
|
+
/** The note as it STANDS after the write — never absent here (a track with no
|
|
19117
|
+
* note reports `''`), for the same reason the booleans are required: a
|
|
19118
|
+
* surface that has just written must be able to render the note it now owns
|
|
19119
|
+
* without a re-fetch, and an absent key would read as "unchanged" on a
|
|
19120
|
+
* screen that has no previous value to keep. */
|
|
19121
|
+
debugNote: string(),
|
|
19080
19122
|
favourited: boolean(),
|
|
19081
19123
|
/** The lifecycle state the boolean was derived from. Required here (unlike on
|
|
19082
19124
|
* 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-BI0EGQ5s.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
//#region src/ha-export/topics.ts
|
|
9
9
|
/**
|
|
@@ -1538,13 +1538,31 @@ function cameraSpecs(device) {
|
|
|
1538
1538
|
icon: "mdi:counter",
|
|
1539
1539
|
enabledByDefault: false
|
|
1540
1540
|
});
|
|
1541
|
-
if (LABELLED_MACROS.includes(macro)) specs.push(
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1541
|
+
if (LABELLED_MACROS.includes(macro)) specs.push(
|
|
1542
|
+
{
|
|
1543
|
+
entity: `${macro}_last_label`,
|
|
1544
|
+
platform: "sensor",
|
|
1545
|
+
label: `${label} last label`,
|
|
1546
|
+
icon: "mdi:tag",
|
|
1547
|
+
enabledByDefault: true
|
|
1548
|
+
},
|
|
1549
|
+
/**
|
|
1550
|
+
* The identification OCCURRENCE — moves on every recognition,
|
|
1551
|
+
* including a repeat of the SAME name (an unchanged `last_label`
|
|
1552
|
+
* is deduped by the push client, and an identical value is not a
|
|
1553
|
+
* state change in HA). This is the entity an automation's trigger
|
|
1554
|
+
* binds to; the label value arrives in the same batch, ordered
|
|
1555
|
+
* first. Enabled for the same reason `<macro>_last_detection` is:
|
|
1556
|
+
* it IS the automation hook, not a diagnostic.
|
|
1557
|
+
*/
|
|
1558
|
+
{
|
|
1559
|
+
entity: `${macro}_last_label_at`,
|
|
1560
|
+
platform: "sensor",
|
|
1561
|
+
label: `${label} last label at`,
|
|
1562
|
+
deviceClass: "timestamp",
|
|
1563
|
+
enabledByDefault: true
|
|
1564
|
+
}
|
|
1565
|
+
);
|
|
1548
1566
|
}
|
|
1549
1567
|
/**
|
|
1550
1568
|
* The live picture — every stream the hub lists EXCEPT the adaptive one,
|
|
@@ -4886,10 +4904,29 @@ function projectTrackLifecycle(deviceKey, track, mediaUrl) {
|
|
|
4886
4904
|
* topic nothing is subscribed to.
|
|
4887
4905
|
*/
|
|
4888
4906
|
const label = track.plateText ?? track.label;
|
|
4889
|
-
if (label !== void 0 && label.length > 0 && LABELLED_MACROS.includes(macro))
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4907
|
+
if (label !== void 0 && label.length > 0 && LABELLED_MACROS.includes(macro)) {
|
|
4908
|
+
values.push({
|
|
4909
|
+
topic: stateTopic(deviceKey, `${macro}_last_label`),
|
|
4910
|
+
value: label
|
|
4911
|
+
});
|
|
4912
|
+
/**
|
|
4913
|
+
* The occurrence STAMP. A second recognition of the same person is a
|
|
4914
|
+
* real occurrence, but it is invisible on `_last_label`: the push
|
|
4915
|
+
* client dedups per topic, and an identical value is not a state
|
|
4916
|
+
* change in Home Assistant anyway — a state-trigger automation would
|
|
4917
|
+
* never re-fire. The stamp is the value that always moves, so an
|
|
4918
|
+
* automation binds its trigger HERE and reads `<macro>_last_label`
|
|
4919
|
+
* for the name — which is ordered BEFORE the stamp in this array, so
|
|
4920
|
+
* it is already applied when the trigger fires. HA's `event` platform
|
|
4921
|
+
* would be the native home for this, but no released component builds
|
|
4922
|
+
* it (`component-support.ts`): announcing one yields a warning in
|
|
4923
|
+
* HA's log and NO entity — the exact D152 zero-entities failure.
|
|
4924
|
+
*/
|
|
4925
|
+
values.push({
|
|
4926
|
+
topic: stateTopic(deviceKey, `${macro}_last_label_at`),
|
|
4927
|
+
value: iso(track.lastSeen)
|
|
4928
|
+
});
|
|
4929
|
+
}
|
|
4893
4930
|
if (mediaUrl !== null) values.push({
|
|
4894
4931
|
topic: stateTopic(deviceKey, "last_image"),
|
|
4895
4932
|
value: mediaUrl
|
|
@@ -5735,6 +5772,23 @@ function projectSynthetic(input, nowMs) {
|
|
|
5735
5772
|
return values;
|
|
5736
5773
|
}
|
|
5737
5774
|
//#endregion
|
|
5775
|
+
//#region src/ha-export/node-reads.ts
|
|
5776
|
+
var UNREAD = {
|
|
5777
|
+
footageUsedBytes: null,
|
|
5778
|
+
footageFreePercent: null
|
|
5779
|
+
};
|
|
5780
|
+
function footageForNode(nodeId, usage) {
|
|
5781
|
+
if (usage === null || usage.nodeId !== nodeId) return UNREAD;
|
|
5782
|
+
const freePercents = (usage.locations ?? []).filter((loc) => loc.availableBytes !== null && loc.totalBytes !== null && loc.totalBytes > 0).map((loc) => loc.availableBytes / loc.totalBytes * 100);
|
|
5783
|
+
return {
|
|
5784
|
+
footageUsedBytes: usage.totalUsedBytes,
|
|
5785
|
+
footageFreePercent: freePercents.length > 0 ? Math.min(...freePercents) : null
|
|
5786
|
+
};
|
|
5787
|
+
}
|
|
5788
|
+
function shouldReadPackageStatus(node) {
|
|
5789
|
+
return !node.isHub && node.isOnline;
|
|
5790
|
+
}
|
|
5791
|
+
//#endregion
|
|
5738
5792
|
//#region src/ha-export/rule-triggers.ts
|
|
5739
5793
|
function reduceOne(row) {
|
|
5740
5794
|
return {
|
|
@@ -6766,35 +6820,33 @@ var HaExportAddon = class extends require_dist.BaseAddon {
|
|
|
6766
6820
|
}
|
|
6767
6821
|
}
|
|
6768
6822
|
/**
|
|
6769
|
-
* Per-node disk usage (Task D2) and per-AGENT package status (Task D3)
|
|
6770
|
-
*
|
|
6771
|
-
*
|
|
6823
|
+
* Per-node disk usage (Task D2) and per-AGENT package status (Task D3).
|
|
6824
|
+
* `recording` is a singleton cap, so its usage is read ONCE, unpinned, and
|
|
6825
|
+
* attributed to the node the answer names (`footageForNode`); package
|
|
6826
|
+
* status is asked only of online agents (`shouldReadPackageStatus`). A node
|
|
6827
|
+
* that is not read stays `unknown` — unread, never failed.
|
|
6772
6828
|
*/
|
|
6773
6829
|
async buildSyntheticNodes(topology) {
|
|
6830
|
+
const usage = await this.ctx.api.recording.getStorageUsage.query({}).catch((err) => {
|
|
6831
|
+
this.ctx.logger.warn("ha-export: could not read storage usage, the footage entities go unknown", { meta: { error: errMsg(err) } });
|
|
6832
|
+
return null;
|
|
6833
|
+
});
|
|
6774
6834
|
let oldestFootageMs = null;
|
|
6835
|
+
for (const device of usage?.devices ?? []) {
|
|
6836
|
+
const oldest = device.oldestMs;
|
|
6837
|
+
if (oldest === null || oldest === void 0) continue;
|
|
6838
|
+
if (oldestFootageMs === null || oldest < oldestFootageMs) oldestFootageMs = oldest;
|
|
6839
|
+
}
|
|
6775
6840
|
return {
|
|
6776
6841
|
nodes: await Promise.all(topology.map(async (node) => {
|
|
6777
|
-
const
|
|
6778
|
-
|
|
6779
|
-
nodeId: node.id,
|
|
6780
|
-
error: errMsg(err)
|
|
6781
|
-
} });
|
|
6782
|
-
return null;
|
|
6783
|
-
});
|
|
6784
|
-
const freePercents = (disk?.locations ?? []).filter((loc) => loc.availableBytes !== null && loc.totalBytes !== null && loc.totalBytes > 0).map((loc) => loc.availableBytes / loc.totalBytes * 100);
|
|
6785
|
-
const footageFreePercent = freePercents.length > 0 ? Math.min(...freePercents) : null;
|
|
6786
|
-
for (const device of disk?.devices ?? []) {
|
|
6787
|
-
const oldest = device.oldestMs;
|
|
6788
|
-
if (oldest === null || oldest === void 0) continue;
|
|
6789
|
-
if (oldestFootageMs === null || oldest < oldestFootageMs) oldestFootageMs = oldest;
|
|
6790
|
-
}
|
|
6791
|
-
const packageStatus = node.isHub ? null : await this.ctx.api.serverManagement.getServerPackageStatus.query({ nodeId: node.id }).catch((err) => {
|
|
6842
|
+
const footage = footageForNode(node.id, usage);
|
|
6843
|
+
const packageStatus = shouldReadPackageStatus(node) ? await this.ctx.api.serverManagement.getServerPackageStatus.query({ nodeId: node.id }).catch((err) => {
|
|
6792
6844
|
this.ctx.logger.warn("ha-export: could not read package status for an agent node, its update entities go unknown", { meta: {
|
|
6793
6845
|
nodeId: node.id,
|
|
6794
6846
|
error: errMsg(err)
|
|
6795
6847
|
} });
|
|
6796
6848
|
return null;
|
|
6797
|
-
});
|
|
6849
|
+
}) : null;
|
|
6798
6850
|
return {
|
|
6799
6851
|
id: node.id,
|
|
6800
6852
|
name: node.name,
|
|
@@ -6803,8 +6855,8 @@ var HaExportAddon = class extends require_dist.BaseAddon {
|
|
|
6803
6855
|
memoryPercent: node.memoryPercent,
|
|
6804
6856
|
uptime: node.uptime,
|
|
6805
6857
|
isHub: node.isHub,
|
|
6806
|
-
footageUsedBytes:
|
|
6807
|
-
footageFreePercent,
|
|
6858
|
+
footageUsedBytes: footage.footageUsedBytes,
|
|
6859
|
+
footageFreePercent: footage.footageFreePercent,
|
|
6808
6860
|
rootPackageVersion: node.rootPackage?.version ?? null,
|
|
6809
6861
|
updateAvailable: packageStatus?.updateAvailable ?? null
|
|
6810
6862
|
};
|
|
@@ -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-CXuG5e6V.mjs";
|
|
2
2
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
3
3
|
//#region src/ha-export/topics.ts
|
|
4
4
|
/**
|
|
@@ -1533,13 +1533,31 @@ function cameraSpecs(device) {
|
|
|
1533
1533
|
icon: "mdi:counter",
|
|
1534
1534
|
enabledByDefault: false
|
|
1535
1535
|
});
|
|
1536
|
-
if (LABELLED_MACROS.includes(macro)) specs.push(
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1536
|
+
if (LABELLED_MACROS.includes(macro)) specs.push(
|
|
1537
|
+
{
|
|
1538
|
+
entity: `${macro}_last_label`,
|
|
1539
|
+
platform: "sensor",
|
|
1540
|
+
label: `${label} last label`,
|
|
1541
|
+
icon: "mdi:tag",
|
|
1542
|
+
enabledByDefault: true
|
|
1543
|
+
},
|
|
1544
|
+
/**
|
|
1545
|
+
* The identification OCCURRENCE — moves on every recognition,
|
|
1546
|
+
* including a repeat of the SAME name (an unchanged `last_label`
|
|
1547
|
+
* is deduped by the push client, and an identical value is not a
|
|
1548
|
+
* state change in HA). This is the entity an automation's trigger
|
|
1549
|
+
* binds to; the label value arrives in the same batch, ordered
|
|
1550
|
+
* first. Enabled for the same reason `<macro>_last_detection` is:
|
|
1551
|
+
* it IS the automation hook, not a diagnostic.
|
|
1552
|
+
*/
|
|
1553
|
+
{
|
|
1554
|
+
entity: `${macro}_last_label_at`,
|
|
1555
|
+
platform: "sensor",
|
|
1556
|
+
label: `${label} last label at`,
|
|
1557
|
+
deviceClass: "timestamp",
|
|
1558
|
+
enabledByDefault: true
|
|
1559
|
+
}
|
|
1560
|
+
);
|
|
1543
1561
|
}
|
|
1544
1562
|
/**
|
|
1545
1563
|
* The live picture — every stream the hub lists EXCEPT the adaptive one,
|
|
@@ -4881,10 +4899,29 @@ function projectTrackLifecycle(deviceKey, track, mediaUrl) {
|
|
|
4881
4899
|
* topic nothing is subscribed to.
|
|
4882
4900
|
*/
|
|
4883
4901
|
const label = track.plateText ?? track.label;
|
|
4884
|
-
if (label !== void 0 && label.length > 0 && LABELLED_MACROS.includes(macro))
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4902
|
+
if (label !== void 0 && label.length > 0 && LABELLED_MACROS.includes(macro)) {
|
|
4903
|
+
values.push({
|
|
4904
|
+
topic: stateTopic(deviceKey, `${macro}_last_label`),
|
|
4905
|
+
value: label
|
|
4906
|
+
});
|
|
4907
|
+
/**
|
|
4908
|
+
* The occurrence STAMP. A second recognition of the same person is a
|
|
4909
|
+
* real occurrence, but it is invisible on `_last_label`: the push
|
|
4910
|
+
* client dedups per topic, and an identical value is not a state
|
|
4911
|
+
* change in Home Assistant anyway — a state-trigger automation would
|
|
4912
|
+
* never re-fire. The stamp is the value that always moves, so an
|
|
4913
|
+
* automation binds its trigger HERE and reads `<macro>_last_label`
|
|
4914
|
+
* for the name — which is ordered BEFORE the stamp in this array, so
|
|
4915
|
+
* it is already applied when the trigger fires. HA's `event` platform
|
|
4916
|
+
* would be the native home for this, but no released component builds
|
|
4917
|
+
* it (`component-support.ts`): announcing one yields a warning in
|
|
4918
|
+
* HA's log and NO entity — the exact D152 zero-entities failure.
|
|
4919
|
+
*/
|
|
4920
|
+
values.push({
|
|
4921
|
+
topic: stateTopic(deviceKey, `${macro}_last_label_at`),
|
|
4922
|
+
value: iso(track.lastSeen)
|
|
4923
|
+
});
|
|
4924
|
+
}
|
|
4888
4925
|
if (mediaUrl !== null) values.push({
|
|
4889
4926
|
topic: stateTopic(deviceKey, "last_image"),
|
|
4890
4927
|
value: mediaUrl
|
|
@@ -5730,6 +5767,23 @@ function projectSynthetic(input, nowMs) {
|
|
|
5730
5767
|
return values;
|
|
5731
5768
|
}
|
|
5732
5769
|
//#endregion
|
|
5770
|
+
//#region src/ha-export/node-reads.ts
|
|
5771
|
+
var UNREAD = {
|
|
5772
|
+
footageUsedBytes: null,
|
|
5773
|
+
footageFreePercent: null
|
|
5774
|
+
};
|
|
5775
|
+
function footageForNode(nodeId, usage) {
|
|
5776
|
+
if (usage === null || usage.nodeId !== nodeId) return UNREAD;
|
|
5777
|
+
const freePercents = (usage.locations ?? []).filter((loc) => loc.availableBytes !== null && loc.totalBytes !== null && loc.totalBytes > 0).map((loc) => loc.availableBytes / loc.totalBytes * 100);
|
|
5778
|
+
return {
|
|
5779
|
+
footageUsedBytes: usage.totalUsedBytes,
|
|
5780
|
+
footageFreePercent: freePercents.length > 0 ? Math.min(...freePercents) : null
|
|
5781
|
+
};
|
|
5782
|
+
}
|
|
5783
|
+
function shouldReadPackageStatus(node) {
|
|
5784
|
+
return !node.isHub && node.isOnline;
|
|
5785
|
+
}
|
|
5786
|
+
//#endregion
|
|
5733
5787
|
//#region src/ha-export/rule-triggers.ts
|
|
5734
5788
|
function reduceOne(row) {
|
|
5735
5789
|
return {
|
|
@@ -6761,35 +6815,33 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
6761
6815
|
}
|
|
6762
6816
|
}
|
|
6763
6817
|
/**
|
|
6764
|
-
* Per-node disk usage (Task D2) and per-AGENT package status (Task D3)
|
|
6765
|
-
*
|
|
6766
|
-
*
|
|
6818
|
+
* Per-node disk usage (Task D2) and per-AGENT package status (Task D3).
|
|
6819
|
+
* `recording` is a singleton cap, so its usage is read ONCE, unpinned, and
|
|
6820
|
+
* attributed to the node the answer names (`footageForNode`); package
|
|
6821
|
+
* status is asked only of online agents (`shouldReadPackageStatus`). A node
|
|
6822
|
+
* that is not read stays `unknown` — unread, never failed.
|
|
6767
6823
|
*/
|
|
6768
6824
|
async buildSyntheticNodes(topology) {
|
|
6825
|
+
const usage = await this.ctx.api.recording.getStorageUsage.query({}).catch((err) => {
|
|
6826
|
+
this.ctx.logger.warn("ha-export: could not read storage usage, the footage entities go unknown", { meta: { error: errMsg(err) } });
|
|
6827
|
+
return null;
|
|
6828
|
+
});
|
|
6769
6829
|
let oldestFootageMs = null;
|
|
6830
|
+
for (const device of usage?.devices ?? []) {
|
|
6831
|
+
const oldest = device.oldestMs;
|
|
6832
|
+
if (oldest === null || oldest === void 0) continue;
|
|
6833
|
+
if (oldestFootageMs === null || oldest < oldestFootageMs) oldestFootageMs = oldest;
|
|
6834
|
+
}
|
|
6770
6835
|
return {
|
|
6771
6836
|
nodes: await Promise.all(topology.map(async (node) => {
|
|
6772
|
-
const
|
|
6773
|
-
|
|
6774
|
-
nodeId: node.id,
|
|
6775
|
-
error: errMsg(err)
|
|
6776
|
-
} });
|
|
6777
|
-
return null;
|
|
6778
|
-
});
|
|
6779
|
-
const freePercents = (disk?.locations ?? []).filter((loc) => loc.availableBytes !== null && loc.totalBytes !== null && loc.totalBytes > 0).map((loc) => loc.availableBytes / loc.totalBytes * 100);
|
|
6780
|
-
const footageFreePercent = freePercents.length > 0 ? Math.min(...freePercents) : null;
|
|
6781
|
-
for (const device of disk?.devices ?? []) {
|
|
6782
|
-
const oldest = device.oldestMs;
|
|
6783
|
-
if (oldest === null || oldest === void 0) continue;
|
|
6784
|
-
if (oldestFootageMs === null || oldest < oldestFootageMs) oldestFootageMs = oldest;
|
|
6785
|
-
}
|
|
6786
|
-
const packageStatus = node.isHub ? null : await this.ctx.api.serverManagement.getServerPackageStatus.query({ nodeId: node.id }).catch((err) => {
|
|
6837
|
+
const footage = footageForNode(node.id, usage);
|
|
6838
|
+
const packageStatus = shouldReadPackageStatus(node) ? await this.ctx.api.serverManagement.getServerPackageStatus.query({ nodeId: node.id }).catch((err) => {
|
|
6787
6839
|
this.ctx.logger.warn("ha-export: could not read package status for an agent node, its update entities go unknown", { meta: {
|
|
6788
6840
|
nodeId: node.id,
|
|
6789
6841
|
error: errMsg(err)
|
|
6790
6842
|
} });
|
|
6791
6843
|
return null;
|
|
6792
|
-
});
|
|
6844
|
+
}) : null;
|
|
6793
6845
|
return {
|
|
6794
6846
|
id: node.id,
|
|
6795
6847
|
name: node.name,
|
|
@@ -6798,8 +6850,8 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
6798
6850
|
memoryPercent: node.memoryPercent,
|
|
6799
6851
|
uptime: node.uptime,
|
|
6800
6852
|
isHub: node.isHub,
|
|
6801
|
-
footageUsedBytes:
|
|
6802
|
-
footageFreePercent,
|
|
6853
|
+
footageUsedBytes: footage.footageUsedBytes,
|
|
6854
|
+
footageFreePercent: footage.footageFreePercent,
|
|
6803
6855
|
rootPackageVersion: node.rootPackage?.version ?? null,
|
|
6804
6856
|
updateAvailable: packageStatus?.updateAvailable ?? null
|
|
6805
6857
|
};
|