@camstack/addon-provider-homeassistant 1.2.43 → 1.2.44
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-DiprHh6i.js → dist-B1grYZFh.js} +77 -2
- package/dist/{dist-Dbn3F-bB.mjs → dist-BjB1J9mO.mjs} +77 -2
- package/dist/ha-export/ha-export.addon.js +124 -3
- package/dist/ha-export/ha-export.addon.mjs +124 -3
- 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-B1grYZFh.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-BjB1J9mO.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
|
|
@@ -16154,7 +16154,23 @@ var NcHistoryEntrySchema = object({
|
|
|
16154
16154
|
updatedAt: number(),
|
|
16155
16155
|
/** Failure detail — present on a `dead` row. */
|
|
16156
16156
|
error: string().optional(),
|
|
16157
|
-
subject: NcHistorySubjectSchema
|
|
16157
|
+
subject: NcHistorySubjectSchema,
|
|
16158
|
+
/**
|
|
16159
|
+
* Ids of the artefacts (still, then gif, then clip) this row's successful
|
|
16160
|
+
* delivery indexed in the artefact library — a REFERENCE, never the bytes
|
|
16161
|
+
* (an artefact is often megabytes; this row is durable JSON rewritten on
|
|
16162
|
+
* every delivery attempt). Absent on a row still pending/dead, a row
|
|
16163
|
+
* delivered before this field shipped, or a wiring with no artefact index.
|
|
16164
|
+
*
|
|
16165
|
+
* Resolve one to a fetchable URL with `resolveArtifactUrl` — an id
|
|
16166
|
+
* outlives any one URL's TTL, so a caller mints a fresh link on demand
|
|
16167
|
+
* rather than trusting one frozen at delivery time. `resolveArtifactUrl`
|
|
16168
|
+
* also answers `null` for an id whose artefact has since expired past the
|
|
16169
|
+
* retained shelf's own age bound — the degrade a caller (the Home
|
|
16170
|
+
* Assistant export) must render as "no image right now", never as a
|
|
16171
|
+
* broken link.
|
|
16172
|
+
*/
|
|
16173
|
+
artifactIds: array(string().min(1)).optional()
|
|
16158
16174
|
});
|
|
16159
16175
|
/**
|
|
16160
16176
|
* Query filter for `getHistory` (spec §4.2). Every field is a narrowing
|
|
@@ -16381,7 +16397,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
|
|
|
16381
16397
|
}), method(object({}), object({
|
|
16382
16398
|
catalog: array(NcConditionDescriptorSchema),
|
|
16383
16399
|
taxonomy: NcTaxonomySchema.optional()
|
|
16384
|
-
})), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" }), method(object({}), object({ snoozes: array(NcSnoozeSchema) }), { caller: "required" }), method(object({ snooze: NcSnoozeInputSchema }), object({ snooze: NcSnoozeSchema }), {
|
|
16400
|
+
})), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { 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 }), {
|
|
16385
16401
|
kind: "mutation",
|
|
16386
16402
|
caller: "required"
|
|
16387
16403
|
}), method(object({ snoozeId: string() }), object({ success: literal(true) }), {
|
|
@@ -27506,6 +27522,39 @@ var ReadGopBytesResultSchema = object({
|
|
|
27506
27522
|
/** Media ms the returned fragment covers. */
|
|
27507
27523
|
gopDurMs: number()
|
|
27508
27524
|
});
|
|
27525
|
+
/**
|
|
27526
|
+
* A time WINDOW of one finalized segment, cut by byte range — the multi-GOP
|
|
27527
|
+
* twin of {@link ReadGopBytesResultSchema}'s single instant. Built for the
|
|
27528
|
+
* replay clip's `recording` source (`docs/design/plans/2026-08-26-replay-clip-su-pipeline.md`):
|
|
27529
|
+
* a replay needs several seconds of native pixels, not one frame.
|
|
27530
|
+
*
|
|
27531
|
+
* `ok.data` is standalone-demuxable, same as a GOP read. `ok.reachesRequestedEnd`
|
|
27532
|
+
* is `false` when the returned bytes were cut short by the read's own safety
|
|
27533
|
+
* byte cap before covering `[fromMs, toMs)` — a truncation, reported, not a
|
|
27534
|
+
* silently shorter answer. `spans-multiple-segments` is a REFUSAL, not a
|
|
27535
|
+
* degradation: a window whose end falls past the covering segment would need
|
|
27536
|
+
* bytes stitched from a second segment file (its own `ftyp`+`moov`), which is
|
|
27537
|
+
* not one standalone-demuxable stream — the caller's answer is to request a
|
|
27538
|
+
* shorter window or one aligned to a single segment, not to receive spliced
|
|
27539
|
+
* bytes nothing has proven decodable.
|
|
27540
|
+
*/
|
|
27541
|
+
var ReadWindowBytesResultSchema = discriminatedUnion("kind", [object({
|
|
27542
|
+
kind: literal("ok"),
|
|
27543
|
+
data: _instanceof(Uint8Array),
|
|
27544
|
+
/** Absolute epoch ms of the returned bytes' first sample — at or before
|
|
27545
|
+
* the requested `fromMs` (anchored on the nearest keyframe). */
|
|
27546
|
+
gopStartMs: number(),
|
|
27547
|
+
/** Media ms the returned bytes cover, from `gopStartMs`. */
|
|
27548
|
+
gopDurMs: number(),
|
|
27549
|
+
/** `false` ⇒ the safety byte cap cut the read short before it reached
|
|
27550
|
+
* the requested `toMs`; the caller got fewer frames than asked for. */
|
|
27551
|
+
reachesRequestedEnd: boolean()
|
|
27552
|
+
}), object({
|
|
27553
|
+
kind: literal("spans-multiple-segments"),
|
|
27554
|
+
/** Where the covering segment's own footage runs out — informational,
|
|
27555
|
+
* not a retry hint (retrying the same window would refuse again). */
|
|
27556
|
+
segmentEndMs: number()
|
|
27557
|
+
})]);
|
|
27509
27558
|
method(object({
|
|
27510
27559
|
deviceId: number(),
|
|
27511
27560
|
fromMs: number(),
|
|
@@ -27556,6 +27605,15 @@ method(object({
|
|
|
27556
27605
|
}), ReadGopBytesResultSchema, {
|
|
27557
27606
|
kind: "query",
|
|
27558
27607
|
auth: "admin"
|
|
27608
|
+
}), method(object({
|
|
27609
|
+
deviceId: number(),
|
|
27610
|
+
profile: string(),
|
|
27611
|
+
startMs: number(),
|
|
27612
|
+
fromMs: number(),
|
|
27613
|
+
toMs: number()
|
|
27614
|
+
}), ReadWindowBytesResultSchema, {
|
|
27615
|
+
kind: "query",
|
|
27616
|
+
auth: "admin"
|
|
27559
27617
|
}), method(object({
|
|
27560
27618
|
deviceId: number(),
|
|
27561
27619
|
config: RecordingConfigSchema
|
|
@@ -33643,6 +33701,12 @@ Object.freeze({
|
|
|
33643
33701
|
addonId: null,
|
|
33644
33702
|
access: "view"
|
|
33645
33703
|
},
|
|
33704
|
+
"notificationRules.resolveArtifactUrl": {
|
|
33705
|
+
capName: "notification-rules",
|
|
33706
|
+
capScope: "system",
|
|
33707
|
+
addonId: null,
|
|
33708
|
+
access: "view"
|
|
33709
|
+
},
|
|
33646
33710
|
"notificationRules.setAlarmConfig": {
|
|
33647
33711
|
capName: "notification-rules",
|
|
33648
33712
|
capScope: "system",
|
|
@@ -35047,6 +35111,12 @@ Object.freeze({
|
|
|
35047
35111
|
addonId: null,
|
|
35048
35112
|
access: "view"
|
|
35049
35113
|
},
|
|
35114
|
+
"recording.readWindowBytes": {
|
|
35115
|
+
capName: "recording",
|
|
35116
|
+
capScope: "system",
|
|
35117
|
+
addonId: null,
|
|
35118
|
+
access: "view"
|
|
35119
|
+
},
|
|
35050
35120
|
"recording.refreshStorageLocationsForMigration": {
|
|
35051
35121
|
capName: "recording",
|
|
35052
35122
|
capScope: "system",
|
|
@@ -37877,6 +37947,11 @@ Object.freeze({
|
|
|
37877
37947
|
form: "single",
|
|
37878
37948
|
optional: false
|
|
37879
37949
|
}],
|
|
37950
|
+
"recording.readWindowBytes": [{
|
|
37951
|
+
name: "deviceId",
|
|
37952
|
+
form: "single",
|
|
37953
|
+
optional: false
|
|
37954
|
+
}],
|
|
37880
37955
|
"recording.relocateFootage": [{
|
|
37881
37956
|
name: "deviceId",
|
|
37882
37957
|
form: "single",
|
|
@@ -16154,7 +16154,23 @@ var NcHistoryEntrySchema = object({
|
|
|
16154
16154
|
updatedAt: number(),
|
|
16155
16155
|
/** Failure detail — present on a `dead` row. */
|
|
16156
16156
|
error: string().optional(),
|
|
16157
|
-
subject: NcHistorySubjectSchema
|
|
16157
|
+
subject: NcHistorySubjectSchema,
|
|
16158
|
+
/**
|
|
16159
|
+
* Ids of the artefacts (still, then gif, then clip) this row's successful
|
|
16160
|
+
* delivery indexed in the artefact library — a REFERENCE, never the bytes
|
|
16161
|
+
* (an artefact is often megabytes; this row is durable JSON rewritten on
|
|
16162
|
+
* every delivery attempt). Absent on a row still pending/dead, a row
|
|
16163
|
+
* delivered before this field shipped, or a wiring with no artefact index.
|
|
16164
|
+
*
|
|
16165
|
+
* Resolve one to a fetchable URL with `resolveArtifactUrl` — an id
|
|
16166
|
+
* outlives any one URL's TTL, so a caller mints a fresh link on demand
|
|
16167
|
+
* rather than trusting one frozen at delivery time. `resolveArtifactUrl`
|
|
16168
|
+
* also answers `null` for an id whose artefact has since expired past the
|
|
16169
|
+
* retained shelf's own age bound — the degrade a caller (the Home
|
|
16170
|
+
* Assistant export) must render as "no image right now", never as a
|
|
16171
|
+
* broken link.
|
|
16172
|
+
*/
|
|
16173
|
+
artifactIds: array(string().min(1)).optional()
|
|
16158
16174
|
});
|
|
16159
16175
|
/**
|
|
16160
16176
|
* Query filter for `getHistory` (spec §4.2). Every field is a narrowing
|
|
@@ -16381,7 +16397,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
|
|
|
16381
16397
|
}), method(object({}), object({
|
|
16382
16398
|
catalog: array(NcConditionDescriptorSchema),
|
|
16383
16399
|
taxonomy: NcTaxonomySchema.optional()
|
|
16384
|
-
})), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" }), method(object({}), object({ snoozes: array(NcSnoozeSchema) }), { caller: "required" }), method(object({ snooze: NcSnoozeInputSchema }), object({ snooze: NcSnoozeSchema }), {
|
|
16400
|
+
})), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { 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 }), {
|
|
16385
16401
|
kind: "mutation",
|
|
16386
16402
|
caller: "required"
|
|
16387
16403
|
}), method(object({ snoozeId: string() }), object({ success: literal(true) }), {
|
|
@@ -27506,6 +27522,39 @@ var ReadGopBytesResultSchema = object({
|
|
|
27506
27522
|
/** Media ms the returned fragment covers. */
|
|
27507
27523
|
gopDurMs: number()
|
|
27508
27524
|
});
|
|
27525
|
+
/**
|
|
27526
|
+
* A time WINDOW of one finalized segment, cut by byte range — the multi-GOP
|
|
27527
|
+
* twin of {@link ReadGopBytesResultSchema}'s single instant. Built for the
|
|
27528
|
+
* replay clip's `recording` source (`docs/design/plans/2026-08-26-replay-clip-su-pipeline.md`):
|
|
27529
|
+
* a replay needs several seconds of native pixels, not one frame.
|
|
27530
|
+
*
|
|
27531
|
+
* `ok.data` is standalone-demuxable, same as a GOP read. `ok.reachesRequestedEnd`
|
|
27532
|
+
* is `false` when the returned bytes were cut short by the read's own safety
|
|
27533
|
+
* byte cap before covering `[fromMs, toMs)` — a truncation, reported, not a
|
|
27534
|
+
* silently shorter answer. `spans-multiple-segments` is a REFUSAL, not a
|
|
27535
|
+
* degradation: a window whose end falls past the covering segment would need
|
|
27536
|
+
* bytes stitched from a second segment file (its own `ftyp`+`moov`), which is
|
|
27537
|
+
* not one standalone-demuxable stream — the caller's answer is to request a
|
|
27538
|
+
* shorter window or one aligned to a single segment, not to receive spliced
|
|
27539
|
+
* bytes nothing has proven decodable.
|
|
27540
|
+
*/
|
|
27541
|
+
var ReadWindowBytesResultSchema = discriminatedUnion("kind", [object({
|
|
27542
|
+
kind: literal("ok"),
|
|
27543
|
+
data: _instanceof(Uint8Array),
|
|
27544
|
+
/** Absolute epoch ms of the returned bytes' first sample — at or before
|
|
27545
|
+
* the requested `fromMs` (anchored on the nearest keyframe). */
|
|
27546
|
+
gopStartMs: number(),
|
|
27547
|
+
/** Media ms the returned bytes cover, from `gopStartMs`. */
|
|
27548
|
+
gopDurMs: number(),
|
|
27549
|
+
/** `false` ⇒ the safety byte cap cut the read short before it reached
|
|
27550
|
+
* the requested `toMs`; the caller got fewer frames than asked for. */
|
|
27551
|
+
reachesRequestedEnd: boolean()
|
|
27552
|
+
}), object({
|
|
27553
|
+
kind: literal("spans-multiple-segments"),
|
|
27554
|
+
/** Where the covering segment's own footage runs out — informational,
|
|
27555
|
+
* not a retry hint (retrying the same window would refuse again). */
|
|
27556
|
+
segmentEndMs: number()
|
|
27557
|
+
})]);
|
|
27509
27558
|
method(object({
|
|
27510
27559
|
deviceId: number(),
|
|
27511
27560
|
fromMs: number(),
|
|
@@ -27556,6 +27605,15 @@ method(object({
|
|
|
27556
27605
|
}), ReadGopBytesResultSchema, {
|
|
27557
27606
|
kind: "query",
|
|
27558
27607
|
auth: "admin"
|
|
27608
|
+
}), method(object({
|
|
27609
|
+
deviceId: number(),
|
|
27610
|
+
profile: string(),
|
|
27611
|
+
startMs: number(),
|
|
27612
|
+
fromMs: number(),
|
|
27613
|
+
toMs: number()
|
|
27614
|
+
}), ReadWindowBytesResultSchema, {
|
|
27615
|
+
kind: "query",
|
|
27616
|
+
auth: "admin"
|
|
27559
27617
|
}), method(object({
|
|
27560
27618
|
deviceId: number(),
|
|
27561
27619
|
config: RecordingConfigSchema
|
|
@@ -33643,6 +33701,12 @@ Object.freeze({
|
|
|
33643
33701
|
addonId: null,
|
|
33644
33702
|
access: "view"
|
|
33645
33703
|
},
|
|
33704
|
+
"notificationRules.resolveArtifactUrl": {
|
|
33705
|
+
capName: "notification-rules",
|
|
33706
|
+
capScope: "system",
|
|
33707
|
+
addonId: null,
|
|
33708
|
+
access: "view"
|
|
33709
|
+
},
|
|
33646
33710
|
"notificationRules.setAlarmConfig": {
|
|
33647
33711
|
capName: "notification-rules",
|
|
33648
33712
|
capScope: "system",
|
|
@@ -35047,6 +35111,12 @@ Object.freeze({
|
|
|
35047
35111
|
addonId: null,
|
|
35048
35112
|
access: "view"
|
|
35049
35113
|
},
|
|
35114
|
+
"recording.readWindowBytes": {
|
|
35115
|
+
capName: "recording",
|
|
35116
|
+
capScope: "system",
|
|
35117
|
+
addonId: null,
|
|
35118
|
+
access: "view"
|
|
35119
|
+
},
|
|
35050
35120
|
"recording.refreshStorageLocationsForMigration": {
|
|
35051
35121
|
capName: "recording",
|
|
35052
35122
|
capScope: "system",
|
|
@@ -37877,6 +37947,11 @@ Object.freeze({
|
|
|
37877
37947
|
form: "single",
|
|
37878
37948
|
optional: false
|
|
37879
37949
|
}],
|
|
37950
|
+
"recording.readWindowBytes": [{
|
|
37951
|
+
name: "deviceId",
|
|
37952
|
+
form: "single",
|
|
37953
|
+
optional: false
|
|
37954
|
+
}],
|
|
37880
37955
|
"recording.relocateFootage": [{
|
|
37881
37956
|
name: "deviceId",
|
|
37882
37957
|
form: "single",
|
|
@@ -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-B1grYZFh.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
//#region src/ha-export/topics.ts
|
|
9
9
|
/**
|
|
@@ -5195,6 +5195,64 @@ function notificationCenterSpecs(input) {
|
|
|
5195
5195
|
writable: true,
|
|
5196
5196
|
options: SNOOZE_OPTIONS
|
|
5197
5197
|
}),
|
|
5198
|
+
(
|
|
5199
|
+
/**
|
|
5200
|
+
* The last trigger, six entities: five facts plus its picture — "conoscere
|
|
5201
|
+
* gli ultimi trigger, le ultime immagini... e da quale label" (operator,
|
|
5202
|
+
* 2026-08-25). ENABLED by default like every other entity on this device
|
|
5203
|
+
* (see {@link buildComponents}'s docblock): a rule the operator wrote is a
|
|
5204
|
+
* rule they want to watch fire.
|
|
5205
|
+
*
|
|
5206
|
+
* Fixed count regardless of camera fleet size — the fan-out this design
|
|
5207
|
+
* explicitly avoids (960 entities at 30 cameras × 32 rules) lives ONLY in
|
|
5208
|
+
* the per-rule switches below, which already existed. These six are on
|
|
5209
|
+
* ONE synthetic device and never repeat per camera or per rule.
|
|
5210
|
+
*/
|
|
5211
|
+
{
|
|
5212
|
+
entity: "last_trigger_at",
|
|
5213
|
+
platform: "sensor",
|
|
5214
|
+
label: "Last trigger",
|
|
5215
|
+
deviceClass: "timestamp"
|
|
5216
|
+
}),
|
|
5217
|
+
{
|
|
5218
|
+
entity: "last_trigger_rule",
|
|
5219
|
+
platform: "sensor",
|
|
5220
|
+
label: "Last trigger rule",
|
|
5221
|
+
icon: "mdi:bell-ring-outline"
|
|
5222
|
+
},
|
|
5223
|
+
{
|
|
5224
|
+
entity: "last_trigger_camera",
|
|
5225
|
+
platform: "sensor",
|
|
5226
|
+
label: "Last trigger camera",
|
|
5227
|
+
icon: "mdi:cctv"
|
|
5228
|
+
},
|
|
5229
|
+
(
|
|
5230
|
+
/** The identified label, or the detected class when none was resolved —
|
|
5231
|
+
* see {@link SyntheticLastTrigger.label}. Never blank. */
|
|
5232
|
+
{
|
|
5233
|
+
entity: "last_trigger_label",
|
|
5234
|
+
platform: "sensor",
|
|
5235
|
+
label: "Last trigger label",
|
|
5236
|
+
icon: "mdi:tag-outline"
|
|
5237
|
+
}),
|
|
5238
|
+
(
|
|
5239
|
+
/** `pending` / `sent` / `dead` — the outbox row's own status, straight
|
|
5240
|
+
* from `getHistory`, never a second delivery ledger. */
|
|
5241
|
+
{
|
|
5242
|
+
entity: "last_trigger_status",
|
|
5243
|
+
platform: "sensor",
|
|
5244
|
+
label: "Last trigger delivery status",
|
|
5245
|
+
icon: "mdi:send-check-outline",
|
|
5246
|
+
entityCategory: "diagnostic"
|
|
5247
|
+
}),
|
|
5248
|
+
(
|
|
5249
|
+
/** See {@link SyntheticLastTrigger.imageUrl} for the expired/unreachable
|
|
5250
|
+
* degrade: no value is published rather than a link that 404s. */
|
|
5251
|
+
{
|
|
5252
|
+
entity: "last_trigger_image",
|
|
5253
|
+
platform: "image",
|
|
5254
|
+
label: "Last trigger image"
|
|
5255
|
+
}),
|
|
5198
5256
|
...input.rules.map((rule) => ({
|
|
5199
5257
|
entity: ruleEntity(rule.id),
|
|
5200
5258
|
platform: "switch",
|
|
@@ -5440,6 +5498,29 @@ function projectSynthetic(input, nowMs) {
|
|
|
5440
5498
|
value: String(input.alertsActiveCount)
|
|
5441
5499
|
}
|
|
5442
5500
|
];
|
|
5501
|
+
if (input.lastTrigger !== null) {
|
|
5502
|
+
const t = input.lastTrigger;
|
|
5503
|
+
values.push({
|
|
5504
|
+
topic: stateTopic(nc, "last_trigger_at"),
|
|
5505
|
+
value: new Date(t.at).toISOString()
|
|
5506
|
+
}, {
|
|
5507
|
+
topic: stateTopic(nc, "last_trigger_rule"),
|
|
5508
|
+
value: t.ruleName
|
|
5509
|
+
}, {
|
|
5510
|
+
topic: stateTopic(nc, "last_trigger_camera"),
|
|
5511
|
+
value: t.deviceName
|
|
5512
|
+
}, {
|
|
5513
|
+
topic: stateTopic(nc, "last_trigger_label"),
|
|
5514
|
+
value: t.label
|
|
5515
|
+
}, {
|
|
5516
|
+
topic: stateTopic(nc, "last_trigger_status"),
|
|
5517
|
+
value: t.status
|
|
5518
|
+
});
|
|
5519
|
+
if (t.imageUrl !== null) values.push({
|
|
5520
|
+
topic: stateTopic(nc, "last_trigger_image"),
|
|
5521
|
+
value: t.imageUrl
|
|
5522
|
+
});
|
|
5523
|
+
}
|
|
5443
5524
|
if (input.alertsLastTitle !== null) values.push({
|
|
5444
5525
|
topic: stateTopic(srv, "alerts_last_title"),
|
|
5445
5526
|
value: input.alertsLastTitle
|
|
@@ -6259,7 +6340,7 @@ var HaExportAddon = class extends require_dist.BaseAddon {
|
|
|
6259
6340
|
* `brokerIds` is every enabled broker, because they belong to the server
|
|
6260
6341
|
* and not to a membership the operator picks per camera.
|
|
6261
6342
|
*/
|
|
6262
|
-
const synthetic = await this.buildSynthetic(snoozes);
|
|
6343
|
+
const synthetic = await this.buildSynthetic(snoozes, byId);
|
|
6263
6344
|
if (synthetic !== null) for (const plan of syntheticPlans(synthetic)) {
|
|
6264
6345
|
exported.set(plan.deviceKey, {
|
|
6265
6346
|
plan,
|
|
@@ -6342,7 +6423,7 @@ var HaExportAddon = class extends require_dist.BaseAddon {
|
|
|
6342
6423
|
* Returns `null` only when NOTHING answered, so the devices are not
|
|
6343
6424
|
* announced empty on a hub that is still booting.
|
|
6344
6425
|
*/
|
|
6345
|
-
async buildSynthetic(snoozes) {
|
|
6426
|
+
async buildSynthetic(snoozes, byId) {
|
|
6346
6427
|
const rules = await this.ctx.api.notificationRules.listRules.query({}).then((r) => r.rules.map((rule) => ({
|
|
6347
6428
|
id: rule.id,
|
|
6348
6429
|
name: rule.name,
|
|
@@ -6369,12 +6450,14 @@ var HaExportAddon = class extends require_dist.BaseAddon {
|
|
|
6369
6450
|
}).catch(() => null);
|
|
6370
6451
|
const server = await this.ctx.api.serverManagement.getServerPackageStatus.query().catch(() => null);
|
|
6371
6452
|
const alerts = await this.readActiveAlerts();
|
|
6453
|
+
const lastTrigger = await this.buildLastTrigger(byId);
|
|
6372
6454
|
if (rules === null && topology === null && addons === null && server === null) return null;
|
|
6373
6455
|
this.syntheticRules = rules ?? this.syntheticRules;
|
|
6374
6456
|
const { nodes, oldestFootageMs } = await this.buildSyntheticNodes(topology ?? []);
|
|
6375
6457
|
return {
|
|
6376
6458
|
rules: rules ?? this.syntheticRules,
|
|
6377
6459
|
snoozed: anySnoozed,
|
|
6460
|
+
lastTrigger,
|
|
6378
6461
|
nodes,
|
|
6379
6462
|
addonsRunning: addons?.running ?? 0,
|
|
6380
6463
|
addonsFailed: addons?.failed ?? 0,
|
|
@@ -6388,6 +6471,44 @@ var HaExportAddon = class extends require_dist.BaseAddon {
|
|
|
6388
6471
|
};
|
|
6389
6472
|
}
|
|
6390
6473
|
/**
|
|
6474
|
+
* The most recent notification-rule trigger — "conoscere gli ultimi
|
|
6475
|
+
* trigger, le ultime immagini... e da quale label" (operator, 2026-08-25).
|
|
6476
|
+
*
|
|
6477
|
+
* `getHistory` is already newest-first (§3.2), so `limit: 1` is the whole
|
|
6478
|
+
* question. Best-effort like every other synthetic source here: a failed
|
|
6479
|
+
* read costs only these six entities, never the rest of the device.
|
|
6480
|
+
*/
|
|
6481
|
+
async buildLastTrigger(byId) {
|
|
6482
|
+
try {
|
|
6483
|
+
const { entries } = await this.ctx.api.notificationRules.getHistory.query({ filter: { limit: 1 } });
|
|
6484
|
+
const entry = entries[0];
|
|
6485
|
+
if (entry === void 0) return null;
|
|
6486
|
+
const label = entry.subject.label ?? entry.subject.className;
|
|
6487
|
+
const artifactId = entry.artifactIds?.[0];
|
|
6488
|
+
const imageUrl = artifactId === void 0 ? null : await this.ctx.api.notificationRules.resolveArtifactUrl.query({ artifactId }).then((r) => r.url).catch((err) => {
|
|
6489
|
+
this.ctx.logger.debug("ha-export: could not resolve the last trigger image url", {
|
|
6490
|
+
tags: { deviceId: entry.deviceId },
|
|
6491
|
+
meta: {
|
|
6492
|
+
artifactId,
|
|
6493
|
+
error: errMsg(err)
|
|
6494
|
+
}
|
|
6495
|
+
});
|
|
6496
|
+
return null;
|
|
6497
|
+
});
|
|
6498
|
+
return {
|
|
6499
|
+
at: entry.createdAt,
|
|
6500
|
+
ruleName: entry.ruleName,
|
|
6501
|
+
deviceName: byId.get(entry.deviceId)?.name ?? `camera ${entry.deviceId}`,
|
|
6502
|
+
label,
|
|
6503
|
+
status: entry.status,
|
|
6504
|
+
imageUrl
|
|
6505
|
+
};
|
|
6506
|
+
} catch (err) {
|
|
6507
|
+
this.ctx.logger.warn("ha-export: could not read the last notification trigger", { meta: { error: errMsg(err) } });
|
|
6508
|
+
return null;
|
|
6509
|
+
}
|
|
6510
|
+
}
|
|
6511
|
+
/**
|
|
6391
6512
|
* `alerts.list` — the conditions nobody said (Task D1). Best-effort: a
|
|
6392
6513
|
* failed read yields "nothing active", not a fabricated absence dressed up
|
|
6393
6514
|
* as a clean one — this addon logs the drop rather than pretending the
|
|
@@ -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-BjB1J9mO.mjs";
|
|
2
2
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
3
3
|
//#region src/ha-export/topics.ts
|
|
4
4
|
/**
|
|
@@ -5190,6 +5190,64 @@ function notificationCenterSpecs(input) {
|
|
|
5190
5190
|
writable: true,
|
|
5191
5191
|
options: SNOOZE_OPTIONS
|
|
5192
5192
|
}),
|
|
5193
|
+
(
|
|
5194
|
+
/**
|
|
5195
|
+
* The last trigger, six entities: five facts plus its picture — "conoscere
|
|
5196
|
+
* gli ultimi trigger, le ultime immagini... e da quale label" (operator,
|
|
5197
|
+
* 2026-08-25). ENABLED by default like every other entity on this device
|
|
5198
|
+
* (see {@link buildComponents}'s docblock): a rule the operator wrote is a
|
|
5199
|
+
* rule they want to watch fire.
|
|
5200
|
+
*
|
|
5201
|
+
* Fixed count regardless of camera fleet size — the fan-out this design
|
|
5202
|
+
* explicitly avoids (960 entities at 30 cameras × 32 rules) lives ONLY in
|
|
5203
|
+
* the per-rule switches below, which already existed. These six are on
|
|
5204
|
+
* ONE synthetic device and never repeat per camera or per rule.
|
|
5205
|
+
*/
|
|
5206
|
+
{
|
|
5207
|
+
entity: "last_trigger_at",
|
|
5208
|
+
platform: "sensor",
|
|
5209
|
+
label: "Last trigger",
|
|
5210
|
+
deviceClass: "timestamp"
|
|
5211
|
+
}),
|
|
5212
|
+
{
|
|
5213
|
+
entity: "last_trigger_rule",
|
|
5214
|
+
platform: "sensor",
|
|
5215
|
+
label: "Last trigger rule",
|
|
5216
|
+
icon: "mdi:bell-ring-outline"
|
|
5217
|
+
},
|
|
5218
|
+
{
|
|
5219
|
+
entity: "last_trigger_camera",
|
|
5220
|
+
platform: "sensor",
|
|
5221
|
+
label: "Last trigger camera",
|
|
5222
|
+
icon: "mdi:cctv"
|
|
5223
|
+
},
|
|
5224
|
+
(
|
|
5225
|
+
/** The identified label, or the detected class when none was resolved —
|
|
5226
|
+
* see {@link SyntheticLastTrigger.label}. Never blank. */
|
|
5227
|
+
{
|
|
5228
|
+
entity: "last_trigger_label",
|
|
5229
|
+
platform: "sensor",
|
|
5230
|
+
label: "Last trigger label",
|
|
5231
|
+
icon: "mdi:tag-outline"
|
|
5232
|
+
}),
|
|
5233
|
+
(
|
|
5234
|
+
/** `pending` / `sent` / `dead` — the outbox row's own status, straight
|
|
5235
|
+
* from `getHistory`, never a second delivery ledger. */
|
|
5236
|
+
{
|
|
5237
|
+
entity: "last_trigger_status",
|
|
5238
|
+
platform: "sensor",
|
|
5239
|
+
label: "Last trigger delivery status",
|
|
5240
|
+
icon: "mdi:send-check-outline",
|
|
5241
|
+
entityCategory: "diagnostic"
|
|
5242
|
+
}),
|
|
5243
|
+
(
|
|
5244
|
+
/** See {@link SyntheticLastTrigger.imageUrl} for the expired/unreachable
|
|
5245
|
+
* degrade: no value is published rather than a link that 404s. */
|
|
5246
|
+
{
|
|
5247
|
+
entity: "last_trigger_image",
|
|
5248
|
+
platform: "image",
|
|
5249
|
+
label: "Last trigger image"
|
|
5250
|
+
}),
|
|
5193
5251
|
...input.rules.map((rule) => ({
|
|
5194
5252
|
entity: ruleEntity(rule.id),
|
|
5195
5253
|
platform: "switch",
|
|
@@ -5435,6 +5493,29 @@ function projectSynthetic(input, nowMs) {
|
|
|
5435
5493
|
value: String(input.alertsActiveCount)
|
|
5436
5494
|
}
|
|
5437
5495
|
];
|
|
5496
|
+
if (input.lastTrigger !== null) {
|
|
5497
|
+
const t = input.lastTrigger;
|
|
5498
|
+
values.push({
|
|
5499
|
+
topic: stateTopic(nc, "last_trigger_at"),
|
|
5500
|
+
value: new Date(t.at).toISOString()
|
|
5501
|
+
}, {
|
|
5502
|
+
topic: stateTopic(nc, "last_trigger_rule"),
|
|
5503
|
+
value: t.ruleName
|
|
5504
|
+
}, {
|
|
5505
|
+
topic: stateTopic(nc, "last_trigger_camera"),
|
|
5506
|
+
value: t.deviceName
|
|
5507
|
+
}, {
|
|
5508
|
+
topic: stateTopic(nc, "last_trigger_label"),
|
|
5509
|
+
value: t.label
|
|
5510
|
+
}, {
|
|
5511
|
+
topic: stateTopic(nc, "last_trigger_status"),
|
|
5512
|
+
value: t.status
|
|
5513
|
+
});
|
|
5514
|
+
if (t.imageUrl !== null) values.push({
|
|
5515
|
+
topic: stateTopic(nc, "last_trigger_image"),
|
|
5516
|
+
value: t.imageUrl
|
|
5517
|
+
});
|
|
5518
|
+
}
|
|
5438
5519
|
if (input.alertsLastTitle !== null) values.push({
|
|
5439
5520
|
topic: stateTopic(srv, "alerts_last_title"),
|
|
5440
5521
|
value: input.alertsLastTitle
|
|
@@ -6254,7 +6335,7 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
6254
6335
|
* `brokerIds` is every enabled broker, because they belong to the server
|
|
6255
6336
|
* and not to a membership the operator picks per camera.
|
|
6256
6337
|
*/
|
|
6257
|
-
const synthetic = await this.buildSynthetic(snoozes);
|
|
6338
|
+
const synthetic = await this.buildSynthetic(snoozes, byId);
|
|
6258
6339
|
if (synthetic !== null) for (const plan of syntheticPlans(synthetic)) {
|
|
6259
6340
|
exported.set(plan.deviceKey, {
|
|
6260
6341
|
plan,
|
|
@@ -6337,7 +6418,7 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
6337
6418
|
* Returns `null` only when NOTHING answered, so the devices are not
|
|
6338
6419
|
* announced empty on a hub that is still booting.
|
|
6339
6420
|
*/
|
|
6340
|
-
async buildSynthetic(snoozes) {
|
|
6421
|
+
async buildSynthetic(snoozes, byId) {
|
|
6341
6422
|
const rules = await this.ctx.api.notificationRules.listRules.query({}).then((r) => r.rules.map((rule) => ({
|
|
6342
6423
|
id: rule.id,
|
|
6343
6424
|
name: rule.name,
|
|
@@ -6364,12 +6445,14 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
6364
6445
|
}).catch(() => null);
|
|
6365
6446
|
const server = await this.ctx.api.serverManagement.getServerPackageStatus.query().catch(() => null);
|
|
6366
6447
|
const alerts = await this.readActiveAlerts();
|
|
6448
|
+
const lastTrigger = await this.buildLastTrigger(byId);
|
|
6367
6449
|
if (rules === null && topology === null && addons === null && server === null) return null;
|
|
6368
6450
|
this.syntheticRules = rules ?? this.syntheticRules;
|
|
6369
6451
|
const { nodes, oldestFootageMs } = await this.buildSyntheticNodes(topology ?? []);
|
|
6370
6452
|
return {
|
|
6371
6453
|
rules: rules ?? this.syntheticRules,
|
|
6372
6454
|
snoozed: anySnoozed,
|
|
6455
|
+
lastTrigger,
|
|
6373
6456
|
nodes,
|
|
6374
6457
|
addonsRunning: addons?.running ?? 0,
|
|
6375
6458
|
addonsFailed: addons?.failed ?? 0,
|
|
@@ -6383,6 +6466,44 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
6383
6466
|
};
|
|
6384
6467
|
}
|
|
6385
6468
|
/**
|
|
6469
|
+
* The most recent notification-rule trigger — "conoscere gli ultimi
|
|
6470
|
+
* trigger, le ultime immagini... e da quale label" (operator, 2026-08-25).
|
|
6471
|
+
*
|
|
6472
|
+
* `getHistory` is already newest-first (§3.2), so `limit: 1` is the whole
|
|
6473
|
+
* question. Best-effort like every other synthetic source here: a failed
|
|
6474
|
+
* read costs only these six entities, never the rest of the device.
|
|
6475
|
+
*/
|
|
6476
|
+
async buildLastTrigger(byId) {
|
|
6477
|
+
try {
|
|
6478
|
+
const { entries } = await this.ctx.api.notificationRules.getHistory.query({ filter: { limit: 1 } });
|
|
6479
|
+
const entry = entries[0];
|
|
6480
|
+
if (entry === void 0) return null;
|
|
6481
|
+
const label = entry.subject.label ?? entry.subject.className;
|
|
6482
|
+
const artifactId = entry.artifactIds?.[0];
|
|
6483
|
+
const imageUrl = artifactId === void 0 ? null : await this.ctx.api.notificationRules.resolveArtifactUrl.query({ artifactId }).then((r) => r.url).catch((err) => {
|
|
6484
|
+
this.ctx.logger.debug("ha-export: could not resolve the last trigger image url", {
|
|
6485
|
+
tags: { deviceId: entry.deviceId },
|
|
6486
|
+
meta: {
|
|
6487
|
+
artifactId,
|
|
6488
|
+
error: errMsg(err)
|
|
6489
|
+
}
|
|
6490
|
+
});
|
|
6491
|
+
return null;
|
|
6492
|
+
});
|
|
6493
|
+
return {
|
|
6494
|
+
at: entry.createdAt,
|
|
6495
|
+
ruleName: entry.ruleName,
|
|
6496
|
+
deviceName: byId.get(entry.deviceId)?.name ?? `camera ${entry.deviceId}`,
|
|
6497
|
+
label,
|
|
6498
|
+
status: entry.status,
|
|
6499
|
+
imageUrl
|
|
6500
|
+
};
|
|
6501
|
+
} catch (err) {
|
|
6502
|
+
this.ctx.logger.warn("ha-export: could not read the last notification trigger", { meta: { error: errMsg(err) } });
|
|
6503
|
+
return null;
|
|
6504
|
+
}
|
|
6505
|
+
}
|
|
6506
|
+
/**
|
|
6386
6507
|
* `alerts.list` — the conditions nobody said (Task D1). Best-effort: a
|
|
6387
6508
|
* failed read yields "nothing active", not a fabricated absence dressed up
|
|
6388
6509
|
* as a clean one — this addon logs the drop rather than pretending the
|