@camstack/addon-provider-homeassistant 1.2.647 → 1.2.648
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/dist/addon.js +1 -1
- package/dist/addon.mjs +1 -1
- package/dist/{ha-broker-source-BFFi4NDH.js → ha-broker-source-Ba20mhmb.js} +211 -3
- package/dist/{ha-broker-source-B6wqfEUz.mjs → ha-broker-source-iLA_DsW8.mjs} +200 -4
- package/dist/ha-export/ha-export.addon.js +137 -60
- package/dist/ha-export/ha-export.addon.mjs +137 -60
- package/i18n/en.json +0 -2
- package/i18n/it.json +0 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.648] - 2026-11-02
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Home Assistant export: the Hub URL setting is gone; each Home Assistant instance now chooses whether its image links use the Internal address or the Public endpoint of the Network page.
|
|
15
|
+
|
|
10
16
|
## [1.2.647] - 2026-11-02
|
|
11
17
|
|
|
12
18
|
### Changed
|
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Edit these from System, Addons, this addon, Settings (a device's own settings ar
|
|
|
23
23
|
- Home Assistant broker: Brokers are managed separately
|
|
24
24
|
- Home Assistant connection: Auth mode, HA base URL, Long-lived access token, Supervisor token
|
|
25
25
|
- Home Assistant instances: No brokers
|
|
26
|
-
- Connection:
|
|
26
|
+
- Connection: per Home Assistant instance, the hub address role its image links use (Internal, or Public endpoint)
|
|
27
27
|
- Advanced: Reconcile interval (s)
|
|
28
28
|
|
|
29
29
|
## Requirements
|
package/dist/addon.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_ha_broker_source = require("./ha-broker-source-
|
|
2
|
+
const require_ha_broker_source = require("./ha-broker-source-Ba20mhmb.js");
|
|
3
3
|
let node_crypto = require("node:crypto");
|
|
4
4
|
let node_zlib = require("node:zlib");
|
|
5
5
|
//#region src/ha-admin-link.ts
|
package/dist/addon.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as humiditySensorCapability, $t as
|
|
1
|
+
import { $ as humiditySensorCapability, $t as discriminatedUnion, A as brokerCapability, At as tamperCapability, C as alarmPanelCapability, Ct as runningFromMediaPlayer, D as bestLocationMatch, E as batteryCapability, Et as scriptRunnerCapability, F as carbonMonoxideCapability, Ft as vacuumControlCapability, G as deviceAdoptionCapability, Gt as defineEntryKind, H as controlCapability, Ht as baseUnitOf, I as climateControlCapability, It as valveCapability, J as eventEmitterCapability, Kt as defineRecordKinds, L as colorCapability, Lt as vibrationCapability, M as buildDeviceAdminUrl, Mt as tryConvertDelta, N as buttonCapability, Nt as tryConvertUnit, O as binaryCapability, Ot as smokeCapability, P as calendarSourceCapability, Pt as updateCapability, Q as humidifierCapability, Qt as boolean, R as connectivityCapability, Rt as waterHeaterCapability, S as airQualitySensorCapability, St as runningFromLawnMower, T as automationControlCapability, Tt as runningStateCapability, U as coverCapability, Ut as createEvent, V as contactCapability, Vt as RecordSecretUnavailableError, W as deviceAdminLinkCapability, X as floodCapability, Xt as _enum, Y as fanControlCapability, Z as gasCapability, Zt as array, a as BaseDevice, an as union, at as motionCapability, b as accessoriesCapability, bt as resolveAttachmentUrls, cn as DeviceFeature, ct as notifierCapability, dn as errMsg, dt as parseAdminBase, en as literal, et as imageCapability, fn as EventCategory, ft as powerMeterCapability, gt as pressureSensorCapability, ht as presenceCapability, in as string, it as mediaPlayerCapability, j as buildAddonRouteProvider, jt as temperatureSensorCapability, k as brightnessCapability, kt as switchCapability, ln as DeviceRole, lt as numericSensorCapability, mt as prepareNotification, nn as object, nt as lawnMowerControlCapability, o as BaseDeviceProvider, on as unknown, ot as normalizeUnit, p as EnumSensorDateTimeFormatSchema, q as enumSensorCapability, qt as entryBody, rn as record, rt as lockControlCapability, sn as url, st as notificationOutputCapability, t as HA_BROKER_KIND, tn as number, tt as ingestReading, un as DeviceType, v as RUNNING_STATE_COLD, vt as publicEndpointReaderFrom, w as ambientLightSensorCapability, wt as runningFromVacuumState, x as addonRoutesCapability, y as TargetSchema, yt as quantityFromUnit, zt as weatherCapability } from "./ha-broker-source-iLA_DsW8.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { brotliCompressSync, deflateSync, gzipSync } from "node:zlib";
|
|
4
4
|
//#region src/ha-admin-link.ts
|
|
@@ -5972,7 +5972,7 @@ var ZodIssueCode = {
|
|
|
5972
5972
|
var ZodFirstPartyTypeKind;
|
|
5973
5973
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5974
5974
|
//#endregion
|
|
5975
|
-
//#region ../types/dist/sleep-
|
|
5975
|
+
//#region ../types/dist/sleep-Bei295Bz.mjs
|
|
5976
5976
|
/**
|
|
5977
5977
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5978
5978
|
* window to float samples (D455).
|
|
@@ -6488,6 +6488,53 @@ function emitReadiness(bus, params) {
|
|
|
6488
6488
|
ts
|
|
6489
6489
|
}));
|
|
6490
6490
|
}
|
|
6491
|
+
/** Why a catalogue entry is not a runnable body of its declared kind. */
|
|
6492
|
+
var AddonMigrationDefinitionError = class extends Error {
|
|
6493
|
+
id;
|
|
6494
|
+
constructor(id, message) {
|
|
6495
|
+
super(`${id}: ${message}`);
|
|
6496
|
+
this.id = id;
|
|
6497
|
+
this.name = "AddonMigrationDefinitionError";
|
|
6498
|
+
}
|
|
6499
|
+
};
|
|
6500
|
+
/**
|
|
6501
|
+
* Bind a catalogue to the addon's ports. Throws {@link AddonMigrationDefinitionError} when
|
|
6502
|
+
* an entry's `bodyKind` does not match the body it carries, or is `sql` (a hub-only
|
|
6503
|
+
* body). A throwing body never escapes `execute`: it is reported as a failed result.
|
|
6504
|
+
*/
|
|
6505
|
+
function defineAddonMigrations(catalogue, ports) {
|
|
6506
|
+
return catalogue.map((entry) => {
|
|
6507
|
+
const { meta } = entry;
|
|
6508
|
+
if ("plan" in entry) {
|
|
6509
|
+
if (meta.bodyKind !== "plan") throw new AddonMigrationDefinitionError(meta.id, `carries a plan body but declares "${meta.bodyKind}"`);
|
|
6510
|
+
return {
|
|
6511
|
+
meta,
|
|
6512
|
+
execute: () => guarded(async () => ({
|
|
6513
|
+
kind: "plan",
|
|
6514
|
+
plan: await entry.plan(ports)
|
|
6515
|
+
}))
|
|
6516
|
+
};
|
|
6517
|
+
}
|
|
6518
|
+
if (meta.bodyKind !== "procedure") throw new AddonMigrationDefinitionError(meta.id, `carries a procedure body but declares "${meta.bodyKind}"`);
|
|
6519
|
+
return {
|
|
6520
|
+
meta,
|
|
6521
|
+
execute: () => guarded(async () => ({
|
|
6522
|
+
kind: "procedure",
|
|
6523
|
+
result: await entry.procedure(ports)
|
|
6524
|
+
}))
|
|
6525
|
+
};
|
|
6526
|
+
});
|
|
6527
|
+
}
|
|
6528
|
+
async function guarded(work) {
|
|
6529
|
+
try {
|
|
6530
|
+
return await work();
|
|
6531
|
+
} catch (err) {
|
|
6532
|
+
return {
|
|
6533
|
+
kind: "failed",
|
|
6534
|
+
error: errMsg(err)
|
|
6535
|
+
};
|
|
6536
|
+
}
|
|
6537
|
+
}
|
|
6491
6538
|
var NOT_RUN = {
|
|
6492
6539
|
declared: false,
|
|
6493
6540
|
snapshot: null,
|
|
@@ -14124,6 +14171,16 @@ var MigrationPlanSchema = object({ edits: array(discriminatedUnion("op", [
|
|
|
14124
14171
|
precondition: PreconditionSchema
|
|
14125
14172
|
})
|
|
14126
14173
|
])) });
|
|
14174
|
+
/** `JSON.stringify` with object keys sorted, so equal values spell the same. */
|
|
14175
|
+
function canonicalJson(value) {
|
|
14176
|
+
if (Array.isArray(value)) return `[${value.map((item) => canonicalJson(item)).join(",")}]`;
|
|
14177
|
+
if (typeof value === "object" && value !== null) return `{${Object.entries(value).filter(([, item]) => item !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(",")}}`;
|
|
14178
|
+
return JSON.stringify(value) ?? "null";
|
|
14179
|
+
}
|
|
14180
|
+
/** The precondition string for a value a plan read (`null` when it read nothing). */
|
|
14181
|
+
function preconditionOf(value) {
|
|
14182
|
+
return canonicalJson(value ?? null);
|
|
14183
|
+
}
|
|
14127
14184
|
/**
|
|
14128
14185
|
* What a `procedure` body reports. `already-done` means the rerun observed the
|
|
14129
14186
|
* work finished (a crash between the work and the ledger row, or a manual fix)
|
|
@@ -16072,8 +16129,18 @@ var LogChannelDescriptorSchema = object({
|
|
|
16072
16129
|
* the operator narrows to one camera, sees nothing, and concludes the code
|
|
16073
16130
|
* path was never taken.
|
|
16074
16131
|
*/
|
|
16075
|
-
perDevice: boolean()
|
|
16076
|
-
|
|
16132
|
+
perDevice: boolean(),
|
|
16133
|
+
/**
|
|
16134
|
+
* What the channel is FOR, so the UI can list the channels an addon offers.
|
|
16135
|
+
*
|
|
16136
|
+
* - `diagnostic` (the default): a per-component diagnostic armed from the
|
|
16137
|
+
* Levels page, emitted at its declared level.
|
|
16138
|
+
* - `debug`: a `debug.<channel>` dump watched live for a short window
|
|
16139
|
+
* (`ctx.debug`). Always per-device, and its lines are denied by the log
|
|
16140
|
+
* ring unless somebody is watching that device.
|
|
16141
|
+
*/
|
|
16142
|
+
kind: _enum(["diagnostic", "debug"]).default("diagnostic")
|
|
16143
|
+
}).refine((d) => d.kind !== "debug" || d.name.startsWith("debug.") && d.perDevice, { message: "a debug channel is named debug.<channel> and is per-device" });
|
|
16077
16144
|
/**
|
|
16078
16145
|
* An armed window over one channel, as the document hands it to a mirror.
|
|
16079
16146
|
*
|
|
@@ -51615,6 +51682,45 @@ var deviceProviderCapability = {
|
|
|
51615
51682
|
}
|
|
51616
51683
|
};
|
|
51617
51684
|
/**
|
|
51685
|
+
* What the passive listener of `local-network` saw on the wire, before any
|
|
51686
|
+
* provider classified it. Device-manager turns an observation into the hits
|
|
51687
|
+
* of the providers whose declared discovery modes it matches.
|
|
51688
|
+
*/
|
|
51689
|
+
var DiscoveryObservationSchema = discriminatedUnion("kind", [object({
|
|
51690
|
+
kind: literal("mdns"),
|
|
51691
|
+
/** Normalised DNS-SD service type, e.g. `_esphomelib._tcp`. */
|
|
51692
|
+
serviceType: string(),
|
|
51693
|
+
instance: string(),
|
|
51694
|
+
host: string(),
|
|
51695
|
+
/** The A record address when the announcement carried one. */
|
|
51696
|
+
address: string().nullable(),
|
|
51697
|
+
port: number().int(),
|
|
51698
|
+
txt: record(string(), string())
|
|
51699
|
+
}), object({
|
|
51700
|
+
kind: literal("ssdp"),
|
|
51701
|
+
/** Sender address of the datagram. */
|
|
51702
|
+
host: string(),
|
|
51703
|
+
port: number().int(),
|
|
51704
|
+
/** `NT` of a NOTIFY, `ST` of a search response. */
|
|
51705
|
+
target: string(),
|
|
51706
|
+
headers: record(string(), string())
|
|
51707
|
+
})]);
|
|
51708
|
+
object({
|
|
51709
|
+
addonId: string(),
|
|
51710
|
+
candidate: DiscoveryCandidateSchema,
|
|
51711
|
+
firstSeenAt: number(),
|
|
51712
|
+
lastSeenAt: number(),
|
|
51713
|
+
dismissed: boolean()
|
|
51714
|
+
});
|
|
51715
|
+
var DiscoveryProposalSchema = object({
|
|
51716
|
+
id: string(),
|
|
51717
|
+
addonId: string(),
|
|
51718
|
+
candidate: DiscoveryCandidateSchema,
|
|
51719
|
+
firstSeenAt: number(),
|
|
51720
|
+
lastSeenAt: number(),
|
|
51721
|
+
dismissed: boolean()
|
|
51722
|
+
});
|
|
51723
|
+
/**
|
|
51618
51724
|
* Device Manager capability — hub-side singleton that unifies device persistence,
|
|
51619
51725
|
* live registry access, and all management operations into a single tRPC surface.
|
|
51620
51726
|
*
|
|
@@ -52343,6 +52449,19 @@ method(object({
|
|
|
52343
52449
|
}), object({ candidates: array(DiscoveryCandidateSchema).readonly() }), {
|
|
52344
52450
|
kind: "mutation",
|
|
52345
52451
|
auth: "admin"
|
|
52452
|
+
}), method(object({}), object({ proposals: array(DiscoveryProposalSchema).readonly() }), { auth: "admin" }), method(object({ id: string() }), object({ dismissed: boolean() }), {
|
|
52453
|
+
kind: "mutation",
|
|
52454
|
+
auth: "admin"
|
|
52455
|
+
}), method(object({ observations: array(DiscoveryObservationSchema).max(512) }), object({ proposed: number().int() }), {
|
|
52456
|
+
kind: "mutation",
|
|
52457
|
+
auth: "admin"
|
|
52458
|
+
}), method(object({}), object({
|
|
52459
|
+
providers: number().int(),
|
|
52460
|
+
proposed: number().int()
|
|
52461
|
+
}), {
|
|
52462
|
+
kind: "mutation",
|
|
52463
|
+
auth: "admin",
|
|
52464
|
+
timeoutMs: 12e4
|
|
52346
52465
|
}), method(object({ addonId: string() }), object({ deviceType: _enum(DeviceType).nullable() }), { auth: "admin" }), method(object({ addonId: string() }), unknown(), { auth: "admin" }), method(object({
|
|
52347
52466
|
deviceId: number(),
|
|
52348
52467
|
key: string(),
|
|
@@ -76690,6 +76809,40 @@ var LoggingSettingsStateSchema = object({
|
|
|
76690
76809
|
opsLogRetentionDays: number().int().min(0),
|
|
76691
76810
|
persisted: boolean()
|
|
76692
76811
|
});
|
|
76812
|
+
/**
|
|
76813
|
+
* Debug WATCH — a short, per-device window over `debug.<channel>` lines.
|
|
76814
|
+
*
|
|
76815
|
+
* `start` opens (or re-arms) a 10 minute window; `rearm` extends a LIVE one
|
|
76816
|
+
* and never revives a lapsed one; `stop` closes it; `list` only reads. No
|
|
76817
|
+
* chain of re-arms outlives 60 minutes from the first start. The hub holds the
|
|
76818
|
+
* watches in memory and pushes the interest to every node, so a watch lapses
|
|
76819
|
+
* by itself and survives nothing — a restart is a stop.
|
|
76820
|
+
*/
|
|
76821
|
+
var DebugWatchInputSchema = object({
|
|
76822
|
+
action: _enum([
|
|
76823
|
+
"list",
|
|
76824
|
+
"start",
|
|
76825
|
+
"rearm",
|
|
76826
|
+
"stop"
|
|
76827
|
+
]),
|
|
76828
|
+
/** Full channel names, `debug.<channel>`, each declared with `kind: 'debug'`. */
|
|
76829
|
+
channels: array(string().min(1)).readonly().default([]),
|
|
76830
|
+
/** Numeric device ids. Required (non-empty) for `start`. */
|
|
76831
|
+
deviceIds: array(number().int()).readonly().default([])
|
|
76832
|
+
});
|
|
76833
|
+
var DebugWatchStateSchema = object({
|
|
76834
|
+
channel: string(),
|
|
76835
|
+
deviceIds: array(number().int()).readonly(),
|
|
76836
|
+
startedAtMs: number(),
|
|
76837
|
+
untilMs: number(),
|
|
76838
|
+
capAtMs: number()
|
|
76839
|
+
});
|
|
76840
|
+
var DebugWatchResultSchema = object({
|
|
76841
|
+
nowMs: number(),
|
|
76842
|
+
watches: array(DebugWatchStateSchema).readonly(),
|
|
76843
|
+
/** Channels a `rearm` found already lapsed: nothing was revived. */
|
|
76844
|
+
lapsed: array(string()).readonly()
|
|
76845
|
+
});
|
|
76693
76846
|
method(_void(), SystemInfoSchema), method(_void(), HealthStatusSchema), method(_void(), FeatureManifestSchema), method(_void(), array(NetworkAddressSchema).readonly()), method(_void(), unknown().nullable(), { auth: "admin" }), method(record(string(), unknown()), _null(), {
|
|
76694
76847
|
kind: "mutation",
|
|
76695
76848
|
auth: "admin"
|
|
@@ -76705,6 +76858,9 @@ method(_void(), SystemInfoSchema), method(_void(), HealthStatusSchema), method(_
|
|
|
76705
76858
|
}), method(_void(), RequestCensusStatusSchema, { auth: "admin" }), method(_void(), array(ReportedLoadContributionSchema).readonly(), { auth: "admin" }), method(_void(), array(ReportedFailureContributionSchema).readonly(), { auth: "admin" }), method(GetLoggingSettingsInputSchema, LoggingSettingsStateSchema, { auth: "admin" }), method(SetLoggingSettingsInputSchema, LoggingSettingsStateSchema, {
|
|
76706
76859
|
kind: "mutation",
|
|
76707
76860
|
auth: "admin"
|
|
76861
|
+
}), method(DebugWatchInputSchema, DebugWatchResultSchema, {
|
|
76862
|
+
kind: "mutation",
|
|
76863
|
+
auth: "admin"
|
|
76708
76864
|
});
|
|
76709
76865
|
/**
|
|
76710
76866
|
* Tamper / case-open detection sensor. Drives Home Assistant
|
|
@@ -83814,6 +83970,13 @@ Object.freeze({
|
|
|
83814
83970
|
access: "view",
|
|
83815
83971
|
role: "admin"
|
|
83816
83972
|
},
|
|
83973
|
+
"deviceManager.dismissDiscoveryProposal": {
|
|
83974
|
+
capName: "device-manager",
|
|
83975
|
+
capScope: "system",
|
|
83976
|
+
addonId: null,
|
|
83977
|
+
access: "create",
|
|
83978
|
+
role: "admin"
|
|
83979
|
+
},
|
|
83817
83980
|
"deviceManager.enable": {
|
|
83818
83981
|
capName: "device-manager",
|
|
83819
83982
|
capScope: "system",
|
|
@@ -83996,6 +84159,13 @@ Object.freeze({
|
|
|
83996
84159
|
access: "view",
|
|
83997
84160
|
role: "viewer"
|
|
83998
84161
|
},
|
|
84162
|
+
"deviceManager.listDiscoveryProposals": {
|
|
84163
|
+
capName: "device-manager",
|
|
84164
|
+
capScope: "system",
|
|
84165
|
+
addonId: null,
|
|
84166
|
+
access: "view",
|
|
84167
|
+
role: "admin"
|
|
84168
|
+
},
|
|
83999
84169
|
"deviceManager.listLocationDescriptions": {
|
|
84000
84170
|
capName: "device-manager",
|
|
84001
84171
|
capScope: "system",
|
|
@@ -84150,6 +84320,13 @@ Object.freeze({
|
|
|
84150
84320
|
access: "create",
|
|
84151
84321
|
role: "admin"
|
|
84152
84322
|
},
|
|
84323
|
+
"deviceManager.reportDiscoveryObservations": {
|
|
84324
|
+
capName: "device-manager",
|
|
84325
|
+
capScope: "system",
|
|
84326
|
+
addonId: null,
|
|
84327
|
+
access: "create",
|
|
84328
|
+
role: "admin"
|
|
84329
|
+
},
|
|
84153
84330
|
"deviceManager.runDeviceAction": {
|
|
84154
84331
|
capName: "device-manager",
|
|
84155
84332
|
capScope: "system",
|
|
@@ -84157,6 +84334,13 @@ Object.freeze({
|
|
|
84157
84334
|
access: "create",
|
|
84158
84335
|
role: "admin"
|
|
84159
84336
|
},
|
|
84337
|
+
"deviceManager.runDiscoverySweep": {
|
|
84338
|
+
capName: "device-manager",
|
|
84339
|
+
capScope: "system",
|
|
84340
|
+
addonId: null,
|
|
84341
|
+
access: "create",
|
|
84342
|
+
role: "admin"
|
|
84343
|
+
},
|
|
84160
84344
|
"deviceManager.setChildLayout": {
|
|
84161
84345
|
capName: "device-manager",
|
|
84162
84346
|
capScope: "system",
|
|
@@ -90744,6 +90928,13 @@ Object.freeze({
|
|
|
90744
90928
|
access: "create",
|
|
90745
90929
|
role: "viewer"
|
|
90746
90930
|
},
|
|
90931
|
+
"system.debugWatch": {
|
|
90932
|
+
capName: "system",
|
|
90933
|
+
capScope: "system",
|
|
90934
|
+
addonId: null,
|
|
90935
|
+
access: "create",
|
|
90936
|
+
role: "admin"
|
|
90937
|
+
},
|
|
90747
90938
|
"system.detectSiteLocation": {
|
|
90748
90939
|
capName: "system",
|
|
90749
90940
|
capScope: "system",
|
|
@@ -94673,6 +94864,11 @@ Object.freeze({
|
|
|
94673
94864
|
form: "single",
|
|
94674
94865
|
optional: false
|
|
94675
94866
|
}],
|
|
94867
|
+
"system.debugWatch": [{
|
|
94868
|
+
name: "deviceIds",
|
|
94869
|
+
form: "array",
|
|
94870
|
+
optional: true
|
|
94871
|
+
}],
|
|
94676
94872
|
"tamper.getStatus": [{
|
|
94677
94873
|
name: "deviceId",
|
|
94678
94874
|
form: "single",
|
|
@@ -97157,6 +97353,12 @@ Object.defineProperty(exports, "createEvent", {
|
|
|
97157
97353
|
return createEvent;
|
|
97158
97354
|
}
|
|
97159
97355
|
});
|
|
97356
|
+
Object.defineProperty(exports, "defineAddonMigrations", {
|
|
97357
|
+
enumerable: true,
|
|
97358
|
+
get: function() {
|
|
97359
|
+
return defineAddonMigrations;
|
|
97360
|
+
}
|
|
97361
|
+
});
|
|
97160
97362
|
Object.defineProperty(exports, "defineEntryKind", {
|
|
97161
97363
|
enumerable: true,
|
|
97162
97364
|
get: function() {
|
|
@@ -97361,6 +97563,12 @@ Object.defineProperty(exports, "powerMeterCapability", {
|
|
|
97361
97563
|
return powerMeterCapability;
|
|
97362
97564
|
}
|
|
97363
97565
|
});
|
|
97566
|
+
Object.defineProperty(exports, "preconditionOf", {
|
|
97567
|
+
enumerable: true,
|
|
97568
|
+
get: function() {
|
|
97569
|
+
return preconditionOf;
|
|
97570
|
+
}
|
|
97571
|
+
});
|
|
97364
97572
|
Object.defineProperty(exports, "prepareNotification", {
|
|
97365
97573
|
enumerable: true,
|
|
97366
97574
|
get: function() {
|
|
@@ -5971,7 +5971,7 @@ var ZodIssueCode = {
|
|
|
5971
5971
|
var ZodFirstPartyTypeKind;
|
|
5972
5972
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5973
5973
|
//#endregion
|
|
5974
|
-
//#region ../types/dist/sleep-
|
|
5974
|
+
//#region ../types/dist/sleep-Bei295Bz.mjs
|
|
5975
5975
|
/**
|
|
5976
5976
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5977
5977
|
* window to float samples (D455).
|
|
@@ -6487,6 +6487,53 @@ function emitReadiness(bus, params) {
|
|
|
6487
6487
|
ts
|
|
6488
6488
|
}));
|
|
6489
6489
|
}
|
|
6490
|
+
/** Why a catalogue entry is not a runnable body of its declared kind. */
|
|
6491
|
+
var AddonMigrationDefinitionError = class extends Error {
|
|
6492
|
+
id;
|
|
6493
|
+
constructor(id, message) {
|
|
6494
|
+
super(`${id}: ${message}`);
|
|
6495
|
+
this.id = id;
|
|
6496
|
+
this.name = "AddonMigrationDefinitionError";
|
|
6497
|
+
}
|
|
6498
|
+
};
|
|
6499
|
+
/**
|
|
6500
|
+
* Bind a catalogue to the addon's ports. Throws {@link AddonMigrationDefinitionError} when
|
|
6501
|
+
* an entry's `bodyKind` does not match the body it carries, or is `sql` (a hub-only
|
|
6502
|
+
* body). A throwing body never escapes `execute`: it is reported as a failed result.
|
|
6503
|
+
*/
|
|
6504
|
+
function defineAddonMigrations(catalogue, ports) {
|
|
6505
|
+
return catalogue.map((entry) => {
|
|
6506
|
+
const { meta } = entry;
|
|
6507
|
+
if ("plan" in entry) {
|
|
6508
|
+
if (meta.bodyKind !== "plan") throw new AddonMigrationDefinitionError(meta.id, `carries a plan body but declares "${meta.bodyKind}"`);
|
|
6509
|
+
return {
|
|
6510
|
+
meta,
|
|
6511
|
+
execute: () => guarded(async () => ({
|
|
6512
|
+
kind: "plan",
|
|
6513
|
+
plan: await entry.plan(ports)
|
|
6514
|
+
}))
|
|
6515
|
+
};
|
|
6516
|
+
}
|
|
6517
|
+
if (meta.bodyKind !== "procedure") throw new AddonMigrationDefinitionError(meta.id, `carries a procedure body but declares "${meta.bodyKind}"`);
|
|
6518
|
+
return {
|
|
6519
|
+
meta,
|
|
6520
|
+
execute: () => guarded(async () => ({
|
|
6521
|
+
kind: "procedure",
|
|
6522
|
+
result: await entry.procedure(ports)
|
|
6523
|
+
}))
|
|
6524
|
+
};
|
|
6525
|
+
});
|
|
6526
|
+
}
|
|
6527
|
+
async function guarded(work) {
|
|
6528
|
+
try {
|
|
6529
|
+
return await work();
|
|
6530
|
+
} catch (err) {
|
|
6531
|
+
return {
|
|
6532
|
+
kind: "failed",
|
|
6533
|
+
error: errMsg(err)
|
|
6534
|
+
};
|
|
6535
|
+
}
|
|
6536
|
+
}
|
|
6490
6537
|
var NOT_RUN = {
|
|
6491
6538
|
declared: false,
|
|
6492
6539
|
snapshot: null,
|
|
@@ -14123,6 +14170,16 @@ var MigrationPlanSchema = object({ edits: array(discriminatedUnion("op", [
|
|
|
14123
14170
|
precondition: PreconditionSchema
|
|
14124
14171
|
})
|
|
14125
14172
|
])) });
|
|
14173
|
+
/** `JSON.stringify` with object keys sorted, so equal values spell the same. */
|
|
14174
|
+
function canonicalJson(value) {
|
|
14175
|
+
if (Array.isArray(value)) return `[${value.map((item) => canonicalJson(item)).join(",")}]`;
|
|
14176
|
+
if (typeof value === "object" && value !== null) return `{${Object.entries(value).filter(([, item]) => item !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(",")}}`;
|
|
14177
|
+
return JSON.stringify(value) ?? "null";
|
|
14178
|
+
}
|
|
14179
|
+
/** The precondition string for a value a plan read (`null` when it read nothing). */
|
|
14180
|
+
function preconditionOf(value) {
|
|
14181
|
+
return canonicalJson(value ?? null);
|
|
14182
|
+
}
|
|
14126
14183
|
/**
|
|
14127
14184
|
* What a `procedure` body reports. `already-done` means the rerun observed the
|
|
14128
14185
|
* work finished (a crash between the work and the ledger row, or a manual fix)
|
|
@@ -16071,8 +16128,18 @@ var LogChannelDescriptorSchema = object({
|
|
|
16071
16128
|
* the operator narrows to one camera, sees nothing, and concludes the code
|
|
16072
16129
|
* path was never taken.
|
|
16073
16130
|
*/
|
|
16074
|
-
perDevice: boolean()
|
|
16075
|
-
|
|
16131
|
+
perDevice: boolean(),
|
|
16132
|
+
/**
|
|
16133
|
+
* What the channel is FOR, so the UI can list the channels an addon offers.
|
|
16134
|
+
*
|
|
16135
|
+
* - `diagnostic` (the default): a per-component diagnostic armed from the
|
|
16136
|
+
* Levels page, emitted at its declared level.
|
|
16137
|
+
* - `debug`: a `debug.<channel>` dump watched live for a short window
|
|
16138
|
+
* (`ctx.debug`). Always per-device, and its lines are denied by the log
|
|
16139
|
+
* ring unless somebody is watching that device.
|
|
16140
|
+
*/
|
|
16141
|
+
kind: _enum(["diagnostic", "debug"]).default("diagnostic")
|
|
16142
|
+
}).refine((d) => d.kind !== "debug" || d.name.startsWith("debug.") && d.perDevice, { message: "a debug channel is named debug.<channel> and is per-device" });
|
|
16076
16143
|
/**
|
|
16077
16144
|
* An armed window over one channel, as the document hands it to a mirror.
|
|
16078
16145
|
*
|
|
@@ -51614,6 +51681,45 @@ var deviceProviderCapability = {
|
|
|
51614
51681
|
}
|
|
51615
51682
|
};
|
|
51616
51683
|
/**
|
|
51684
|
+
* What the passive listener of `local-network` saw on the wire, before any
|
|
51685
|
+
* provider classified it. Device-manager turns an observation into the hits
|
|
51686
|
+
* of the providers whose declared discovery modes it matches.
|
|
51687
|
+
*/
|
|
51688
|
+
var DiscoveryObservationSchema = discriminatedUnion("kind", [object({
|
|
51689
|
+
kind: literal("mdns"),
|
|
51690
|
+
/** Normalised DNS-SD service type, e.g. `_esphomelib._tcp`. */
|
|
51691
|
+
serviceType: string(),
|
|
51692
|
+
instance: string(),
|
|
51693
|
+
host: string(),
|
|
51694
|
+
/** The A record address when the announcement carried one. */
|
|
51695
|
+
address: string().nullable(),
|
|
51696
|
+
port: number().int(),
|
|
51697
|
+
txt: record(string(), string())
|
|
51698
|
+
}), object({
|
|
51699
|
+
kind: literal("ssdp"),
|
|
51700
|
+
/** Sender address of the datagram. */
|
|
51701
|
+
host: string(),
|
|
51702
|
+
port: number().int(),
|
|
51703
|
+
/** `NT` of a NOTIFY, `ST` of a search response. */
|
|
51704
|
+
target: string(),
|
|
51705
|
+
headers: record(string(), string())
|
|
51706
|
+
})]);
|
|
51707
|
+
object({
|
|
51708
|
+
addonId: string(),
|
|
51709
|
+
candidate: DiscoveryCandidateSchema,
|
|
51710
|
+
firstSeenAt: number(),
|
|
51711
|
+
lastSeenAt: number(),
|
|
51712
|
+
dismissed: boolean()
|
|
51713
|
+
});
|
|
51714
|
+
var DiscoveryProposalSchema = object({
|
|
51715
|
+
id: string(),
|
|
51716
|
+
addonId: string(),
|
|
51717
|
+
candidate: DiscoveryCandidateSchema,
|
|
51718
|
+
firstSeenAt: number(),
|
|
51719
|
+
lastSeenAt: number(),
|
|
51720
|
+
dismissed: boolean()
|
|
51721
|
+
});
|
|
51722
|
+
/**
|
|
51617
51723
|
* Device Manager capability — hub-side singleton that unifies device persistence,
|
|
51618
51724
|
* live registry access, and all management operations into a single tRPC surface.
|
|
51619
51725
|
*
|
|
@@ -52342,6 +52448,19 @@ method(object({
|
|
|
52342
52448
|
}), object({ candidates: array(DiscoveryCandidateSchema).readonly() }), {
|
|
52343
52449
|
kind: "mutation",
|
|
52344
52450
|
auth: "admin"
|
|
52451
|
+
}), method(object({}), object({ proposals: array(DiscoveryProposalSchema).readonly() }), { auth: "admin" }), method(object({ id: string() }), object({ dismissed: boolean() }), {
|
|
52452
|
+
kind: "mutation",
|
|
52453
|
+
auth: "admin"
|
|
52454
|
+
}), method(object({ observations: array(DiscoveryObservationSchema).max(512) }), object({ proposed: number().int() }), {
|
|
52455
|
+
kind: "mutation",
|
|
52456
|
+
auth: "admin"
|
|
52457
|
+
}), method(object({}), object({
|
|
52458
|
+
providers: number().int(),
|
|
52459
|
+
proposed: number().int()
|
|
52460
|
+
}), {
|
|
52461
|
+
kind: "mutation",
|
|
52462
|
+
auth: "admin",
|
|
52463
|
+
timeoutMs: 12e4
|
|
52345
52464
|
}), method(object({ addonId: string() }), object({ deviceType: _enum(DeviceType).nullable() }), { auth: "admin" }), method(object({ addonId: string() }), unknown(), { auth: "admin" }), method(object({
|
|
52346
52465
|
deviceId: number(),
|
|
52347
52466
|
key: string(),
|
|
@@ -76689,6 +76808,40 @@ var LoggingSettingsStateSchema = object({
|
|
|
76689
76808
|
opsLogRetentionDays: number().int().min(0),
|
|
76690
76809
|
persisted: boolean()
|
|
76691
76810
|
});
|
|
76811
|
+
/**
|
|
76812
|
+
* Debug WATCH — a short, per-device window over `debug.<channel>` lines.
|
|
76813
|
+
*
|
|
76814
|
+
* `start` opens (or re-arms) a 10 minute window; `rearm` extends a LIVE one
|
|
76815
|
+
* and never revives a lapsed one; `stop` closes it; `list` only reads. No
|
|
76816
|
+
* chain of re-arms outlives 60 minutes from the first start. The hub holds the
|
|
76817
|
+
* watches in memory and pushes the interest to every node, so a watch lapses
|
|
76818
|
+
* by itself and survives nothing — a restart is a stop.
|
|
76819
|
+
*/
|
|
76820
|
+
var DebugWatchInputSchema = object({
|
|
76821
|
+
action: _enum([
|
|
76822
|
+
"list",
|
|
76823
|
+
"start",
|
|
76824
|
+
"rearm",
|
|
76825
|
+
"stop"
|
|
76826
|
+
]),
|
|
76827
|
+
/** Full channel names, `debug.<channel>`, each declared with `kind: 'debug'`. */
|
|
76828
|
+
channels: array(string().min(1)).readonly().default([]),
|
|
76829
|
+
/** Numeric device ids. Required (non-empty) for `start`. */
|
|
76830
|
+
deviceIds: array(number().int()).readonly().default([])
|
|
76831
|
+
});
|
|
76832
|
+
var DebugWatchStateSchema = object({
|
|
76833
|
+
channel: string(),
|
|
76834
|
+
deviceIds: array(number().int()).readonly(),
|
|
76835
|
+
startedAtMs: number(),
|
|
76836
|
+
untilMs: number(),
|
|
76837
|
+
capAtMs: number()
|
|
76838
|
+
});
|
|
76839
|
+
var DebugWatchResultSchema = object({
|
|
76840
|
+
nowMs: number(),
|
|
76841
|
+
watches: array(DebugWatchStateSchema).readonly(),
|
|
76842
|
+
/** Channels a `rearm` found already lapsed: nothing was revived. */
|
|
76843
|
+
lapsed: array(string()).readonly()
|
|
76844
|
+
});
|
|
76692
76845
|
method(_void(), SystemInfoSchema), method(_void(), HealthStatusSchema), method(_void(), FeatureManifestSchema), method(_void(), array(NetworkAddressSchema).readonly()), method(_void(), unknown().nullable(), { auth: "admin" }), method(record(string(), unknown()), _null(), {
|
|
76693
76846
|
kind: "mutation",
|
|
76694
76847
|
auth: "admin"
|
|
@@ -76704,6 +76857,9 @@ method(_void(), SystemInfoSchema), method(_void(), HealthStatusSchema), method(_
|
|
|
76704
76857
|
}), method(_void(), RequestCensusStatusSchema, { auth: "admin" }), method(_void(), array(ReportedLoadContributionSchema).readonly(), { auth: "admin" }), method(_void(), array(ReportedFailureContributionSchema).readonly(), { auth: "admin" }), method(GetLoggingSettingsInputSchema, LoggingSettingsStateSchema, { auth: "admin" }), method(SetLoggingSettingsInputSchema, LoggingSettingsStateSchema, {
|
|
76705
76858
|
kind: "mutation",
|
|
76706
76859
|
auth: "admin"
|
|
76860
|
+
}), method(DebugWatchInputSchema, DebugWatchResultSchema, {
|
|
76861
|
+
kind: "mutation",
|
|
76862
|
+
auth: "admin"
|
|
76707
76863
|
});
|
|
76708
76864
|
/**
|
|
76709
76865
|
* Tamper / case-open detection sensor. Drives Home Assistant
|
|
@@ -83813,6 +83969,13 @@ Object.freeze({
|
|
|
83813
83969
|
access: "view",
|
|
83814
83970
|
role: "admin"
|
|
83815
83971
|
},
|
|
83972
|
+
"deviceManager.dismissDiscoveryProposal": {
|
|
83973
|
+
capName: "device-manager",
|
|
83974
|
+
capScope: "system",
|
|
83975
|
+
addonId: null,
|
|
83976
|
+
access: "create",
|
|
83977
|
+
role: "admin"
|
|
83978
|
+
},
|
|
83816
83979
|
"deviceManager.enable": {
|
|
83817
83980
|
capName: "device-manager",
|
|
83818
83981
|
capScope: "system",
|
|
@@ -83995,6 +84158,13 @@ Object.freeze({
|
|
|
83995
84158
|
access: "view",
|
|
83996
84159
|
role: "viewer"
|
|
83997
84160
|
},
|
|
84161
|
+
"deviceManager.listDiscoveryProposals": {
|
|
84162
|
+
capName: "device-manager",
|
|
84163
|
+
capScope: "system",
|
|
84164
|
+
addonId: null,
|
|
84165
|
+
access: "view",
|
|
84166
|
+
role: "admin"
|
|
84167
|
+
},
|
|
83998
84168
|
"deviceManager.listLocationDescriptions": {
|
|
83999
84169
|
capName: "device-manager",
|
|
84000
84170
|
capScope: "system",
|
|
@@ -84149,6 +84319,13 @@ Object.freeze({
|
|
|
84149
84319
|
access: "create",
|
|
84150
84320
|
role: "admin"
|
|
84151
84321
|
},
|
|
84322
|
+
"deviceManager.reportDiscoveryObservations": {
|
|
84323
|
+
capName: "device-manager",
|
|
84324
|
+
capScope: "system",
|
|
84325
|
+
addonId: null,
|
|
84326
|
+
access: "create",
|
|
84327
|
+
role: "admin"
|
|
84328
|
+
},
|
|
84152
84329
|
"deviceManager.runDeviceAction": {
|
|
84153
84330
|
capName: "device-manager",
|
|
84154
84331
|
capScope: "system",
|
|
@@ -84156,6 +84333,13 @@ Object.freeze({
|
|
|
84156
84333
|
access: "create",
|
|
84157
84334
|
role: "admin"
|
|
84158
84335
|
},
|
|
84336
|
+
"deviceManager.runDiscoverySweep": {
|
|
84337
|
+
capName: "device-manager",
|
|
84338
|
+
capScope: "system",
|
|
84339
|
+
addonId: null,
|
|
84340
|
+
access: "create",
|
|
84341
|
+
role: "admin"
|
|
84342
|
+
},
|
|
84159
84343
|
"deviceManager.setChildLayout": {
|
|
84160
84344
|
capName: "device-manager",
|
|
84161
84345
|
capScope: "system",
|
|
@@ -90743,6 +90927,13 @@ Object.freeze({
|
|
|
90743
90927
|
access: "create",
|
|
90744
90928
|
role: "viewer"
|
|
90745
90929
|
},
|
|
90930
|
+
"system.debugWatch": {
|
|
90931
|
+
capName: "system",
|
|
90932
|
+
capScope: "system",
|
|
90933
|
+
addonId: null,
|
|
90934
|
+
access: "create",
|
|
90935
|
+
role: "admin"
|
|
90936
|
+
},
|
|
90746
90937
|
"system.detectSiteLocation": {
|
|
90747
90938
|
capName: "system",
|
|
90748
90939
|
capScope: "system",
|
|
@@ -94672,6 +94863,11 @@ Object.freeze({
|
|
|
94672
94863
|
form: "single",
|
|
94673
94864
|
optional: false
|
|
94674
94865
|
}],
|
|
94866
|
+
"system.debugWatch": [{
|
|
94867
|
+
name: "deviceIds",
|
|
94868
|
+
form: "array",
|
|
94869
|
+
optional: true
|
|
94870
|
+
}],
|
|
94675
94871
|
"tamper.getStatus": [{
|
|
94676
94872
|
name: "deviceId",
|
|
94677
94873
|
form: "single",
|
|
@@ -96838,4 +97034,4 @@ async function listHomeAssistantBrokers(query) {
|
|
|
96838
97034
|
return (await query({})).filter((broker) => broker.kind === HA_BROKER_KIND);
|
|
96839
97035
|
}
|
|
96840
97036
|
//#endregion
|
|
96841
|
-
export { humiditySensorCapability as $,
|
|
97037
|
+
export { humiditySensorCapability as $, discriminatedUnion as $t, brokerCapability as A, tamperCapability as At, consumableKindOf as B, BaseAddon as Bt, alarmPanelCapability as C, runningFromMediaPlayer as Ct, bestLocationMatch as D, sendToExportedTarget as Dt, batteryCapability as E, scriptRunnerCapability as Et, carbonMonoxideCapability as F, vacuumControlCapability as Ft, deviceAdoptionCapability as G, defineEntryKind as Gt, controlCapability as H, baseUnitOf as Ht, climateControlCapability as I, valveCapability as It, eventEmitterCapability as J, isReadingQuantity as Jt, deviceExportCapability as K, defineRecordKinds as Kt, colorCapability as L, vibrationCapability as Lt, buildDeviceAdminUrl as M, tryConvertDelta as Mt, buttonCapability as N, tryConvertUnit as Nt, binaryCapability as O, smokeCapability as Ot, calendarSourceCapability as P, updateCapability as Pt, humidifierCapability as Q, boolean as Qt, connectivityCapability as R, waterHeaterCapability as Rt, airQualitySensorCapability as S, runningFromLawnMower as St, automationControlCapability as T, runningStateCapability as Tt, coverCapability as U, createEvent as Ut, contactCapability as V, RecordSecretUnavailableError as Vt, deviceAdminLinkCapability as W, defineAddonMigrations as Wt, floodCapability as X, _enum as Xt, fanControlCapability as Y, nodePin as Yt, gasCapability as Z, array as Zt, PLUG_APPLIANCE_TYPES as _, pruneDeletedEntities as _t, BaseDevice as a, union as an, motionCapability as at, accessoriesCapability as b, resolveAttachmentUrls as bt, COCO_TO_MACRO as c, DeviceFeature as cn, notifierCapability as ct, ConsumableItemSchema as d, errMsg as dn, parseAdminBase as dt, literal as en, imageCapability as et, ConsumableStatusSchema as f, EventCategory as fn, powerMeterCapability as ft, MediaPlayerRepeatSchema as g, pressureSensorCapability as gt, HvacModeSchema as h, presenceCapability as ht, AlarmArmModeSchema as i, string as in, mediaPlayerCapability as it, buildAddonRouteProvider as j, temperatureSensorCapability as jt, brightnessCapability as k, switchCapability as kt, CameraSwitchIdSchema as l, DeviceRole as ln, numericSensorCapability as lt, FanDirectionSchema as m, prepareNotification as mt, HA_PROVIDER_ADDON_ID as n, object as nn, lawnMowerControlCapability as nt, BaseDeviceProvider as o, unknown as on, normalizeUnit as ot, EnumSensorDateTimeFormatSchema as p, preconditionOf as pt, enumSensorCapability as q, entryBody as qt, listHomeAssistantBrokers as r, record as rn, lockControlCapability as rt, CAMERA_SWITCH_ORDER as s, url as sn, notificationOutputCapability as st, HA_BROKER_KIND as t, number as tn, ingestReading as tt, ConsumableFormSchema as u, DeviceType as un, oauthIntegrationCapability as ut, RUNNING_STATE_COLD as v, publicEndpointReaderFrom as vt, ambientLightSensorCapability as w, runningFromVacuumState as wt, addonRoutesCapability as x, runExportedAction as xt, TargetSchema as y, quantityFromUnit as yt, consumableFormOf as z, weatherCapability as zt };
|
|
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
enumerable: true
|
|
25
25
|
}) : target, mod));
|
|
26
26
|
//#endregion
|
|
27
|
-
const require_ha_broker_source = require("../ha-broker-source-
|
|
27
|
+
const require_ha_broker_source = require("../ha-broker-source-Ba20mhmb.js");
|
|
28
28
|
let node_crypto = require("node:crypto");
|
|
29
29
|
require("node:zlib");
|
|
30
30
|
let node_fs = require("node:fs");
|
|
@@ -4940,6 +4940,49 @@ async function call(slice, capName, make) {
|
|
|
4940
4940
|
}
|
|
4941
4941
|
return { ok: true };
|
|
4942
4942
|
}
|
|
4943
|
+
//#endregion
|
|
4944
|
+
//#region src/ha-export/migrations/drop-public-base-url.ts
|
|
4945
|
+
/**
|
|
4946
|
+
* The media base is no longer a setting of this addon (D986 §5): each Home Assistant
|
|
4947
|
+
* instance chooses a hub address ROLE (`mediaRole:<brokerId>`) and the address is read from
|
|
4948
|
+
* the palette on every use. The `publicBaseUrl` key older versions persisted is deleted.
|
|
4949
|
+
* Every other key is left exactly as stored.
|
|
4950
|
+
*
|
|
4951
|
+
* Idempotent: a store without the key yields an empty plan, which writes nothing.
|
|
4952
|
+
*/
|
|
4953
|
+
/** The settings scope of this addon (`<addonId>:addon-settings`). */
|
|
4954
|
+
var HA_EXPORT_SETTINGS_SCOPE = "homeassistant-export:addon-settings";
|
|
4955
|
+
var RETIRED_URL_KEYS = ["publicBaseUrl"];
|
|
4956
|
+
async function planDropPublicBaseUrl(ports) {
|
|
4957
|
+
const stored = await ports.readStored();
|
|
4958
|
+
const present = RETIRED_URL_KEYS.filter((key) => key in stored);
|
|
4959
|
+
if (present.length === 0) return { edits: [] };
|
|
4960
|
+
return { edits: [{
|
|
4961
|
+
op: "deleteKeys",
|
|
4962
|
+
scope: HA_EXPORT_SETTINGS_SCOPE,
|
|
4963
|
+
keys: [...present],
|
|
4964
|
+
precondition: require_ha_broker_source.preconditionOf(Object.fromEntries(present.map((key) => [key, stored[key]])))
|
|
4965
|
+
}] };
|
|
4966
|
+
}
|
|
4967
|
+
//#endregion
|
|
4968
|
+
//#region src/ha-export/migrations/index.ts
|
|
4969
|
+
/** The `homeassistant-export` catalogue (D987): offered to the engine when the addon initialises. */
|
|
4970
|
+
var ADDON_MIGRATIONS = [{
|
|
4971
|
+
meta: {
|
|
4972
|
+
id: "homeassistant-export/2026-11-02-drop-public-base-url",
|
|
4973
|
+
owner: "homeassistant-export",
|
|
4974
|
+
level: "addon-state",
|
|
4975
|
+
seq: 1,
|
|
4976
|
+
dependsOn: [],
|
|
4977
|
+
store: "settings",
|
|
4978
|
+
scope: "cluster",
|
|
4979
|
+
onFailure: "tolerate",
|
|
4980
|
+
bodyKind: "plan",
|
|
4981
|
+
description: "Delete the retired publicBaseUrl setting; each instance now chooses a hub address role",
|
|
4982
|
+
fixtureMark: "the reconcile interval the migration does not know"
|
|
4983
|
+
},
|
|
4984
|
+
plan: planDropPublicBaseUrl
|
|
4985
|
+
}];
|
|
4943
4986
|
process.platform, process.env["SystemRoot"];
|
|
4944
4987
|
/**
|
|
4945
4988
|
* The ONE derivation of "a signed, expiring URL".
|
|
@@ -5019,15 +5062,26 @@ function verifyExpiringUrl(input) {
|
|
|
5019
5062
|
* an expired link is refused whether or not its signature is valid, so a leaked
|
|
5020
5063
|
* URL stops working on its own.
|
|
5021
5064
|
*/
|
|
5065
|
+
/** The settings key that carries one broker's media role (per-instance, like `exportTo:`). */
|
|
5066
|
+
function mediaRoleKey(brokerId) {
|
|
5067
|
+
return `mediaRole:${brokerId}`;
|
|
5068
|
+
}
|
|
5069
|
+
/** A broker's chosen role; anything stored that is not a known role is `internal`. */
|
|
5070
|
+
function haMediaRoleOf(store, brokerId) {
|
|
5071
|
+
return store[mediaRoleKey(brokerId)] === "publicEndpoint" ? "publicEndpoint" : "internal";
|
|
5072
|
+
}
|
|
5022
5073
|
/**
|
|
5023
|
-
* The base a media link is minted on
|
|
5024
|
-
*
|
|
5025
|
-
*
|
|
5026
|
-
*
|
|
5027
|
-
* endpoint, and never loopback (`null` when nothing qualifies).
|
|
5074
|
+
* The base a media link is minted on: the hub's role (D734) — `internal` unless the
|
|
5075
|
+
* broker chose `publicEndpoint`. Ranked by `local-network`, never loopback (`null`
|
|
5076
|
+
* when the role resolves to nothing): a link Home Assistant cannot open is worse
|
|
5077
|
+
* than no image.
|
|
5028
5078
|
*/
|
|
5029
|
-
function haMediaBaseUrl(book) {
|
|
5030
|
-
return book.roles.
|
|
5079
|
+
function haMediaBaseUrl(book, role = "internal") {
|
|
5080
|
+
return book.roles[role].resolved;
|
|
5081
|
+
}
|
|
5082
|
+
/** brokerId → media base, each broker on ITS role. Pure: the caller reads the palette per use. */
|
|
5083
|
+
function mediaBasesByBroker(store, brokerIds, book) {
|
|
5084
|
+
return new Map(brokerIds.map((brokerId) => [brokerId, haMediaBaseUrl(book, haMediaRoleOf(store, brokerId))]));
|
|
5031
5085
|
}
|
|
5032
5086
|
/** How long a minted link stays valid. Long enough for HA to render it. */
|
|
5033
5087
|
var MEDIA_URL_TTL_MS = 360 * 60 * 1e3;
|
|
@@ -7168,8 +7222,7 @@ var DEFAULT_CONFIG = {
|
|
|
7168
7222
|
exportedEntities: {
|
|
7169
7223
|
actions: [],
|
|
7170
7224
|
targets: []
|
|
7171
|
-
}
|
|
7172
|
-
publicBaseUrl: ""
|
|
7225
|
+
}
|
|
7173
7226
|
};
|
|
7174
7227
|
var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
7175
7228
|
links = /* @__PURE__ */ new Map();
|
|
@@ -7178,7 +7231,6 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
7178
7231
|
/** numeric deviceId → deviceKey, for the event path. */
|
|
7179
7232
|
keyByDeviceId = /* @__PURE__ */ new Map();
|
|
7180
7233
|
mediaSecret = "";
|
|
7181
|
-
mediaBaseUrl = null;
|
|
7182
7234
|
reconcileTimer = null;
|
|
7183
7235
|
reconcileInFlight = false;
|
|
7184
7236
|
/** A reason asked for while one was running. Re-run, never dropped. */
|
|
@@ -7278,6 +7330,7 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
7278
7330
|
super({ ...DEFAULT_CONFIG });
|
|
7279
7331
|
}
|
|
7280
7332
|
async onInitialize() {
|
|
7333
|
+
await this.runMigrations(require_ha_broker_source.defineAddonMigrations(ADDON_MIGRATIONS, { readStored: async () => this.resolveGlobalStore() }));
|
|
7281
7334
|
const secret = this.state("haExportMediaSecret", require_ha_broker_source.string(), "");
|
|
7282
7335
|
const existing = await secret.get();
|
|
7283
7336
|
if (existing.length === 0) {
|
|
@@ -7678,32 +7731,39 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
7678
7731
|
const lastSeen = readNumber(data, "lastSeen") ?? Date.now();
|
|
7679
7732
|
const zonesVisited = readStringArray(data, "zonesVisited");
|
|
7680
7733
|
const fusedIntoTrackId = readString(data, "fusedIntoTrackId");
|
|
7681
|
-
const
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
|
|
7704
|
-
return;
|
|
7734
|
+
const exported = this.exported.get(deviceKey);
|
|
7735
|
+
if (exported === void 0) return;
|
|
7736
|
+
const bases = await this.mediaBasesFor(exported.brokerIds, deviceId);
|
|
7737
|
+
let produced = false;
|
|
7738
|
+
for (const brokerId of exported.brokerIds) {
|
|
7739
|
+
const link = this.links.get(brokerId);
|
|
7740
|
+
if (link === void 0) continue;
|
|
7741
|
+
const base = bases.get(brokerId) ?? null;
|
|
7742
|
+
const values = projectTrackLifecycle(deviceKey, {
|
|
7743
|
+
deviceId,
|
|
7744
|
+
trackId,
|
|
7745
|
+
phase,
|
|
7746
|
+
classes,
|
|
7747
|
+
...bestClassName !== null ? { bestClassName } : {},
|
|
7748
|
+
...label !== null ? { label } : {},
|
|
7749
|
+
...plateText !== null ? { plateText } : {},
|
|
7750
|
+
lastSeen,
|
|
7751
|
+
zonesVisited,
|
|
7752
|
+
...fusedIntoTrackId !== null ? { fusedIntoTrackId } : {}
|
|
7753
|
+
}, base === null ? null : this.mintTrackMediaUrl(trackId, deviceId, base));
|
|
7754
|
+
if (values.length === 0) continue;
|
|
7755
|
+
produced = true;
|
|
7756
|
+
for (const value of values) link.client.publishState(value.topic, value.value);
|
|
7705
7757
|
}
|
|
7706
|
-
this.
|
|
7758
|
+
if (!produced) this.ctx.logger.debug("ha-export: track produced no exportable state", {
|
|
7759
|
+
tags: { deviceId },
|
|
7760
|
+
meta: {
|
|
7761
|
+
trackId,
|
|
7762
|
+
bestClassName,
|
|
7763
|
+
phase,
|
|
7764
|
+
fusedIntoTrackId
|
|
7765
|
+
}
|
|
7766
|
+
});
|
|
7707
7767
|
}
|
|
7708
7768
|
onAudioWindow(data) {
|
|
7709
7769
|
const deviceId = readNumber(data, "deviceId");
|
|
@@ -7848,7 +7908,6 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
7848
7908
|
await this.refreshBrokers();
|
|
7849
7909
|
const fullResync = this.resync.take(reason);
|
|
7850
7910
|
let readsHeld = false;
|
|
7851
|
-
await this.refreshMediaBaseUrl();
|
|
7852
7911
|
await this.refreshComponentSupport();
|
|
7853
7912
|
const enabled = this.enabledBrokerIds();
|
|
7854
7913
|
const devices = await this.ctx.api.deviceManager.listAll.query({});
|
|
@@ -9325,28 +9384,41 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9325
9384
|
* cannot open is worse than no image, because it renders as a broken
|
|
9326
9385
|
* entity rather than an empty one.
|
|
9327
9386
|
*/
|
|
9328
|
-
mintTrackMediaUrl(trackId, deviceId) {
|
|
9329
|
-
if (this.
|
|
9387
|
+
mintTrackMediaUrl(trackId, deviceId, baseUrl) {
|
|
9388
|
+
if (this.mediaSecret.length === 0) return null;
|
|
9330
9389
|
return buildMediaUrl({
|
|
9331
|
-
baseUrl
|
|
9390
|
+
baseUrl,
|
|
9332
9391
|
routePrefix: MEDIA_ROUTE_PREFIX,
|
|
9333
9392
|
id: `${String(deviceId)}:${trackId}`,
|
|
9334
9393
|
secret: this.mediaSecret,
|
|
9335
9394
|
expMs: Date.now() + MEDIA_URL_TTL_MS
|
|
9336
9395
|
});
|
|
9337
9396
|
}
|
|
9338
|
-
|
|
9339
|
-
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
|
|
9397
|
+
/**
|
|
9398
|
+
* Each broker's media base, read per use: the palette's role the broker chose
|
|
9399
|
+
* (`mediaRole:<brokerId>`, `internal` by default). A role that resolves to nothing
|
|
9400
|
+
* is `null` and says so — the image entities then carry no value, which reads as
|
|
9401
|
+
* empty rather than as a broken link.
|
|
9402
|
+
*/
|
|
9403
|
+
async mediaBasesFor(brokerIds, deviceId) {
|
|
9404
|
+
let bases;
|
|
9343
9405
|
try {
|
|
9344
|
-
|
|
9406
|
+
bases = mediaBasesByBroker(await this.resolveGlobalStore(), brokerIds, await this.ctx.api.localNetwork.getAddresses.query());
|
|
9345
9407
|
} catch (err) {
|
|
9346
|
-
this.
|
|
9347
|
-
|
|
9408
|
+
this.ctx.logger.debug("ha-export: could not resolve the media base URLs", {
|
|
9409
|
+
tags: { deviceId },
|
|
9410
|
+
meta: { error: errMsg(err) }
|
|
9411
|
+
});
|
|
9412
|
+
return new Map(brokerIds.map((brokerId) => [brokerId, null]));
|
|
9413
|
+
}
|
|
9414
|
+
for (const brokerId of brokerIds) {
|
|
9415
|
+
if (bases.get(brokerId) !== null) continue;
|
|
9416
|
+
this.ctx.logger.warn("ha-export: the broker media role resolves to no hub address — image entities carry no value until the Network page names one", {
|
|
9417
|
+
tags: { deviceId },
|
|
9418
|
+
meta: { brokerId }
|
|
9419
|
+
});
|
|
9348
9420
|
}
|
|
9349
|
-
|
|
9421
|
+
return bases;
|
|
9350
9422
|
}
|
|
9351
9423
|
/**
|
|
9352
9424
|
* The cap-level expose/unexpose applies to EVERY enabled broker.
|
|
@@ -9400,21 +9472,26 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9400
9472
|
variant: "warning"
|
|
9401
9473
|
}]
|
|
9402
9474
|
},
|
|
9403
|
-
{
|
|
9475
|
+
...this.config.knownBrokers.length > 0 ? [{
|
|
9404
9476
|
id: "ha-export-transport",
|
|
9405
9477
|
title: "Connection",
|
|
9406
9478
|
titleKey: "provider-homeassistant:globalSettings.s_ha-export-transport.title",
|
|
9479
|
+
description: "The hub address each Home Assistant instance fetches image links from. Internal is the address flagged Internal on the Network page; choose Public endpoint for an instance outside your network.",
|
|
9407
9480
|
columns: 1,
|
|
9408
|
-
fields:
|
|
9409
|
-
type: "
|
|
9410
|
-
key:
|
|
9411
|
-
label:
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
|
|
9417
|
-
|
|
9481
|
+
fields: this.config.knownBrokers.map((broker) => ({
|
|
9482
|
+
type: "select",
|
|
9483
|
+
key: mediaRoleKey(broker.id),
|
|
9484
|
+
label: `Image links for ${broker.name}`,
|
|
9485
|
+
options: [{
|
|
9486
|
+
value: "internal",
|
|
9487
|
+
label: "Internal (LAN address)"
|
|
9488
|
+
}, {
|
|
9489
|
+
value: "publicEndpoint",
|
|
9490
|
+
label: "Public endpoint (reachable from outside)"
|
|
9491
|
+
}],
|
|
9492
|
+
default: "internal"
|
|
9493
|
+
}))
|
|
9494
|
+
}] : [],
|
|
9418
9495
|
{
|
|
9419
9496
|
id: "ha-export-advanced",
|
|
9420
9497
|
title: "Advanced",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as consumableKindOf,
|
|
1
|
+
import { B as consumableKindOf, Bt as BaseAddon, Dt as sendToExportedTarget, Ht as baseUnitOf, Jt as isReadingQuantity, K as deviceExportCapability, Wt as defineAddonMigrations, Yt as nodePin, Zt as array, _ as PLUG_APPLIANCE_TYPES, _t as pruneDeletedEntities, c as COCO_TO_MACRO, d as ConsumableItemSchema, f as ConsumableStatusSchema, fn as EventCategory, g as MediaPlayerRepeatSchema, h as HvacModeSchema, i as AlarmArmModeSchema, in as string, j as buildAddonRouteProvider, l as CameraSwitchIdSchema, m as FanDirectionSchema, nn as object, on as unknown, pt as preconditionOf, r as listHomeAssistantBrokers, s as CAMERA_SWITCH_ORDER, u as ConsumableFormSchema, ut as oauthIntegrationCapability, x as addonRoutesCapability, xt as runExportedAction, z as consumableFormOf } from "../ha-broker-source-iLA_DsW8.mjs";
|
|
2
2
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
3
3
|
import "node:zlib";
|
|
4
4
|
import "node:fs";
|
|
@@ -4905,6 +4905,49 @@ async function call(slice, capName, make) {
|
|
|
4905
4905
|
}
|
|
4906
4906
|
return { ok: true };
|
|
4907
4907
|
}
|
|
4908
|
+
//#endregion
|
|
4909
|
+
//#region src/ha-export/migrations/drop-public-base-url.ts
|
|
4910
|
+
/**
|
|
4911
|
+
* The media base is no longer a setting of this addon (D986 §5): each Home Assistant
|
|
4912
|
+
* instance chooses a hub address ROLE (`mediaRole:<brokerId>`) and the address is read from
|
|
4913
|
+
* the palette on every use. The `publicBaseUrl` key older versions persisted is deleted.
|
|
4914
|
+
* Every other key is left exactly as stored.
|
|
4915
|
+
*
|
|
4916
|
+
* Idempotent: a store without the key yields an empty plan, which writes nothing.
|
|
4917
|
+
*/
|
|
4918
|
+
/** The settings scope of this addon (`<addonId>:addon-settings`). */
|
|
4919
|
+
var HA_EXPORT_SETTINGS_SCOPE = "homeassistant-export:addon-settings";
|
|
4920
|
+
var RETIRED_URL_KEYS = ["publicBaseUrl"];
|
|
4921
|
+
async function planDropPublicBaseUrl(ports) {
|
|
4922
|
+
const stored = await ports.readStored();
|
|
4923
|
+
const present = RETIRED_URL_KEYS.filter((key) => key in stored);
|
|
4924
|
+
if (present.length === 0) return { edits: [] };
|
|
4925
|
+
return { edits: [{
|
|
4926
|
+
op: "deleteKeys",
|
|
4927
|
+
scope: HA_EXPORT_SETTINGS_SCOPE,
|
|
4928
|
+
keys: [...present],
|
|
4929
|
+
precondition: preconditionOf(Object.fromEntries(present.map((key) => [key, stored[key]])))
|
|
4930
|
+
}] };
|
|
4931
|
+
}
|
|
4932
|
+
//#endregion
|
|
4933
|
+
//#region src/ha-export/migrations/index.ts
|
|
4934
|
+
/** The `homeassistant-export` catalogue (D987): offered to the engine when the addon initialises. */
|
|
4935
|
+
var ADDON_MIGRATIONS = [{
|
|
4936
|
+
meta: {
|
|
4937
|
+
id: "homeassistant-export/2026-11-02-drop-public-base-url",
|
|
4938
|
+
owner: "homeassistant-export",
|
|
4939
|
+
level: "addon-state",
|
|
4940
|
+
seq: 1,
|
|
4941
|
+
dependsOn: [],
|
|
4942
|
+
store: "settings",
|
|
4943
|
+
scope: "cluster",
|
|
4944
|
+
onFailure: "tolerate",
|
|
4945
|
+
bodyKind: "plan",
|
|
4946
|
+
description: "Delete the retired publicBaseUrl setting; each instance now chooses a hub address role",
|
|
4947
|
+
fixtureMark: "the reconcile interval the migration does not know"
|
|
4948
|
+
},
|
|
4949
|
+
plan: planDropPublicBaseUrl
|
|
4950
|
+
}];
|
|
4908
4951
|
process.platform, process.env["SystemRoot"];
|
|
4909
4952
|
/**
|
|
4910
4953
|
* The ONE derivation of "a signed, expiring URL".
|
|
@@ -4984,15 +5027,26 @@ function verifyExpiringUrl(input) {
|
|
|
4984
5027
|
* an expired link is refused whether or not its signature is valid, so a leaked
|
|
4985
5028
|
* URL stops working on its own.
|
|
4986
5029
|
*/
|
|
5030
|
+
/** The settings key that carries one broker's media role (per-instance, like `exportTo:`). */
|
|
5031
|
+
function mediaRoleKey(brokerId) {
|
|
5032
|
+
return `mediaRole:${brokerId}`;
|
|
5033
|
+
}
|
|
5034
|
+
/** A broker's chosen role; anything stored that is not a known role is `internal`. */
|
|
5035
|
+
function haMediaRoleOf(store, brokerId) {
|
|
5036
|
+
return store[mediaRoleKey(brokerId)] === "publicEndpoint" ? "publicEndpoint" : "internal";
|
|
5037
|
+
}
|
|
4987
5038
|
/**
|
|
4988
|
-
* The base a media link is minted on
|
|
4989
|
-
*
|
|
4990
|
-
*
|
|
4991
|
-
*
|
|
4992
|
-
* endpoint, and never loopback (`null` when nothing qualifies).
|
|
5039
|
+
* The base a media link is minted on: the hub's role (D734) — `internal` unless the
|
|
5040
|
+
* broker chose `publicEndpoint`. Ranked by `local-network`, never loopback (`null`
|
|
5041
|
+
* when the role resolves to nothing): a link Home Assistant cannot open is worse
|
|
5042
|
+
* than no image.
|
|
4993
5043
|
*/
|
|
4994
|
-
function haMediaBaseUrl(book) {
|
|
4995
|
-
return book.roles.
|
|
5044
|
+
function haMediaBaseUrl(book, role = "internal") {
|
|
5045
|
+
return book.roles[role].resolved;
|
|
5046
|
+
}
|
|
5047
|
+
/** brokerId → media base, each broker on ITS role. Pure: the caller reads the palette per use. */
|
|
5048
|
+
function mediaBasesByBroker(store, brokerIds, book) {
|
|
5049
|
+
return new Map(brokerIds.map((brokerId) => [brokerId, haMediaBaseUrl(book, haMediaRoleOf(store, brokerId))]));
|
|
4996
5050
|
}
|
|
4997
5051
|
/** How long a minted link stays valid. Long enough for HA to render it. */
|
|
4998
5052
|
var MEDIA_URL_TTL_MS = 360 * 60 * 1e3;
|
|
@@ -7133,8 +7187,7 @@ var DEFAULT_CONFIG = {
|
|
|
7133
7187
|
exportedEntities: {
|
|
7134
7188
|
actions: [],
|
|
7135
7189
|
targets: []
|
|
7136
|
-
}
|
|
7137
|
-
publicBaseUrl: ""
|
|
7190
|
+
}
|
|
7138
7191
|
};
|
|
7139
7192
|
var HaExportAddon = class extends BaseAddon {
|
|
7140
7193
|
links = /* @__PURE__ */ new Map();
|
|
@@ -7143,7 +7196,6 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
7143
7196
|
/** numeric deviceId → deviceKey, for the event path. */
|
|
7144
7197
|
keyByDeviceId = /* @__PURE__ */ new Map();
|
|
7145
7198
|
mediaSecret = "";
|
|
7146
|
-
mediaBaseUrl = null;
|
|
7147
7199
|
reconcileTimer = null;
|
|
7148
7200
|
reconcileInFlight = false;
|
|
7149
7201
|
/** A reason asked for while one was running. Re-run, never dropped. */
|
|
@@ -7243,6 +7295,7 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
7243
7295
|
super({ ...DEFAULT_CONFIG });
|
|
7244
7296
|
}
|
|
7245
7297
|
async onInitialize() {
|
|
7298
|
+
await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, { readStored: async () => this.resolveGlobalStore() }));
|
|
7246
7299
|
const secret = this.state("haExportMediaSecret", string(), "");
|
|
7247
7300
|
const existing = await secret.get();
|
|
7248
7301
|
if (existing.length === 0) {
|
|
@@ -7643,32 +7696,39 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
7643
7696
|
const lastSeen = readNumber(data, "lastSeen") ?? Date.now();
|
|
7644
7697
|
const zonesVisited = readStringArray(data, "zonesVisited");
|
|
7645
7698
|
const fusedIntoTrackId = readString(data, "fusedIntoTrackId");
|
|
7646
|
-
const
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
7661
|
-
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
return;
|
|
7699
|
+
const exported = this.exported.get(deviceKey);
|
|
7700
|
+
if (exported === void 0) return;
|
|
7701
|
+
const bases = await this.mediaBasesFor(exported.brokerIds, deviceId);
|
|
7702
|
+
let produced = false;
|
|
7703
|
+
for (const brokerId of exported.brokerIds) {
|
|
7704
|
+
const link = this.links.get(brokerId);
|
|
7705
|
+
if (link === void 0) continue;
|
|
7706
|
+
const base = bases.get(brokerId) ?? null;
|
|
7707
|
+
const values = projectTrackLifecycle(deviceKey, {
|
|
7708
|
+
deviceId,
|
|
7709
|
+
trackId,
|
|
7710
|
+
phase,
|
|
7711
|
+
classes,
|
|
7712
|
+
...bestClassName !== null ? { bestClassName } : {},
|
|
7713
|
+
...label !== null ? { label } : {},
|
|
7714
|
+
...plateText !== null ? { plateText } : {},
|
|
7715
|
+
lastSeen,
|
|
7716
|
+
zonesVisited,
|
|
7717
|
+
...fusedIntoTrackId !== null ? { fusedIntoTrackId } : {}
|
|
7718
|
+
}, base === null ? null : this.mintTrackMediaUrl(trackId, deviceId, base));
|
|
7719
|
+
if (values.length === 0) continue;
|
|
7720
|
+
produced = true;
|
|
7721
|
+
for (const value of values) link.client.publishState(value.topic, value.value);
|
|
7670
7722
|
}
|
|
7671
|
-
this.
|
|
7723
|
+
if (!produced) this.ctx.logger.debug("ha-export: track produced no exportable state", {
|
|
7724
|
+
tags: { deviceId },
|
|
7725
|
+
meta: {
|
|
7726
|
+
trackId,
|
|
7727
|
+
bestClassName,
|
|
7728
|
+
phase,
|
|
7729
|
+
fusedIntoTrackId
|
|
7730
|
+
}
|
|
7731
|
+
});
|
|
7672
7732
|
}
|
|
7673
7733
|
onAudioWindow(data) {
|
|
7674
7734
|
const deviceId = readNumber(data, "deviceId");
|
|
@@ -7813,7 +7873,6 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
7813
7873
|
await this.refreshBrokers();
|
|
7814
7874
|
const fullResync = this.resync.take(reason);
|
|
7815
7875
|
let readsHeld = false;
|
|
7816
|
-
await this.refreshMediaBaseUrl();
|
|
7817
7876
|
await this.refreshComponentSupport();
|
|
7818
7877
|
const enabled = this.enabledBrokerIds();
|
|
7819
7878
|
const devices = await this.ctx.api.deviceManager.listAll.query({});
|
|
@@ -9290,28 +9349,41 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9290
9349
|
* cannot open is worse than no image, because it renders as a broken
|
|
9291
9350
|
* entity rather than an empty one.
|
|
9292
9351
|
*/
|
|
9293
|
-
mintTrackMediaUrl(trackId, deviceId) {
|
|
9294
|
-
if (this.
|
|
9352
|
+
mintTrackMediaUrl(trackId, deviceId, baseUrl) {
|
|
9353
|
+
if (this.mediaSecret.length === 0) return null;
|
|
9295
9354
|
return buildMediaUrl({
|
|
9296
|
-
baseUrl
|
|
9355
|
+
baseUrl,
|
|
9297
9356
|
routePrefix: MEDIA_ROUTE_PREFIX,
|
|
9298
9357
|
id: `${String(deviceId)}:${trackId}`,
|
|
9299
9358
|
secret: this.mediaSecret,
|
|
9300
9359
|
expMs: Date.now() + MEDIA_URL_TTL_MS
|
|
9301
9360
|
});
|
|
9302
9361
|
}
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
9362
|
+
/**
|
|
9363
|
+
* Each broker's media base, read per use: the palette's role the broker chose
|
|
9364
|
+
* (`mediaRole:<brokerId>`, `internal` by default). A role that resolves to nothing
|
|
9365
|
+
* is `null` and says so — the image entities then carry no value, which reads as
|
|
9366
|
+
* empty rather than as a broken link.
|
|
9367
|
+
*/
|
|
9368
|
+
async mediaBasesFor(brokerIds, deviceId) {
|
|
9369
|
+
let bases;
|
|
9308
9370
|
try {
|
|
9309
|
-
|
|
9371
|
+
bases = mediaBasesByBroker(await this.resolveGlobalStore(), brokerIds, await this.ctx.api.localNetwork.getAddresses.query());
|
|
9310
9372
|
} catch (err) {
|
|
9311
|
-
this.
|
|
9312
|
-
|
|
9373
|
+
this.ctx.logger.debug("ha-export: could not resolve the media base URLs", {
|
|
9374
|
+
tags: { deviceId },
|
|
9375
|
+
meta: { error: errMsg(err) }
|
|
9376
|
+
});
|
|
9377
|
+
return new Map(brokerIds.map((brokerId) => [brokerId, null]));
|
|
9378
|
+
}
|
|
9379
|
+
for (const brokerId of brokerIds) {
|
|
9380
|
+
if (bases.get(brokerId) !== null) continue;
|
|
9381
|
+
this.ctx.logger.warn("ha-export: the broker media role resolves to no hub address — image entities carry no value until the Network page names one", {
|
|
9382
|
+
tags: { deviceId },
|
|
9383
|
+
meta: { brokerId }
|
|
9384
|
+
});
|
|
9313
9385
|
}
|
|
9314
|
-
|
|
9386
|
+
return bases;
|
|
9315
9387
|
}
|
|
9316
9388
|
/**
|
|
9317
9389
|
* The cap-level expose/unexpose applies to EVERY enabled broker.
|
|
@@ -9365,21 +9437,26 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9365
9437
|
variant: "warning"
|
|
9366
9438
|
}]
|
|
9367
9439
|
},
|
|
9368
|
-
{
|
|
9440
|
+
...this.config.knownBrokers.length > 0 ? [{
|
|
9369
9441
|
id: "ha-export-transport",
|
|
9370
9442
|
title: "Connection",
|
|
9371
9443
|
titleKey: "provider-homeassistant:globalSettings.s_ha-export-transport.title",
|
|
9444
|
+
description: "The hub address each Home Assistant instance fetches image links from. Internal is the address flagged Internal on the Network page; choose Public endpoint for an instance outside your network.",
|
|
9372
9445
|
columns: 1,
|
|
9373
|
-
fields:
|
|
9374
|
-
type: "
|
|
9375
|
-
key:
|
|
9376
|
-
label:
|
|
9377
|
-
|
|
9378
|
-
|
|
9379
|
-
|
|
9380
|
-
|
|
9381
|
-
|
|
9382
|
-
|
|
9446
|
+
fields: this.config.knownBrokers.map((broker) => ({
|
|
9447
|
+
type: "select",
|
|
9448
|
+
key: mediaRoleKey(broker.id),
|
|
9449
|
+
label: `Image links for ${broker.name}`,
|
|
9450
|
+
options: [{
|
|
9451
|
+
value: "internal",
|
|
9452
|
+
label: "Internal (LAN address)"
|
|
9453
|
+
}, {
|
|
9454
|
+
value: "publicEndpoint",
|
|
9455
|
+
label: "Public endpoint (reachable from outside)"
|
|
9456
|
+
}],
|
|
9457
|
+
default: "internal"
|
|
9458
|
+
}))
|
|
9459
|
+
}] : [],
|
|
9383
9460
|
{
|
|
9384
9461
|
id: "ha-export-advanced",
|
|
9385
9462
|
title: "Advanced",
|
package/i18n/en.json
CHANGED
|
@@ -14,8 +14,6 @@
|
|
|
14
14
|
"globalSettings.s_ha-export-brokers.title": "Home Assistant instances",
|
|
15
15
|
"globalSettings.s_ha-export-brokers.ha-export___no-brokers.label": "No brokers",
|
|
16
16
|
"globalSettings.s_ha-export-transport.title": "Connection",
|
|
17
|
-
"globalSettings.s_ha-export-transport.publicBaseUrl.label": "Hub URL Home Assistant can reach",
|
|
18
|
-
"globalSettings.s_ha-export-transport.publicBaseUrl.description": "Used for the signed image links Home Assistant fetches; leave empty to derive one automatically.",
|
|
19
17
|
"globalSettings.s_ha-export-advanced.title": "Advanced",
|
|
20
18
|
"globalSettings.s_ha-export-advanced.reconcileIntervalSec.label": "Reconcile interval (s)",
|
|
21
19
|
"globalSettings.s_ha-export-advanced.reconcileIntervalSec.description": "Full re-announce and re-push cadence.",
|
package/i18n/it.json
CHANGED
|
@@ -14,8 +14,6 @@
|
|
|
14
14
|
"globalSettings.s_ha-export-brokers.title": "Istanze di Home Assistant",
|
|
15
15
|
"globalSettings.s_ha-export-brokers.ha-export___no-brokers.label": "Nessun broker",
|
|
16
16
|
"globalSettings.s_ha-export-transport.title": "Connessione",
|
|
17
|
-
"globalSettings.s_ha-export-transport.publicBaseUrl.label": "URL dell'hub raggiungibile da Home Assistant",
|
|
18
|
-
"globalSettings.s_ha-export-transport.publicBaseUrl.description": "Usato per i link firmati delle immagini che Home Assistant scarica; lascia vuoto per ricavarlo automaticamente.",
|
|
19
17
|
"globalSettings.s_ha-export-advanced.title": "Avanzate",
|
|
20
18
|
"globalSettings.s_ha-export-advanced.reconcileIntervalSec.label": "Intervallo di riconciliazione (s)",
|
|
21
19
|
"globalSettings.s_ha-export-advanced.reconcileIntervalSec.description": "Cadenza di ri-annuncio e ri-invio completo.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-provider-homeassistant",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.648",
|
|
4
4
|
"description": "Home Assistant device provider addon for CamStack",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
"description": "Device provider (import) + native entity export (push). Two addons, one bundle, separate processes."
|
|
128
128
|
},
|
|
129
129
|
"requires": {
|
|
130
|
-
"server": ">=1.2.
|
|
130
|
+
"server": ">=1.2.733"
|
|
131
131
|
}
|
|
132
132
|
},
|
|
133
133
|
"files": [
|