@camstack/addon-provider-homeassistant 1.2.653 → 1.2.655
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 +15 -0
- package/dist/addon.js +938 -540
- package/dist/addon.mjs +481 -86
- package/dist/ha-export/ha-export.addon.js +452 -160
- package/dist/ha-export/ha-export.addon.mjs +406 -114
- package/dist/{ha-broker-source-DnSL-mbs.js → ha-instance-settings-5Ms7yRju.js} +1402 -525
- package/dist/{ha-broker-source-CKdH9XHn.mjs → ha-instance-settings-DLDNOmgL.mjs} +1342 -525
- package/package.json +6 -2
package/dist/addon.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
2
|
+
const require_ha_instance_settings = require("./ha-instance-settings-5Ms7yRju.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
|
|
@@ -72,21 +72,21 @@ function registerHaAdminLink(ctx, deviceId, brokerId, subject) {
|
|
|
72
72
|
});
|
|
73
73
|
return null;
|
|
74
74
|
};
|
|
75
|
-
ctx.registerNativeCap(
|
|
75
|
+
ctx.registerNativeCap(require_ha_instance_settings.deviceAdminLinkCapability, { getAdminLink: async (input) => {
|
|
76
76
|
if (input.deviceId !== deviceId) throw new Error(`HaAdminLink: deviceId mismatch, expected ${deviceId}, got ${input.deviceId}`);
|
|
77
77
|
if (cachedBaseUrl === null) {
|
|
78
78
|
const raw = (await ctx.api.broker.getBrokerConfig.query({ id: brokerId }))?.["baseUrl"];
|
|
79
79
|
if (typeof raw !== "string" || raw.length === 0) return refuse("no-base-url");
|
|
80
80
|
cachedBaseUrl = raw;
|
|
81
81
|
}
|
|
82
|
-
const base =
|
|
82
|
+
const base = require_ha_instance_settings.parseAdminBase(cachedBaseUrl);
|
|
83
83
|
if (!base.ok) {
|
|
84
84
|
cachedBaseUrl = null;
|
|
85
85
|
return refuse("base-unusable", { baseReason: base.reason });
|
|
86
86
|
}
|
|
87
87
|
const page = pageFor(subject, base.basePath);
|
|
88
88
|
if (page === null) return refuse("empty-subject");
|
|
89
|
-
const built =
|
|
89
|
+
const built = require_ha_instance_settings.buildDeviceAdminUrl({
|
|
90
90
|
host: base.host,
|
|
91
91
|
port: base.port,
|
|
92
92
|
scheme: base.scheme,
|
|
@@ -114,25 +114,25 @@ function registerHaAdminLink(ctx, deviceId, brokerId, subject) {
|
|
|
114
114
|
* `readHaSource` (returns null) — do NOT use `z.object(haSourceSchemaFields).parse`.
|
|
115
115
|
*/
|
|
116
116
|
var haSourceSchemaFields = {
|
|
117
|
-
system:
|
|
118
|
-
brokerId:
|
|
119
|
-
haDeviceId:
|
|
120
|
-
entityId:
|
|
121
|
-
referenceEntityId:
|
|
122
|
-
capabilities:
|
|
123
|
-
role:
|
|
124
|
-
childDeviceIds:
|
|
125
|
-
unitOfMeasurement:
|
|
126
|
-
deviceClass:
|
|
127
|
-
integrationId:
|
|
128
|
-
options:
|
|
129
|
-
features:
|
|
117
|
+
system: require_ha_instance_settings.literal("homeassistant").optional(),
|
|
118
|
+
brokerId: require_ha_instance_settings.string(),
|
|
119
|
+
haDeviceId: require_ha_instance_settings.string().optional(),
|
|
120
|
+
entityId: require_ha_instance_settings.string().optional(),
|
|
121
|
+
referenceEntityId: require_ha_instance_settings.string().nullable().optional(),
|
|
122
|
+
capabilities: require_ha_instance_settings.array(require_ha_instance_settings.string()).default([]),
|
|
123
|
+
role: require_ha_instance_settings.string().optional(),
|
|
124
|
+
childDeviceIds: require_ha_instance_settings.array(require_ha_instance_settings.number()).default([]),
|
|
125
|
+
unitOfMeasurement: require_ha_instance_settings.string().optional(),
|
|
126
|
+
deviceClass: require_ha_instance_settings.string().optional(),
|
|
127
|
+
integrationId: require_ha_instance_settings.string().optional(),
|
|
128
|
+
options: require_ha_instance_settings.array(require_ha_instance_settings.string()).optional(),
|
|
129
|
+
features: require_ha_instance_settings.array(require_ha_instance_settings.string()).optional(),
|
|
130
130
|
/** Sibling meter sensors whose readings this entity carries as `power-meter` (D951). */
|
|
131
|
-
meterEntities:
|
|
132
|
-
power:
|
|
133
|
-
energy:
|
|
134
|
-
voltage:
|
|
135
|
-
current:
|
|
131
|
+
meterEntities: require_ha_instance_settings.object({
|
|
132
|
+
power: require_ha_instance_settings.string().optional(),
|
|
133
|
+
energy: require_ha_instance_settings.string().optional(),
|
|
134
|
+
voltage: require_ha_instance_settings.string().optional(),
|
|
135
|
+
current: require_ha_instance_settings.string().optional()
|
|
136
136
|
}).optional()
|
|
137
137
|
};
|
|
138
138
|
/** Project a persisted config blob into a typed source, or null when the
|
|
@@ -176,7 +176,7 @@ function readHaSource(config) {
|
|
|
176
176
|
function attachAccessoriesCap(ctx, deviceId, opts) {
|
|
177
177
|
let childIds = [];
|
|
178
178
|
const hidden = new Set(opts.initialHidden);
|
|
179
|
-
ctx.registerNativeCap(
|
|
179
|
+
ctx.registerNativeCap(require_ha_instance_settings.accessoriesCapability, {
|
|
180
180
|
getStatus: async () => ({
|
|
181
181
|
childDeviceIds: childIds,
|
|
182
182
|
hiddenChildIds: [...hidden]
|
|
@@ -193,7 +193,7 @@ function attachAccessoriesCap(ctx, deviceId, opts) {
|
|
|
193
193
|
type: "device",
|
|
194
194
|
id: deviceId
|
|
195
195
|
},
|
|
196
|
-
category:
|
|
196
|
+
category: require_ha_instance_settings.EventCategory.AccessoriesChildVisibilityChanged,
|
|
197
197
|
data: {
|
|
198
198
|
deviceId,
|
|
199
199
|
childDeviceId,
|
|
@@ -211,7 +211,7 @@ function attachAccessoriesCap(ctx, deviceId, opts) {
|
|
|
211
211
|
type: "device",
|
|
212
212
|
id: deviceId
|
|
213
213
|
},
|
|
214
|
-
category:
|
|
214
|
+
category: require_ha_instance_settings.EventCategory.AccessoriesChanged,
|
|
215
215
|
data: {
|
|
216
216
|
deviceId,
|
|
217
217
|
childDeviceIds: childIds,
|
|
@@ -232,23 +232,23 @@ function attachAccessoriesCap(ctx, deviceId, opts) {
|
|
|
232
232
|
* in the materialisation factory (Phase E) where each domain becomes
|
|
233
233
|
* its own DeviceType + cap set.
|
|
234
234
|
*/
|
|
235
|
-
var HaEntityStateSchema =
|
|
236
|
-
entity_id:
|
|
235
|
+
var HaEntityStateSchema = require_ha_instance_settings.object({
|
|
236
|
+
entity_id: require_ha_instance_settings.string().min(1),
|
|
237
237
|
/** Domain-specific state value (`on`/`off`/`open`/temperature
|
|
238
238
|
* number-as-string/etc.). Always a string in HA. */
|
|
239
|
-
state:
|
|
239
|
+
state: require_ha_instance_settings.string(),
|
|
240
240
|
/** Free-form attribute map. Domain-specific. */
|
|
241
|
-
attributes:
|
|
241
|
+
attributes: require_ha_instance_settings.record(require_ha_instance_settings.string(), require_ha_instance_settings.unknown()),
|
|
242
242
|
/** Optional context envelope (user/parent id) the HA server attaches. */
|
|
243
|
-
context:
|
|
244
|
-
id:
|
|
245
|
-
parent_id:
|
|
246
|
-
user_id:
|
|
243
|
+
context: require_ha_instance_settings.object({
|
|
244
|
+
id: require_ha_instance_settings.string(),
|
|
245
|
+
parent_id: require_ha_instance_settings.string().nullable(),
|
|
246
|
+
user_id: require_ha_instance_settings.string().nullable()
|
|
247
247
|
}).optional(),
|
|
248
248
|
/** ISO 8601 timestamp of the last state change. */
|
|
249
|
-
last_changed:
|
|
249
|
+
last_changed: require_ha_instance_settings.string(),
|
|
250
250
|
/** ISO 8601 timestamp of the last update (state OR attributes). */
|
|
251
|
-
last_updated:
|
|
251
|
+
last_updated: require_ha_instance_settings.string()
|
|
252
252
|
});
|
|
253
253
|
/**
|
|
254
254
|
* Auth strategy. Two mutually-exclusive options:
|
|
@@ -259,16 +259,16 @@ var HaEntityStateSchema = require_ha_broker_source.object({
|
|
|
259
259
|
* the addon config; the WS URL points at their HA instance
|
|
260
260
|
* (`ws://ha.example:8123/api/websocket` style).
|
|
261
261
|
*/
|
|
262
|
-
var HaAuthConfigSchema =
|
|
263
|
-
kind:
|
|
262
|
+
var HaAuthConfigSchema = require_ha_instance_settings.discriminatedUnion("kind", [require_ha_instance_settings.object({
|
|
263
|
+
kind: require_ha_instance_settings.literal("supervisor"),
|
|
264
264
|
/** Token injected by the Supervisor at runtime via env. */
|
|
265
|
-
supervisorToken:
|
|
266
|
-
}),
|
|
267
|
-
kind:
|
|
265
|
+
supervisorToken: require_ha_instance_settings.string().min(1).meta({ secret: true })
|
|
266
|
+
}), require_ha_instance_settings.object({
|
|
267
|
+
kind: require_ha_instance_settings.literal("long-lived"),
|
|
268
268
|
/** Origin URL of the HA instance (e.g. `https://ha.example:8123`). */
|
|
269
|
-
baseUrl:
|
|
269
|
+
baseUrl: require_ha_instance_settings.url(),
|
|
270
270
|
/** Long-lived access token issued by the user in their HA profile. */
|
|
271
|
-
accessToken:
|
|
271
|
+
accessToken: require_ha_instance_settings.string().min(1).meta({ secret: true })
|
|
272
272
|
})]);
|
|
273
273
|
/** HA's standard domain enum — supported subset; future domains added
|
|
274
274
|
* as integration coverage grows. */
|
|
@@ -306,7 +306,7 @@ var HaDomain = /* @__PURE__ */ function(HaDomain) {
|
|
|
306
306
|
* an unknown unit is never guessed (D393, D944).
|
|
307
307
|
*/
|
|
308
308
|
function takeReading(logger, deviceId, value, rawUnit, quantity) {
|
|
309
|
-
const result =
|
|
309
|
+
const result = require_ha_instance_settings.ingestReading({
|
|
310
310
|
value,
|
|
311
311
|
rawUnit,
|
|
312
312
|
quantity
|
|
@@ -367,7 +367,7 @@ function applyMeterReading(prev, field, value, lastFetchedAt) {
|
|
|
367
367
|
lastFetchedAt
|
|
368
368
|
};
|
|
369
369
|
}
|
|
370
|
-
var LOAD_TYPES = new Set(
|
|
370
|
+
var LOAD_TYPES = new Set(require_ha_instance_settings.powerMeterCapability.deviceTypes.filter((type) => type !== require_ha_instance_settings.DeviceType.Sensor));
|
|
371
371
|
function isLoad(entity) {
|
|
372
372
|
return LOAD_TYPES.has(entity.deviceType) && entity.entityCategory === void 0 && !entity.capabilities.includes("power-meter");
|
|
373
373
|
}
|
|
@@ -428,7 +428,7 @@ var HaMeterCarrier = class {
|
|
|
428
428
|
const entityId = meters[field];
|
|
429
429
|
return entityId === void 0 ? [] : [[entityId, field]];
|
|
430
430
|
}));
|
|
431
|
-
this.ctx.registerNativeCap(
|
|
431
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.powerMeterCapability, { getStatus: async () => this.runtimeState.getCapState("power-meter") ?? COLD_START$20 });
|
|
432
432
|
this.runtimeState.setCapState("power-meter", {
|
|
433
433
|
...COLD_START$20,
|
|
434
434
|
...this.runtimeState.getCapState("power-meter")
|
|
@@ -469,7 +469,7 @@ var HaMeterCarrier = class {
|
|
|
469
469
|
* and carries `childDeviceIds` so parent-mode devices can host the
|
|
470
470
|
* accessories cap for their entity-children.
|
|
471
471
|
*/
|
|
472
|
-
var haChildDeviceSchema =
|
|
472
|
+
var haChildDeviceSchema = require_ha_instance_settings.object({ ...haSourceSchemaFields });
|
|
473
473
|
/**
|
|
474
474
|
* Abstract base for every HA-entity-backed CamStack accessory device.
|
|
475
475
|
*
|
|
@@ -490,14 +490,14 @@ var haChildDeviceSchema = require_ha_broker_source.object({ ...haSourceSchemaFie
|
|
|
490
490
|
* own (beyond BaseDevice's auto-registered `device-status` /
|
|
491
491
|
* `feature-probe`).
|
|
492
492
|
*/
|
|
493
|
-
var DEVICE_FEATURE_VALUES = new Set(Object.values(
|
|
493
|
+
var DEVICE_FEATURE_VALUES = new Set(Object.values(require_ha_instance_settings.DeviceFeature));
|
|
494
494
|
/** Keep only strings that are valid DeviceFeature enum values (the persisted
|
|
495
495
|
* config stores them as plain strings). */
|
|
496
496
|
function parseDeviceFeatures(raw) {
|
|
497
497
|
if (!Array.isArray(raw)) return [];
|
|
498
498
|
return raw.filter((f) => typeof f === "string" && DEVICE_FEATURE_VALUES.has(f));
|
|
499
499
|
}
|
|
500
|
-
var HaBaseChildDevice = class HaBaseChildDevice extends
|
|
500
|
+
var HaBaseChildDevice = class HaBaseChildDevice extends require_ha_instance_settings.BaseDevice {
|
|
501
501
|
/** Mapping-derived feature flags (e.g. `lock-open`), read from the persisted
|
|
502
502
|
* config at construction. Surfaced on `device.features` (and re-published to
|
|
503
503
|
* the device meta in `onActivate` via `refreshFeatures`) so the UI gates
|
|
@@ -639,7 +639,7 @@ var HaBaseChildDevice = class HaBaseChildDevice extends require_ha_broker_source
|
|
|
639
639
|
this.deviceLogger.warn("ha-child getRawState failed to read cached state", { meta: {
|
|
640
640
|
brokerId: this.brokerId,
|
|
641
641
|
entityId: this.entityId,
|
|
642
|
-
error:
|
|
642
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
643
643
|
} });
|
|
644
644
|
return null;
|
|
645
645
|
}
|
|
@@ -689,7 +689,7 @@ var HaBaseChildDevice = class HaBaseChildDevice extends require_ha_broker_source
|
|
|
689
689
|
entityId: this.entityId,
|
|
690
690
|
domain,
|
|
691
691
|
service,
|
|
692
|
-
error:
|
|
692
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
693
693
|
} });
|
|
694
694
|
throw err;
|
|
695
695
|
}
|
|
@@ -716,7 +716,7 @@ var HaBaseChildDevice = class HaBaseChildDevice extends require_ha_broker_source
|
|
|
716
716
|
this.deviceLogger.warn("ha-child failed to register broker subscription", { meta: {
|
|
717
717
|
entityId: this.entityId,
|
|
718
718
|
brokerId: this.brokerId,
|
|
719
|
-
error:
|
|
719
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
720
720
|
} });
|
|
721
721
|
return false;
|
|
722
722
|
}
|
|
@@ -757,7 +757,7 @@ var HaBaseChildDevice = class HaBaseChildDevice extends require_ha_broker_source
|
|
|
757
757
|
} catch (err) {
|
|
758
758
|
this.deviceLogger.warn("ha-child state-push handler error", { meta: {
|
|
759
759
|
entityId: this.entityId,
|
|
760
|
-
error:
|
|
760
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
761
761
|
} });
|
|
762
762
|
}
|
|
763
763
|
};
|
|
@@ -786,7 +786,7 @@ var HaButtonDevice = class extends HaBaseChildDevice {
|
|
|
786
786
|
this.registerButtonCap();
|
|
787
787
|
}
|
|
788
788
|
registerButtonCap() {
|
|
789
|
-
this.ctx.registerNativeCap(
|
|
789
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.buttonCapability, { press: async () => {
|
|
790
790
|
const domain = this.entityId.split(".")[0] ?? "button";
|
|
791
791
|
if (domain === "scene") {
|
|
792
792
|
await this.callHaService("scene", "turn_on");
|
|
@@ -844,7 +844,7 @@ var HaUpdateDevice = class extends HaBaseChildDevice {
|
|
|
844
844
|
});
|
|
845
845
|
}
|
|
846
846
|
registerUpdateCap() {
|
|
847
|
-
this.ctx.registerNativeCap(
|
|
847
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.updateCapability, {
|
|
848
848
|
getStatus: async () => {
|
|
849
849
|
return this.runtimeState.getCapState(UPDATE_CAP) ?? COLD_START$19;
|
|
850
850
|
},
|
|
@@ -928,7 +928,7 @@ var HaSwitchDevice = class extends HaBaseChildDevice {
|
|
|
928
928
|
});
|
|
929
929
|
}
|
|
930
930
|
registerSwitchCap() {
|
|
931
|
-
this.ctx.registerNativeCap(
|
|
931
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.switchCapability, {
|
|
932
932
|
getStatus: async () => {
|
|
933
933
|
return this.runtimeState.getCapState("switch") ?? {
|
|
934
934
|
on: false,
|
|
@@ -1045,7 +1045,7 @@ var HaNumericSensorDevice = class extends HaBaseChildDevice {
|
|
|
1045
1045
|
const stored = this.sourceInfo.quantity;
|
|
1046
1046
|
if (stored !== void 0 && stored !== "device") return stored;
|
|
1047
1047
|
if (unitRaw === void 0) return "none";
|
|
1048
|
-
const learned =
|
|
1048
|
+
const learned = require_ha_instance_settings.quantityFromUnit(unitRaw);
|
|
1049
1049
|
if (learned === void 0 || learned === "device") {
|
|
1050
1050
|
this.deviceLogger.warn("numeric reading refused", {
|
|
1051
1051
|
tags: { deviceId: this.id },
|
|
@@ -1071,7 +1071,7 @@ var HaNumericSensorDevice = class extends HaBaseChildDevice {
|
|
|
1071
1071
|
value: "",
|
|
1072
1072
|
lastFetchedAt: 0
|
|
1073
1073
|
};
|
|
1074
|
-
this.ctx.registerNativeCap(
|
|
1074
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.enumSensorCapability, { getStatus: async () => {
|
|
1075
1075
|
return this.runtimeState.getCapState("enum-sensor") ?? COLD_START;
|
|
1076
1076
|
} });
|
|
1077
1077
|
this.runtimeState.setCapState("enum-sensor", COLD_START);
|
|
@@ -1083,7 +1083,7 @@ var HaNumericSensorDevice = class extends HaBaseChildDevice {
|
|
|
1083
1083
|
temperature: 0,
|
|
1084
1084
|
lastFetchedAt: 0
|
|
1085
1085
|
};
|
|
1086
|
-
this.ctx.registerNativeCap(
|
|
1086
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.temperatureSensorCapability, { getStatus: async () => this.readSlice(COLD_START) });
|
|
1087
1087
|
return;
|
|
1088
1088
|
}
|
|
1089
1089
|
case "humidity-sensor": {
|
|
@@ -1091,7 +1091,7 @@ var HaNumericSensorDevice = class extends HaBaseChildDevice {
|
|
|
1091
1091
|
humidity: 0,
|
|
1092
1092
|
lastFetchedAt: 0
|
|
1093
1093
|
};
|
|
1094
|
-
this.ctx.registerNativeCap(
|
|
1094
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.humiditySensorCapability, { getStatus: async () => this.readSlice(COLD_START) });
|
|
1095
1095
|
return;
|
|
1096
1096
|
}
|
|
1097
1097
|
case "ambient-light-sensor": {
|
|
@@ -1099,7 +1099,7 @@ var HaNumericSensorDevice = class extends HaBaseChildDevice {
|
|
|
1099
1099
|
illuminance: 0,
|
|
1100
1100
|
lastFetchedAt: 0
|
|
1101
1101
|
};
|
|
1102
|
-
this.ctx.registerNativeCap(
|
|
1102
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.ambientLightSensorCapability, { getStatus: async () => this.readSlice(COLD_START) });
|
|
1103
1103
|
return;
|
|
1104
1104
|
}
|
|
1105
1105
|
case "pressure-sensor": {
|
|
@@ -1107,7 +1107,7 @@ var HaNumericSensorDevice = class extends HaBaseChildDevice {
|
|
|
1107
1107
|
pressure: 0,
|
|
1108
1108
|
lastFetchedAt: 0
|
|
1109
1109
|
};
|
|
1110
|
-
this.ctx.registerNativeCap(
|
|
1110
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.pressureSensorCapability, { getStatus: async () => this.readSlice(COLD_START) });
|
|
1111
1111
|
return;
|
|
1112
1112
|
}
|
|
1113
1113
|
case "numeric-sensor": {
|
|
@@ -1115,7 +1115,7 @@ var HaNumericSensorDevice = class extends HaBaseChildDevice {
|
|
|
1115
1115
|
value: 0,
|
|
1116
1116
|
lastFetchedAt: 0
|
|
1117
1117
|
};
|
|
1118
|
-
this.ctx.registerNativeCap(
|
|
1118
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.numericSensorCapability, { getStatus: async () => this.readSlice(COLD_START) });
|
|
1119
1119
|
return;
|
|
1120
1120
|
}
|
|
1121
1121
|
}
|
|
@@ -1207,61 +1207,61 @@ var HaBinarySensorDevice = class extends HaBaseChildDevice {
|
|
|
1207
1207
|
registerCap(name) {
|
|
1208
1208
|
switch (name) {
|
|
1209
1209
|
case "contact":
|
|
1210
|
-
this.ctx.registerNativeCap(
|
|
1210
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.contactCapability, { getStatus: async () => this.readSlice({
|
|
1211
1211
|
entryOpen: false,
|
|
1212
1212
|
lastChangedAt: 0
|
|
1213
1213
|
}) });
|
|
1214
1214
|
return;
|
|
1215
1215
|
case "flood":
|
|
1216
|
-
this.ctx.registerNativeCap(
|
|
1216
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.floodCapability, { getStatus: async () => this.readSlice({
|
|
1217
1217
|
flooded: false,
|
|
1218
1218
|
lastChangedAt: 0
|
|
1219
1219
|
}) });
|
|
1220
1220
|
return;
|
|
1221
1221
|
case "smoke":
|
|
1222
|
-
this.ctx.registerNativeCap(
|
|
1222
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.smokeCapability, { getStatus: async () => this.readSlice({
|
|
1223
1223
|
detected: false,
|
|
1224
1224
|
lastChangedAt: 0
|
|
1225
1225
|
}) });
|
|
1226
1226
|
return;
|
|
1227
1227
|
case "carbon-monoxide":
|
|
1228
|
-
this.ctx.registerNativeCap(
|
|
1228
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.carbonMonoxideCapability, { getStatus: async () => this.readSlice({
|
|
1229
1229
|
detected: false,
|
|
1230
1230
|
lastChangedAt: 0
|
|
1231
1231
|
}) });
|
|
1232
1232
|
return;
|
|
1233
1233
|
case "gas":
|
|
1234
|
-
this.ctx.registerNativeCap(
|
|
1234
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.gasCapability, { getStatus: async () => this.readSlice({
|
|
1235
1235
|
detected: false,
|
|
1236
1236
|
lastChangedAt: 0
|
|
1237
1237
|
}) });
|
|
1238
1238
|
return;
|
|
1239
1239
|
case "tamper":
|
|
1240
|
-
this.ctx.registerNativeCap(
|
|
1240
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.tamperCapability, { getStatus: async () => this.readSlice({
|
|
1241
1241
|
tampered: false,
|
|
1242
1242
|
lastChangedAt: 0
|
|
1243
1243
|
}) });
|
|
1244
1244
|
return;
|
|
1245
1245
|
case "vibration":
|
|
1246
|
-
this.ctx.registerNativeCap(
|
|
1246
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.vibrationCapability, { getStatus: async () => this.readSlice({
|
|
1247
1247
|
detected: false,
|
|
1248
1248
|
lastChangedAt: 0
|
|
1249
1249
|
}) });
|
|
1250
1250
|
return;
|
|
1251
1251
|
case "connectivity":
|
|
1252
|
-
this.ctx.registerNativeCap(
|
|
1252
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.connectivityCapability, { getStatus: async () => this.readSlice({
|
|
1253
1253
|
connected: false,
|
|
1254
1254
|
lastChangedAt: 0
|
|
1255
1255
|
}) });
|
|
1256
1256
|
return;
|
|
1257
1257
|
case "binary":
|
|
1258
|
-
this.ctx.registerNativeCap(
|
|
1258
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.binaryCapability, { getStatus: async () => this.readSlice({
|
|
1259
1259
|
on: false,
|
|
1260
1260
|
lastChangedAt: 0
|
|
1261
1261
|
}) });
|
|
1262
1262
|
return;
|
|
1263
1263
|
case "motion":
|
|
1264
|
-
this.ctx.registerNativeCap(
|
|
1264
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.motionCapability, {
|
|
1265
1265
|
getStatus: async () => this.readSlice({
|
|
1266
1266
|
detected: false,
|
|
1267
1267
|
lastDetectedAt: null,
|
|
@@ -1434,7 +1434,7 @@ var HaLightDevice = class extends HaBaseChildDevice {
|
|
|
1434
1434
|
}
|
|
1435
1435
|
}
|
|
1436
1436
|
registerSwitchCap() {
|
|
1437
|
-
this.ctx.registerNativeCap(
|
|
1437
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.switchCapability, {
|
|
1438
1438
|
getStatus: async () => {
|
|
1439
1439
|
return this.runtimeState.getCapState("switch") ?? {
|
|
1440
1440
|
on: false,
|
|
@@ -1447,7 +1447,7 @@ var HaLightDevice = class extends HaBaseChildDevice {
|
|
|
1447
1447
|
});
|
|
1448
1448
|
}
|
|
1449
1449
|
registerBrightnessCap() {
|
|
1450
|
-
this.ctx.registerNativeCap(
|
|
1450
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.brightnessCapability, {
|
|
1451
1451
|
getStatus: async () => {
|
|
1452
1452
|
return this.runtimeState.getCapState("brightness") ?? {
|
|
1453
1453
|
level: 0,
|
|
@@ -1460,7 +1460,7 @@ var HaLightDevice = class extends HaBaseChildDevice {
|
|
|
1460
1460
|
});
|
|
1461
1461
|
}
|
|
1462
1462
|
registerColorCap() {
|
|
1463
|
-
this.ctx.registerNativeCap(
|
|
1463
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.colorCapability, {
|
|
1464
1464
|
getStatus: async () => {
|
|
1465
1465
|
return this.runtimeState.getCapState("color") ?? {
|
|
1466
1466
|
mode: "rgb",
|
|
@@ -1590,7 +1590,7 @@ var HaCoverDevice = class extends HaBaseChildDevice {
|
|
|
1590
1590
|
});
|
|
1591
1591
|
}
|
|
1592
1592
|
registerCoverCap() {
|
|
1593
|
-
this.ctx.registerNativeCap(
|
|
1593
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.coverCapability, {
|
|
1594
1594
|
getStatus: async () => {
|
|
1595
1595
|
return this.runtimeState.getCapState("cover") ?? COLD_START$18;
|
|
1596
1596
|
},
|
|
@@ -1661,7 +1661,7 @@ var HaValveDevice = class extends HaBaseChildDevice {
|
|
|
1661
1661
|
});
|
|
1662
1662
|
}
|
|
1663
1663
|
registerValveCap() {
|
|
1664
|
-
this.ctx.registerNativeCap(
|
|
1664
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.valveCapability, {
|
|
1665
1665
|
getStatus: async () => {
|
|
1666
1666
|
return this.runtimeState.getCapState("valve") ?? COLD_START$17;
|
|
1667
1667
|
},
|
|
@@ -1735,7 +1735,7 @@ var HaLockDevice = class extends HaBaseChildDevice {
|
|
|
1735
1735
|
});
|
|
1736
1736
|
}
|
|
1737
1737
|
registerLockCap() {
|
|
1738
|
-
this.ctx.registerNativeCap(
|
|
1738
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.lockControlCapability, {
|
|
1739
1739
|
getStatus: async () => {
|
|
1740
1740
|
return this.runtimeState.getCapState("lock-control") ?? COLD_START$16;
|
|
1741
1741
|
},
|
|
@@ -1819,7 +1819,7 @@ var HaClimateDevice = class extends HaBaseChildDevice {
|
|
|
1819
1819
|
this.swingHorizontalModes = readStringArray$4(state.attributes, "swing_horizontal_modes");
|
|
1820
1820
|
const unit = readStringAttr$6(state.attributes, "temperature_unit");
|
|
1821
1821
|
if (unit !== null) {
|
|
1822
|
-
const probe =
|
|
1822
|
+
const probe = require_ha_instance_settings.ingestReading({
|
|
1823
1823
|
value: 0,
|
|
1824
1824
|
rawUnit: unit,
|
|
1825
1825
|
quantity: "temperature"
|
|
@@ -1836,7 +1836,7 @@ var HaClimateDevice = class extends HaBaseChildDevice {
|
|
|
1836
1836
|
this.runtimeState.setCapState("climate-control", climateSliceFromState(state));
|
|
1837
1837
|
}
|
|
1838
1838
|
registerClimateCap() {
|
|
1839
|
-
this.ctx.registerNativeCap(
|
|
1839
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.climateControlCapability, {
|
|
1840
1840
|
getStatus: async () => {
|
|
1841
1841
|
return this.runtimeState.getCapState("climate-control") ?? COLD_START$15;
|
|
1842
1842
|
},
|
|
@@ -1894,7 +1894,7 @@ function climateSliceFromState(state) {
|
|
|
1894
1894
|
const tempUnit = readStringAttr$6(attrs, "temperature_unit") ?? void 0;
|
|
1895
1895
|
const celsius = (raw) => {
|
|
1896
1896
|
if (raw === null) return null;
|
|
1897
|
-
const result =
|
|
1897
|
+
const result = require_ha_instance_settings.ingestReading({
|
|
1898
1898
|
value: raw,
|
|
1899
1899
|
rawUnit: tempUnit,
|
|
1900
1900
|
quantity: "temperature"
|
|
@@ -2043,7 +2043,7 @@ var HaHumidifierDevice = class extends HaBaseChildDevice {
|
|
|
2043
2043
|
});
|
|
2044
2044
|
}
|
|
2045
2045
|
registerHumidifierCap() {
|
|
2046
|
-
this.ctx.registerNativeCap(
|
|
2046
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.humidifierCapability, {
|
|
2047
2047
|
getStatus: async () => {
|
|
2048
2048
|
return this.runtimeState.getCapState("humidifier") ?? COLD_START$14;
|
|
2049
2049
|
},
|
|
@@ -2123,7 +2123,7 @@ var HaWaterHeaterDevice = class extends HaBaseChildDevice {
|
|
|
2123
2123
|
});
|
|
2124
2124
|
}
|
|
2125
2125
|
registerWaterHeaterCap() {
|
|
2126
|
-
this.ctx.registerNativeCap(
|
|
2126
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.waterHeaterCapability, {
|
|
2127
2127
|
getStatus: async () => {
|
|
2128
2128
|
return this.runtimeState.getCapState("water-heater") ?? COLD_START$13;
|
|
2129
2129
|
},
|
|
@@ -2206,7 +2206,7 @@ var HaFanDevice = class extends HaBaseChildDevice {
|
|
|
2206
2206
|
this.runtimeState.setCapState("fan-control", slice);
|
|
2207
2207
|
}
|
|
2208
2208
|
registerSwitchCap() {
|
|
2209
|
-
this.ctx.registerNativeCap(
|
|
2209
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.switchCapability, {
|
|
2210
2210
|
getStatus: async () => {
|
|
2211
2211
|
return this.runtimeState.getCapState("switch") ?? {
|
|
2212
2212
|
on: false,
|
|
@@ -2219,7 +2219,7 @@ var HaFanDevice = class extends HaBaseChildDevice {
|
|
|
2219
2219
|
});
|
|
2220
2220
|
}
|
|
2221
2221
|
registerFanCap() {
|
|
2222
|
-
this.ctx.registerNativeCap(
|
|
2222
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.fanControlCapability, {
|
|
2223
2223
|
getStatus: async () => {
|
|
2224
2224
|
return this.runtimeState.getCapState("fan-control") ?? FAN_COLD_START;
|
|
2225
2225
|
},
|
|
@@ -2287,7 +2287,7 @@ var HaVacuumDevice = class extends HaBaseChildDevice {
|
|
|
2287
2287
|
this.registerVacuumCap();
|
|
2288
2288
|
this.registerRunningCap();
|
|
2289
2289
|
this.runtimeState.setCapState("vacuum-control", COLD_START$12);
|
|
2290
|
-
this.runtimeState.setCapState("running-state",
|
|
2290
|
+
this.runtimeState.setCapState("running-state", require_ha_instance_settings.RUNNING_STATE_COLD);
|
|
2291
2291
|
}
|
|
2292
2292
|
handleStatePush(state) {
|
|
2293
2293
|
if (state.state === "unavailable" || state.state === "unknown") return;
|
|
@@ -2309,13 +2309,13 @@ var HaVacuumDevice = class extends HaBaseChildDevice {
|
|
|
2309
2309
|
});
|
|
2310
2310
|
const fetchedAt = Date.now();
|
|
2311
2311
|
const previous = this.runtimeState.getCapState("running-state");
|
|
2312
|
-
this.runtimeState.setCapState("running-state",
|
|
2312
|
+
this.runtimeState.setCapState("running-state", require_ha_instance_settings.runningFromVacuumState(lifecycleState, previous, fetchedAt, lastChangedAt));
|
|
2313
2313
|
}
|
|
2314
2314
|
registerRunningCap() {
|
|
2315
|
-
this.ctx.registerNativeCap(
|
|
2315
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.runningStateCapability, { getStatus: async () => this.runtimeState.getCapState("running-state") ?? require_ha_instance_settings.RUNNING_STATE_COLD });
|
|
2316
2316
|
}
|
|
2317
2317
|
registerVacuumCap() {
|
|
2318
|
-
this.ctx.registerNativeCap(
|
|
2318
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.vacuumControlCapability, {
|
|
2319
2319
|
getStatus: async () => {
|
|
2320
2320
|
return this.runtimeState.getCapState("vacuum-control") ?? COLD_START$12;
|
|
2321
2321
|
},
|
|
@@ -2387,7 +2387,7 @@ var HaLawnMowerDevice = class extends HaBaseChildDevice {
|
|
|
2387
2387
|
this.registerLawnMowerCap();
|
|
2388
2388
|
this.registerRunningCap();
|
|
2389
2389
|
this.runtimeState.setCapState("lawn-mower-control", COLD_START$11);
|
|
2390
|
-
this.runtimeState.setCapState("running-state",
|
|
2390
|
+
this.runtimeState.setCapState("running-state", require_ha_instance_settings.RUNNING_STATE_COLD);
|
|
2391
2391
|
}
|
|
2392
2392
|
handleStatePush(state) {
|
|
2393
2393
|
if (state.state === "unavailable" || state.state === "unknown") return;
|
|
@@ -2405,13 +2405,13 @@ var HaLawnMowerDevice = class extends HaBaseChildDevice {
|
|
|
2405
2405
|
});
|
|
2406
2406
|
const fetchedAt = Date.now();
|
|
2407
2407
|
const previous = this.runtimeState.getCapState("running-state");
|
|
2408
|
-
this.runtimeState.setCapState("running-state",
|
|
2408
|
+
this.runtimeState.setCapState("running-state", require_ha_instance_settings.runningFromLawnMower(activity, previous, fetchedAt, lastChangedAt));
|
|
2409
2409
|
}
|
|
2410
2410
|
registerRunningCap() {
|
|
2411
|
-
this.ctx.registerNativeCap(
|
|
2411
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.runningStateCapability, { getStatus: async () => this.runtimeState.getCapState("running-state") ?? require_ha_instance_settings.RUNNING_STATE_COLD });
|
|
2412
2412
|
}
|
|
2413
2413
|
registerLawnMowerCap() {
|
|
2414
|
-
this.ctx.registerNativeCap(
|
|
2414
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.lawnMowerControlCapability, {
|
|
2415
2415
|
getStatus: async () => {
|
|
2416
2416
|
return this.runtimeState.getCapState("lawn-mower-control") ?? COLD_START$11;
|
|
2417
2417
|
},
|
|
@@ -2467,7 +2467,7 @@ var HaMediaPlayerDevice = class extends HaBaseChildDevice {
|
|
|
2467
2467
|
this.registerMediaPlayerCap();
|
|
2468
2468
|
this.registerRunningCap();
|
|
2469
2469
|
this.runtimeState.setCapState("media-player", COLD_START$10);
|
|
2470
|
-
this.runtimeState.setCapState("running-state",
|
|
2470
|
+
this.runtimeState.setCapState("running-state", require_ha_instance_settings.RUNNING_STATE_COLD);
|
|
2471
2471
|
}
|
|
2472
2472
|
handleStatePush(state) {
|
|
2473
2473
|
if (state.state === "unavailable" || state.state === "unknown") return;
|
|
@@ -2492,16 +2492,38 @@ var HaMediaPlayerDevice = class extends HaBaseChildDevice {
|
|
|
2492
2492
|
this.runtimeState.setCapState("media-player", slice);
|
|
2493
2493
|
const fetchedAt = Date.now();
|
|
2494
2494
|
const previous = this.runtimeState.getCapState("running-state");
|
|
2495
|
-
this.runtimeState.setCapState("running-state",
|
|
2495
|
+
this.runtimeState.setCapState("running-state", require_ha_instance_settings.runningFromMediaPlayer(playerState, previous, fetchedAt, lastChangedAt));
|
|
2496
2496
|
}
|
|
2497
2497
|
registerRunningCap() {
|
|
2498
|
-
this.ctx.registerNativeCap(
|
|
2498
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.runningStateCapability, { getStatus: async () => this.runtimeState.getCapState("running-state") ?? require_ha_instance_settings.RUNNING_STATE_COLD });
|
|
2499
|
+
}
|
|
2500
|
+
/** One service call per step, in order: HA has no "N steps" form. */
|
|
2501
|
+
async repeatHaService(service, steps) {
|
|
2502
|
+
for (let step = 0; step < steps; step += 1) await this.callHaService("media_player", service);
|
|
2499
2503
|
}
|
|
2500
2504
|
registerMediaPlayerCap() {
|
|
2501
|
-
this.ctx.registerNativeCap(
|
|
2505
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.mediaPlayerCapability, {
|
|
2502
2506
|
getStatus: async () => {
|
|
2503
2507
|
return this.runtimeState.getCapState("media-player") ?? COLD_START$10;
|
|
2504
2508
|
},
|
|
2509
|
+
turnOn: async () => {
|
|
2510
|
+
await this.callHaService("media_player", "turn_on");
|
|
2511
|
+
},
|
|
2512
|
+
turnOff: async () => {
|
|
2513
|
+
await this.callHaService("media_player", "turn_off");
|
|
2514
|
+
},
|
|
2515
|
+
volumeUp: async ({ steps }) => {
|
|
2516
|
+
await this.repeatHaService("volume_up", steps);
|
|
2517
|
+
},
|
|
2518
|
+
volumeDown: async ({ steps }) => {
|
|
2519
|
+
await this.repeatHaService("volume_down", steps);
|
|
2520
|
+
},
|
|
2521
|
+
channelUp: async ({ steps }) => {
|
|
2522
|
+
await this.repeatHaService("media_next_track", steps);
|
|
2523
|
+
},
|
|
2524
|
+
channelDown: async ({ steps }) => {
|
|
2525
|
+
await this.repeatHaService("media_previous_track", steps);
|
|
2526
|
+
},
|
|
2505
2527
|
play: async () => {
|
|
2506
2528
|
await this.callHaService("media_player", "media_play");
|
|
2507
2529
|
},
|
|
@@ -2630,7 +2652,7 @@ var HaAlarmPanelDevice = class extends HaBaseChildDevice {
|
|
|
2630
2652
|
});
|
|
2631
2653
|
}
|
|
2632
2654
|
registerAlarmCap() {
|
|
2633
|
-
this.ctx.registerNativeCap(
|
|
2655
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.alarmPanelCapability, {
|
|
2634
2656
|
getStatus: async () => {
|
|
2635
2657
|
return this.runtimeState.getCapState("alarm-panel") ?? COLD_START$9;
|
|
2636
2658
|
},
|
|
@@ -2745,7 +2767,7 @@ var HaPresenceDevice = class extends HaBaseChildDevice {
|
|
|
2745
2767
|
});
|
|
2746
2768
|
}
|
|
2747
2769
|
registerBatteryCap() {
|
|
2748
|
-
this.ctx.registerNativeCap(
|
|
2770
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.batteryCapability, {
|
|
2749
2771
|
getStatus: async () => this.runtimeState.getCapState("battery") ?? COLD_BATTERY,
|
|
2750
2772
|
wakeForStream: async () => ({
|
|
2751
2773
|
awoke: true,
|
|
@@ -2754,7 +2776,7 @@ var HaPresenceDevice = class extends HaBaseChildDevice {
|
|
|
2754
2776
|
});
|
|
2755
2777
|
}
|
|
2756
2778
|
registerPresenceCap() {
|
|
2757
|
-
this.ctx.registerNativeCap(
|
|
2779
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.presenceCapability, { getStatus: async () => {
|
|
2758
2780
|
return this.runtimeState.getCapState("presence") ?? COLD_START$8;
|
|
2759
2781
|
} });
|
|
2760
2782
|
}
|
|
@@ -2822,7 +2844,7 @@ var HaControlDevice = class extends HaBaseChildDevice {
|
|
|
2822
2844
|
if (slice !== null) this.runtimeState.setCapState("control", slice);
|
|
2823
2845
|
}
|
|
2824
2846
|
registerControlCap() {
|
|
2825
|
-
this.ctx.registerNativeCap(
|
|
2847
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.controlCapability, {
|
|
2826
2848
|
getStatus: async () => {
|
|
2827
2849
|
return this.runtimeState.getCapState("control") ?? {
|
|
2828
2850
|
kind: this.kind,
|
|
@@ -2861,10 +2883,10 @@ var HaControlDevice = class extends HaBaseChildDevice {
|
|
|
2861
2883
|
/** Base-unit value → the unit the upstream entity still speaks. */
|
|
2862
2884
|
toUpstream(value) {
|
|
2863
2885
|
const quantity = this.sourceInfo.quantity;
|
|
2864
|
-
const base = quantity === void 0 ? void 0 :
|
|
2886
|
+
const base = quantity === void 0 ? void 0 : require_ha_instance_settings.baseUnitOf(quantity);
|
|
2865
2887
|
const upstream = this.upstreamUnit;
|
|
2866
2888
|
if (base === void 0 || upstream === void 0 || upstream === base) return value;
|
|
2867
|
-
const converted =
|
|
2889
|
+
const converted = require_ha_instance_settings.tryConvertUnit(value, base, upstream);
|
|
2868
2890
|
if (converted !== void 0) return converted;
|
|
2869
2891
|
this.deviceLogger.warn("numeric reading refused", {
|
|
2870
2892
|
tags: { deviceId: this.id },
|
|
@@ -2965,7 +2987,7 @@ function decodeNumeric(state, lastChangedAt, decode) {
|
|
|
2965
2987
|
if (!Number.isFinite(v)) return null;
|
|
2966
2988
|
const rawUnit = state.attributes["unit_of_measurement"];
|
|
2967
2989
|
const unit = typeof rawUnit === "string" && rawUnit.length > 0 ? rawUnit : void 0;
|
|
2968
|
-
const incoming = unit === void 0 ? void 0 :
|
|
2990
|
+
const incoming = unit === void 0 ? void 0 : require_ha_instance_settings.quantityFromUnit(unit);
|
|
2969
2991
|
if (unit !== void 0 && (incoming === void 0 || incoming === "device")) {
|
|
2970
2992
|
decode.logger.warn("numeric reading refused", {
|
|
2971
2993
|
tags: { deviceId: decode.deviceId },
|
|
@@ -2992,7 +3014,7 @@ function decodeNumeric(state, lastChangedAt, decode) {
|
|
|
2992
3014
|
}
|
|
2993
3015
|
const value = takeReading(decode.logger, decode.deviceId, v, unit, quantity);
|
|
2994
3016
|
if (value === null) return null;
|
|
2995
|
-
decode.note(quantity, unit === void 0 ? void 0 :
|
|
3017
|
+
decode.note(quantity, unit === void 0 ? void 0 : require_ha_instance_settings.normalizeUnit(unit));
|
|
2996
3018
|
const rawMin = state.attributes["min"];
|
|
2997
3019
|
const rawMax = state.attributes["max"];
|
|
2998
3020
|
const rawStep = state.attributes["step"];
|
|
@@ -3012,10 +3034,10 @@ function decodeNumeric(state, lastChangedAt, decode) {
|
|
|
3012
3034
|
/** A step is a difference: °F's offset must not move it. */
|
|
3013
3035
|
function convertStep(step, unit, quantity) {
|
|
3014
3036
|
if (step === void 0) return void 0;
|
|
3015
|
-
const base =
|
|
3016
|
-
const from = unit === void 0 ? void 0 :
|
|
3037
|
+
const base = require_ha_instance_settings.baseUnitOf(quantity);
|
|
3038
|
+
const from = unit === void 0 ? void 0 : require_ha_instance_settings.normalizeUnit(unit);
|
|
3017
3039
|
if (base === void 0 || from === void 0 || from === base) return step;
|
|
3018
|
-
return
|
|
3040
|
+
return require_ha_instance_settings.tryConvertDelta(step, from, base);
|
|
3019
3041
|
}
|
|
3020
3042
|
//#endregion
|
|
3021
3043
|
//#region src/devices/ha-script-device.ts
|
|
@@ -3058,7 +3080,7 @@ var HaScriptDevice = class extends HaBaseChildDevice {
|
|
|
3058
3080
|
});
|
|
3059
3081
|
}
|
|
3060
3082
|
registerScriptCap() {
|
|
3061
|
-
this.ctx.registerNativeCap(
|
|
3083
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.scriptRunnerCapability, {
|
|
3062
3084
|
getStatus: async () => {
|
|
3063
3085
|
return this.runtimeState.getCapState("script-runner") ?? COLD_START$7;
|
|
3064
3086
|
},
|
|
@@ -3117,7 +3139,7 @@ var HaAutomationDevice = class extends HaBaseChildDevice {
|
|
|
3117
3139
|
});
|
|
3118
3140
|
}
|
|
3119
3141
|
registerAutomationCap() {
|
|
3120
|
-
this.ctx.registerNativeCap(
|
|
3142
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.automationControlCapability, {
|
|
3121
3143
|
getStatus: async () => {
|
|
3122
3144
|
return this.runtimeState.getCapState("automation-control") ?? COLD_START$6;
|
|
3123
3145
|
},
|
|
@@ -3176,7 +3198,7 @@ var HaNotifierDevice = class extends HaBaseChildDevice {
|
|
|
3176
3198
|
handleStatePush(_state) {}
|
|
3177
3199
|
registerNotifierCap() {
|
|
3178
3200
|
const serviceName = this.entityId.split(".")[1] ?? "";
|
|
3179
|
-
this.ctx.registerNativeCap(
|
|
3201
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.notifierCapability, {
|
|
3180
3202
|
getStatus: async () => {
|
|
3181
3203
|
return this.runtimeState.getCapState("notifier") ?? COLD_START$5;
|
|
3182
3204
|
},
|
|
@@ -3272,7 +3294,7 @@ var HaPowerMeterDevice = class extends HaBaseChildDevice {
|
|
|
3272
3294
|
if (update !== null) this.runtimeState.setCapState("power-meter", update);
|
|
3273
3295
|
}
|
|
3274
3296
|
registerPowerMeterCap() {
|
|
3275
|
-
this.ctx.registerNativeCap(
|
|
3297
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.powerMeterCapability, { getStatus: async () => {
|
|
3276
3298
|
return this.runtimeState.getCapState("power-meter") ?? COLD_START$4;
|
|
3277
3299
|
} });
|
|
3278
3300
|
}
|
|
@@ -3351,7 +3373,7 @@ var HaAirQualityDevice = class extends HaBaseChildDevice {
|
|
|
3351
3373
|
if (update !== null) this.runtimeState.setCapState("air-quality-sensor", update);
|
|
3352
3374
|
}
|
|
3353
3375
|
registerCap() {
|
|
3354
|
-
this.ctx.registerNativeCap(
|
|
3376
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.airQualitySensorCapability, { getStatus: async () => {
|
|
3355
3377
|
return this.runtimeState.getCapState("air-quality-sensor") ?? COLD_START$3;
|
|
3356
3378
|
} });
|
|
3357
3379
|
}
|
|
@@ -3426,7 +3448,7 @@ var HaEnumSensorDevice = class extends HaBaseChildDevice {
|
|
|
3426
3448
|
constructor(ctx) {
|
|
3427
3449
|
const persisted = ctx.persistedConfig ?? {};
|
|
3428
3450
|
super(ctx, persisted.role !== void 0 ? { role: persisted.role } : {});
|
|
3429
|
-
const parsedFormat =
|
|
3451
|
+
const parsedFormat = require_ha_instance_settings.EnumSensorDateTimeFormatSchema.safeParse(persisted.dateTimeFormat);
|
|
3430
3452
|
this.dateTimeFormat = parsedFormat.success ? parsedFormat.data : void 0;
|
|
3431
3453
|
if (this.dateTimeFormat !== void 0) this.updateSourceInfo({ dateTimeShape: this.dateTimeFormat });
|
|
3432
3454
|
this.registerCap();
|
|
@@ -3448,7 +3470,7 @@ var HaEnumSensorDevice = class extends HaBaseChildDevice {
|
|
|
3448
3470
|
});
|
|
3449
3471
|
}
|
|
3450
3472
|
registerCap() {
|
|
3451
|
-
this.ctx.registerNativeCap(
|
|
3473
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.enumSensorCapability, { getStatus: async () => {
|
|
3452
3474
|
return this.runtimeState.getCapState("enum-sensor") ?? this.coldStart();
|
|
3453
3475
|
} });
|
|
3454
3476
|
}
|
|
@@ -3547,7 +3569,7 @@ var HaEventEmitterDevice = class extends HaBaseChildDevice {
|
|
|
3547
3569
|
timestamp: Date.now(),
|
|
3548
3570
|
seq: ++this.#seq
|
|
3549
3571
|
};
|
|
3550
|
-
this.ctx.eventBus.emit(
|
|
3572
|
+
this.ctx.eventBus.emit(require_ha_instance_settings.createEvent(require_ha_instance_settings.EventCategory.EventEmitted, {
|
|
3551
3573
|
type: "device",
|
|
3552
3574
|
id: this.stableId,
|
|
3553
3575
|
deviceId: this.id,
|
|
@@ -3561,7 +3583,7 @@ var HaEventEmitterDevice = class extends HaBaseChildDevice {
|
|
|
3561
3583
|
this.runtimeState.setCapState("event-emitter", slice);
|
|
3562
3584
|
}
|
|
3563
3585
|
registerEventEmitterCap() {
|
|
3564
|
-
this.ctx.registerNativeCap(
|
|
3586
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.eventEmitterCapability, { getStatus: async () => this.readSlice() });
|
|
3565
3587
|
}
|
|
3566
3588
|
readSlice() {
|
|
3567
3589
|
return this.runtimeState.getCapState("event-emitter") ?? {
|
|
@@ -3644,7 +3666,7 @@ var HaWeatherDevice = class extends HaBaseChildDevice {
|
|
|
3644
3666
|
this.runtimeState.setCapState("weather", slice);
|
|
3645
3667
|
}
|
|
3646
3668
|
registerWeatherCap() {
|
|
3647
|
-
this.ctx.registerNativeCap(
|
|
3669
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.weatherCapability, { getStatus: async () => {
|
|
3648
3670
|
return this.runtimeState.getCapState("weather") ?? COLD_START$1;
|
|
3649
3671
|
} });
|
|
3650
3672
|
}
|
|
@@ -3742,7 +3764,7 @@ var HaImageDevice = class extends HaBaseChildDevice {
|
|
|
3742
3764
|
this.deviceLogger.warn("ha-image: failed to build absolute image url", { meta: {
|
|
3743
3765
|
entityId: this.entityId,
|
|
3744
3766
|
brokerId: this.brokerId,
|
|
3745
|
-
error:
|
|
3767
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
3746
3768
|
} });
|
|
3747
3769
|
url = null;
|
|
3748
3770
|
}
|
|
@@ -3774,7 +3796,7 @@ var HaImageDevice = class extends HaBaseChildDevice {
|
|
|
3774
3796
|
return typeof raw === "string" && raw.length > 0 ? raw : null;
|
|
3775
3797
|
}
|
|
3776
3798
|
registerImageCap() {
|
|
3777
|
-
this.ctx.registerNativeCap(
|
|
3799
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.imageCapability, { getStatus: async () => {
|
|
3778
3800
|
return this.runtimeState.getCapState("image") ?? COLD_START;
|
|
3779
3801
|
} });
|
|
3780
3802
|
}
|
|
@@ -3868,28 +3890,28 @@ function pickHaChildClass(capabilities) {
|
|
|
3868
3890
|
//#region src/ha-container-reconcile.ts
|
|
3869
3891
|
/** Validates a persisted Container `entities[]` blob (mirrors `haEntitySchema`
|
|
3870
3892
|
* in ha-container-device.ts). Returns `[]` on absent/invalid input. */
|
|
3871
|
-
var containerEntitySchema =
|
|
3872
|
-
entityId:
|
|
3873
|
-
deviceType:
|
|
3874
|
-
capabilities:
|
|
3875
|
-
role:
|
|
3876
|
-
unitOfMeasurement:
|
|
3877
|
-
deviceClass:
|
|
3878
|
-
options:
|
|
3879
|
-
features:
|
|
3880
|
-
name:
|
|
3881
|
-
dateTimeFormat:
|
|
3882
|
-
entityCategory:
|
|
3883
|
-
meterEntities:
|
|
3884
|
-
power:
|
|
3885
|
-
energy:
|
|
3886
|
-
voltage:
|
|
3887
|
-
current:
|
|
3893
|
+
var containerEntitySchema = require_ha_instance_settings.object({
|
|
3894
|
+
entityId: require_ha_instance_settings.string(),
|
|
3895
|
+
deviceType: require_ha_instance_settings._enum(require_ha_instance_settings.DeviceType),
|
|
3896
|
+
capabilities: require_ha_instance_settings.array(require_ha_instance_settings.string()),
|
|
3897
|
+
role: require_ha_instance_settings.string().optional(),
|
|
3898
|
+
unitOfMeasurement: require_ha_instance_settings.string().optional(),
|
|
3899
|
+
deviceClass: require_ha_instance_settings.string().optional(),
|
|
3900
|
+
options: require_ha_instance_settings.array(require_ha_instance_settings.string()).optional(),
|
|
3901
|
+
features: require_ha_instance_settings.array(require_ha_instance_settings.string()).optional(),
|
|
3902
|
+
name: require_ha_instance_settings.string().optional(),
|
|
3903
|
+
dateTimeFormat: require_ha_instance_settings.EnumSensorDateTimeFormatSchema.optional(),
|
|
3904
|
+
entityCategory: require_ha_instance_settings._enum(["config", "diagnostic"]).optional(),
|
|
3905
|
+
meterEntities: require_ha_instance_settings.object({
|
|
3906
|
+
power: require_ha_instance_settings.string().optional(),
|
|
3907
|
+
energy: require_ha_instance_settings.string().optional(),
|
|
3908
|
+
voltage: require_ha_instance_settings.string().optional(),
|
|
3909
|
+
current: require_ha_instance_settings.string().optional()
|
|
3888
3910
|
}).optional(),
|
|
3889
|
-
foldedInto:
|
|
3911
|
+
foldedInto: require_ha_instance_settings.string().optional()
|
|
3890
3912
|
});
|
|
3891
3913
|
function parseContainerEntities(raw) {
|
|
3892
|
-
const result =
|
|
3914
|
+
const result = require_ha_instance_settings.array(containerEntitySchema).safeParse(raw);
|
|
3893
3915
|
return result.success ? result.data : [];
|
|
3894
3916
|
}
|
|
3895
3917
|
/**
|
|
@@ -4043,45 +4065,45 @@ function deriveChildLayout(entities) {
|
|
|
4043
4065
|
}
|
|
4044
4066
|
//#endregion
|
|
4045
4067
|
//#region src/devices/ha-container-device.ts
|
|
4046
|
-
var haEntitySchema =
|
|
4047
|
-
entityId:
|
|
4048
|
-
deviceType:
|
|
4049
|
-
capabilities:
|
|
4050
|
-
role:
|
|
4051
|
-
unitOfMeasurement:
|
|
4052
|
-
deviceClass:
|
|
4068
|
+
var haEntitySchema = require_ha_instance_settings.object({
|
|
4069
|
+
entityId: require_ha_instance_settings.string(),
|
|
4070
|
+
deviceType: require_ha_instance_settings._enum(require_ha_instance_settings.DeviceType),
|
|
4071
|
+
capabilities: require_ha_instance_settings.array(require_ha_instance_settings.string()),
|
|
4072
|
+
role: require_ha_instance_settings.string().optional(),
|
|
4073
|
+
unitOfMeasurement: require_ha_instance_settings.string().optional(),
|
|
4074
|
+
deviceClass: require_ha_instance_settings.string().optional(),
|
|
4053
4075
|
/** HA `options` attribute — present on select / input_select entities.
|
|
4054
4076
|
* Persisted at adoption so the control slice is seeded immediately. */
|
|
4055
|
-
options:
|
|
4077
|
+
options: require_ha_instance_settings.array(require_ha_instance_settings.string()).optional(),
|
|
4056
4078
|
/** Mapping-derived DeviceFeature flags (e.g. `lock-open`) — surfaced on the
|
|
4057
4079
|
* child's `device.features` so the UI gates feature affordances. */
|
|
4058
|
-
features:
|
|
4080
|
+
features: require_ha_instance_settings.array(require_ha_instance_settings.string()).optional(),
|
|
4059
4081
|
/** HA friendly_name — persisted so the child device name survives
|
|
4060
4082
|
* a reboot without a discovery round-trip. Falls back to entityId. */
|
|
4061
|
-
name:
|
|
4083
|
+
name: require_ha_instance_settings.string().optional(),
|
|
4062
4084
|
/** Locale-render shape for a `DateTimeSensor`-role enum sensor
|
|
4063
4085
|
* (`device_class=timestamp` → `'datetime'`, `date` → `'date'`). Seeded into
|
|
4064
4086
|
* the enum-sensor cap slice by the child so the UI renders the ISO `value`. */
|
|
4065
|
-
dateTimeFormat:
|
|
4087
|
+
dateTimeFormat: require_ha_instance_settings.EnumSensorDateTimeFormatSchema.optional(),
|
|
4066
4088
|
/** HA `entity_category` — `config` / `diagnostic` / absent (a normal
|
|
4067
4089
|
* control). Drives the accordion section the child renders in. */
|
|
4068
|
-
entityCategory:
|
|
4090
|
+
entityCategory: require_ha_instance_settings._enum(["config", "diagnostic"]).optional(),
|
|
4069
4091
|
/** Sibling meter sensors this entity reads as its own power-meter (D951). */
|
|
4070
|
-
meterEntities:
|
|
4071
|
-
power:
|
|
4072
|
-
energy:
|
|
4073
|
-
voltage:
|
|
4074
|
-
current:
|
|
4092
|
+
meterEntities: require_ha_instance_settings.object({
|
|
4093
|
+
power: require_ha_instance_settings.string().optional(),
|
|
4094
|
+
energy: require_ha_instance_settings.string().optional(),
|
|
4095
|
+
voltage: require_ha_instance_settings.string().optional(),
|
|
4096
|
+
current: require_ha_instance_settings.string().optional()
|
|
4075
4097
|
}).optional(),
|
|
4076
4098
|
/** Set on a meter sensor folded into another entity: sectioned as Diagnostic. */
|
|
4077
|
-
foldedInto:
|
|
4099
|
+
foldedInto: require_ha_instance_settings.string().optional()
|
|
4078
4100
|
});
|
|
4079
|
-
var haContainerSchema =
|
|
4080
|
-
system:
|
|
4081
|
-
brokerId:
|
|
4082
|
-
haDeviceId:
|
|
4083
|
-
integrationId:
|
|
4084
|
-
entities:
|
|
4101
|
+
var haContainerSchema = require_ha_instance_settings.object({
|
|
4102
|
+
system: require_ha_instance_settings.literal("homeassistant"),
|
|
4103
|
+
brokerId: require_ha_instance_settings.string(),
|
|
4104
|
+
haDeviceId: require_ha_instance_settings.string(),
|
|
4105
|
+
integrationId: require_ha_instance_settings.string().optional(),
|
|
4106
|
+
entities: require_ha_instance_settings.array(haEntitySchema).default([])
|
|
4085
4107
|
});
|
|
4086
4108
|
/**
|
|
4087
4109
|
* A container entity is a battery source when the HA domain mapping
|
|
@@ -4091,7 +4113,7 @@ var haContainerSchema = require_ha_broker_source.object({
|
|
|
4091
4113
|
* device_class is the original HA attribute that drove it.
|
|
4092
4114
|
*/
|
|
4093
4115
|
function isBatteryEntity(entity) {
|
|
4094
|
-
return entity.role ===
|
|
4116
|
+
return entity.role === require_ha_instance_settings.DeviceRole.BatterySensor || entity.deviceClass === "battery";
|
|
4095
4117
|
}
|
|
4096
4118
|
/**
|
|
4097
4119
|
* A battery entity is BINARY when HA exposes it as a `binary_sensor`
|
|
@@ -4183,7 +4205,7 @@ function batteryEntityId(entities) {
|
|
|
4183
4205
|
* persist initial config, build context, construct via factory, then
|
|
4184
4206
|
* run the child's own probe + accessory + activate phases.
|
|
4185
4207
|
*/
|
|
4186
|
-
var HaContainerDevice = class HaContainerDevice extends
|
|
4208
|
+
var HaContainerDevice = class HaContainerDevice extends require_ha_instance_settings.BaseDevice {
|
|
4187
4209
|
features;
|
|
4188
4210
|
brokerId;
|
|
4189
4211
|
integrationId;
|
|
@@ -4204,10 +4226,10 @@ var HaContainerDevice = class HaContainerDevice extends require_ha_broker_source
|
|
|
4204
4226
|
constructor(ctx) {
|
|
4205
4227
|
const cfg = haContainerSchema.parse(ctx.persistedConfig ?? {});
|
|
4206
4228
|
const batteryEntity = selectBatteryEntity(cfg.entities);
|
|
4207
|
-
super(ctx, haContainerSchema, { type:
|
|
4229
|
+
super(ctx, haContainerSchema, { type: require_ha_instance_settings.DeviceType.Container });
|
|
4208
4230
|
this.brokerId = cfg.brokerId;
|
|
4209
4231
|
this.integrationId = cfg.integrationId;
|
|
4210
|
-
this.features = batteryEntity !== null ? [
|
|
4232
|
+
this.features = batteryEntity !== null ? [require_ha_instance_settings.DeviceFeature.Resyncable, require_ha_instance_settings.DeviceFeature.BatteryOperated] : [require_ha_instance_settings.DeviceFeature.Resyncable];
|
|
4211
4233
|
this.entityIds = new Set(cfg.entities.map((e) => e.entityId));
|
|
4212
4234
|
this.online = false;
|
|
4213
4235
|
this.batteryEntityId = batteryEntityId(cfg.entities);
|
|
@@ -4218,7 +4240,7 @@ var HaContainerDevice = class HaContainerDevice extends require_ha_broker_source
|
|
|
4218
4240
|
sleeping: false,
|
|
4219
4241
|
lastUpdated: 0
|
|
4220
4242
|
};
|
|
4221
|
-
this.ctx.registerNativeCap(
|
|
4243
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.batteryCapability, {
|
|
4222
4244
|
getStatus: async () => this.runtimeState.getCapState("battery") ?? COLD,
|
|
4223
4245
|
wakeForStream: async () => ({
|
|
4224
4246
|
awoke: true,
|
|
@@ -4293,7 +4315,7 @@ var HaContainerDevice = class HaContainerDevice extends require_ha_broker_source
|
|
|
4293
4315
|
} catch (err) {
|
|
4294
4316
|
this.ctx.logger.warn("ha-container failed to subscribe entities for availability", { meta: {
|
|
4295
4317
|
brokerId: this.brokerId,
|
|
4296
|
-
error:
|
|
4318
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
4297
4319
|
} });
|
|
4298
4320
|
}
|
|
4299
4321
|
}
|
|
@@ -4334,7 +4356,7 @@ var HaContainerDevice = class HaContainerDevice extends require_ha_broker_source
|
|
|
4334
4356
|
} catch (err) {
|
|
4335
4357
|
this.ctx.logger.warn("ha-container failed to persist childLayout", { meta: {
|
|
4336
4358
|
haDeviceId: this.config.values.haDeviceId,
|
|
4337
|
-
error:
|
|
4359
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
4338
4360
|
} });
|
|
4339
4361
|
}
|
|
4340
4362
|
}
|
|
@@ -4413,10 +4435,10 @@ var HaContainerDevice = class HaContainerDevice extends require_ha_broker_source
|
|
|
4413
4435
|
* entity-backed (no `entityId`), so it does NOT extend the HA child base
|
|
4414
4436
|
* device — it is a thin standalone `BaseDevice` subclass.
|
|
4415
4437
|
*/
|
|
4416
|
-
var haEventBusDeviceSchema =
|
|
4417
|
-
system:
|
|
4418
|
-
brokerId:
|
|
4419
|
-
kind:
|
|
4438
|
+
var haEventBusDeviceSchema = require_ha_instance_settings.object({
|
|
4439
|
+
system: require_ha_instance_settings.literal("homeassistant"),
|
|
4440
|
+
brokerId: require_ha_instance_settings.string(),
|
|
4441
|
+
kind: require_ha_instance_settings.literal("event-bus")
|
|
4420
4442
|
});
|
|
4421
4443
|
/**
|
|
4422
4444
|
* Case 2 — generic per-broker HA bus-event aggregator.
|
|
@@ -4437,7 +4459,7 @@ var haEventBusDeviceSchema = require_ha_broker_source.object({
|
|
|
4437
4459
|
* directly via {@link handleBusEvent} (the integration manager subscribes
|
|
4438
4460
|
* to the curated bus event types and threads each fire through the addon).
|
|
4439
4461
|
*/
|
|
4440
|
-
var HaEventBusDevice = class extends
|
|
4462
|
+
var HaEventBusDevice = class extends require_ha_instance_settings.BaseDevice {
|
|
4441
4463
|
features = [];
|
|
4442
4464
|
/** Monotonic sequence — two identical events still produce distinct slice
|
|
4443
4465
|
* writes / bus emissions. */
|
|
@@ -4476,7 +4498,7 @@ var HaEventBusDevice = class extends require_ha_broker_source.BaseDevice {
|
|
|
4476
4498
|
timestamp: Date.now(),
|
|
4477
4499
|
seq: ++this.#seq
|
|
4478
4500
|
};
|
|
4479
|
-
this.ctx.eventBus.emit(
|
|
4501
|
+
this.ctx.eventBus.emit(require_ha_instance_settings.createEvent(require_ha_instance_settings.EventCategory.EventEmitted, {
|
|
4480
4502
|
type: "device",
|
|
4481
4503
|
id: this.stableId,
|
|
4482
4504
|
deviceId: this.id,
|
|
@@ -4490,7 +4512,7 @@ var HaEventBusDevice = class extends require_ha_broker_source.BaseDevice {
|
|
|
4490
4512
|
this.runtimeState.setCapState("event-emitter", slice);
|
|
4491
4513
|
}
|
|
4492
4514
|
registerEventEmitterCap() {
|
|
4493
|
-
this.ctx.registerNativeCap(
|
|
4515
|
+
this.ctx.registerNativeCap(require_ha_instance_settings.eventEmitterCapability, { getStatus: async () => this.readSlice() });
|
|
4494
4516
|
}
|
|
4495
4517
|
readSlice() {
|
|
4496
4518
|
return this.runtimeState.getCapState("event-emitter") ?? {
|
|
@@ -4525,7 +4547,7 @@ async function cascadeRemoveDevicesForBroker(input) {
|
|
|
4525
4547
|
tags: { deviceId: d.id },
|
|
4526
4548
|
meta: {
|
|
4527
4549
|
brokerId,
|
|
4528
|
-
error:
|
|
4550
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
4529
4551
|
}
|
|
4530
4552
|
});
|
|
4531
4553
|
}
|
|
@@ -5431,7 +5453,7 @@ var HaIntegrationManager = class HaIntegrationManager {
|
|
|
5431
5453
|
} catch (err) {
|
|
5432
5454
|
this.opts.logger.warn("ha-broker onConnected handler threw", { meta: {
|
|
5433
5455
|
brokerId: this.opts.id,
|
|
5434
|
-
error:
|
|
5456
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
5435
5457
|
} });
|
|
5436
5458
|
}
|
|
5437
5459
|
},
|
|
@@ -5446,12 +5468,12 @@ var HaIntegrationManager = class HaIntegrationManager {
|
|
|
5446
5468
|
} catch (err) {
|
|
5447
5469
|
this.opts.logger.warn("ha-broker onDisconnected handler threw", { meta: {
|
|
5448
5470
|
brokerId: this.opts.id,
|
|
5449
|
-
error:
|
|
5471
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
5450
5472
|
} });
|
|
5451
5473
|
}
|
|
5452
5474
|
},
|
|
5453
5475
|
onReconnectError: (err, nextDelayMs) => {
|
|
5454
|
-
this.lastError =
|
|
5476
|
+
this.lastError = require_ha_instance_settings.errMsg(err);
|
|
5455
5477
|
this.opts.logger.warn("ha-broker reconnect attempt failed", { meta: {
|
|
5456
5478
|
brokerId: this.opts.id,
|
|
5457
5479
|
error: this.lastError,
|
|
@@ -5632,7 +5654,7 @@ var HaIntegrationManager = class HaIntegrationManager {
|
|
|
5632
5654
|
this.opts.logger.warn("ha-broker bus-event subscribe failed", { meta: {
|
|
5633
5655
|
brokerId: this.opts.id,
|
|
5634
5656
|
eventType,
|
|
5635
|
-
error:
|
|
5657
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
5636
5658
|
} });
|
|
5637
5659
|
}
|
|
5638
5660
|
}
|
|
@@ -5668,7 +5690,7 @@ var HaIntegrationManager = class HaIntegrationManager {
|
|
|
5668
5690
|
return false;
|
|
5669
5691
|
}
|
|
5670
5692
|
emitBrokerMessage(subscriptionId, entityId, state) {
|
|
5671
|
-
this.opts.eventBus.emit(
|
|
5693
|
+
this.opts.eventBus.emit(require_ha_instance_settings.createEvent("broker.message", {
|
|
5672
5694
|
type: "addon",
|
|
5673
5695
|
id: this.opts.id
|
|
5674
5696
|
}, {
|
|
@@ -5690,7 +5712,7 @@ var HaIntegrationManager = class HaIntegrationManager {
|
|
|
5690
5712
|
} catch (err) {
|
|
5691
5713
|
this.opts.logger.debug("ha get_config probe failed", { meta: {
|
|
5692
5714
|
brokerId: this.opts.id,
|
|
5693
|
-
error:
|
|
5715
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
5694
5716
|
} });
|
|
5695
5717
|
}
|
|
5696
5718
|
}
|
|
@@ -5698,7 +5720,7 @@ var HaIntegrationManager = class HaIntegrationManager {
|
|
|
5698
5720
|
if (this.status === next) return;
|
|
5699
5721
|
this.status = next;
|
|
5700
5722
|
this.lastCheckedAt = Date.now();
|
|
5701
|
-
this.opts.eventBus.emit(
|
|
5723
|
+
this.opts.eventBus.emit(require_ha_instance_settings.createEvent("broker.status-changed", {
|
|
5702
5724
|
type: "addon",
|
|
5703
5725
|
id: this.opts.id
|
|
5704
5726
|
}, {
|
|
@@ -5719,7 +5741,7 @@ var HaIntegrationManager = class HaIntegrationManager {
|
|
|
5719
5741
|
this.lastError = "Home Assistant host / URL is required";
|
|
5720
5742
|
this.setStatus("unreachable");
|
|
5721
5743
|
} else {
|
|
5722
|
-
this.lastError =
|
|
5744
|
+
this.lastError = require_ha_instance_settings.errMsg(err);
|
|
5723
5745
|
this.setStatus("error");
|
|
5724
5746
|
}
|
|
5725
5747
|
}
|
|
@@ -5740,25 +5762,19 @@ var TEST_HANDSHAKE_TIMEOUT_MS = 8e3;
|
|
|
5740
5762
|
* `dataDir` being protected by filesystem permissions. (MQTT broker
|
|
5741
5763
|
* encrypts at rest; HA broker does not yet — TODO for hardening.)
|
|
5742
5764
|
*/
|
|
5743
|
-
var HaBrokerEntrySchema =
|
|
5744
|
-
id:
|
|
5745
|
-
name:
|
|
5746
|
-
/**
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
* Populated when the wizard's "+ New Integration" flow created the
|
|
5757
|
-
* broker; null when the operator added it via the Settings panel
|
|
5758
|
-
* directly. Used by the integration listener to keep the broker
|
|
5759
|
-
* list in sync with the integrations table (cascade-delete on
|
|
5760
|
-
* integration removal). */
|
|
5761
|
-
integrationId: require_ha_broker_source.string().optional()
|
|
5765
|
+
var HaBrokerEntrySchema = require_ha_instance_settings.object({
|
|
5766
|
+
id: require_ha_instance_settings.string(),
|
|
5767
|
+
name: require_ha_instance_settings.string(),
|
|
5768
|
+
/** Supervisor (token from env) or long-lived (persisted); picks which fields below are used. */
|
|
5769
|
+
authKind: require_ha_instance_settings._enum(["long-lived", "supervisor"]),
|
|
5770
|
+
/** Origin URL of the HA instance; long-lived only. */
|
|
5771
|
+
baseUrl: require_ha_instance_settings.string().optional(),
|
|
5772
|
+
accessToken: require_ha_instance_settings.string().optional().meta({ secret: true }),
|
|
5773
|
+
supervisorToken: require_ha_instance_settings.string().optional().meta({ secret: true }),
|
|
5774
|
+
/** Optional FK to the integration record that spawned this broker. */
|
|
5775
|
+
integrationId: require_ha_instance_settings.string().optional(),
|
|
5776
|
+
/** The instance's own switch (D996); absent means on. */
|
|
5777
|
+
enabled: require_ha_instance_settings.boolean().optional()
|
|
5762
5778
|
});
|
|
5763
5779
|
/**
|
|
5764
5780
|
* Validates a `broker.add` `settings` payload for the HA kind. Accepts:
|
|
@@ -5767,10 +5783,10 @@ var HaBrokerEntrySchema = require_ha_broker_source.object({
|
|
|
5767
5783
|
* persisting allows the addon to keep working after the env var
|
|
5768
5784
|
* rotates out of scope)
|
|
5769
5785
|
*/
|
|
5770
|
-
var HaAddSettingsSchema =
|
|
5771
|
-
baseUrl:
|
|
5772
|
-
accessToken:
|
|
5773
|
-
}),
|
|
5786
|
+
var HaAddSettingsSchema = require_ha_instance_settings.union([require_ha_instance_settings.object({
|
|
5787
|
+
baseUrl: require_ha_instance_settings.url(),
|
|
5788
|
+
accessToken: require_ha_instance_settings.string().min(1).meta({ secret: true })
|
|
5789
|
+
}), require_ha_instance_settings.object({ supervisorToken: require_ha_instance_settings.string().min(1).meta({ secret: true }) })]);
|
|
5774
5790
|
function settingsToAuth(settings) {
|
|
5775
5791
|
const parsed = HaAddSettingsSchema.parse(settings);
|
|
5776
5792
|
if ("supervisorToken" in parsed) return {
|
|
@@ -5783,6 +5799,34 @@ function settingsToAuth(settings) {
|
|
|
5783
5799
|
accessToken: parsed.accessToken
|
|
5784
5800
|
};
|
|
5785
5801
|
}
|
|
5802
|
+
/** The flat entry fields of an auth config: the one place the two shapes meet. */
|
|
5803
|
+
function authToEntryFields(auth) {
|
|
5804
|
+
return auth.kind === "supervisor" ? {
|
|
5805
|
+
authKind: "supervisor",
|
|
5806
|
+
supervisorToken: auth.supervisorToken
|
|
5807
|
+
} : {
|
|
5808
|
+
authKind: "long-lived",
|
|
5809
|
+
baseUrl: auth.baseUrl,
|
|
5810
|
+
accessToken: auth.accessToken
|
|
5811
|
+
};
|
|
5812
|
+
}
|
|
5813
|
+
/** The auth config an entry stands for; null while the entry is not filled in. */
|
|
5814
|
+
function haAuthOf(entry) {
|
|
5815
|
+
const parsed = HaAuthConfigSchema.safeParse(entry.authKind === "supervisor" ? {
|
|
5816
|
+
kind: "supervisor",
|
|
5817
|
+
supervisorToken: entry.supervisorToken ?? ""
|
|
5818
|
+
} : {
|
|
5819
|
+
kind: "long-lived",
|
|
5820
|
+
baseUrl: entry.baseUrl ?? "",
|
|
5821
|
+
accessToken: entry.accessToken ?? ""
|
|
5822
|
+
});
|
|
5823
|
+
return parsed.success ? parsed.data : null;
|
|
5824
|
+
}
|
|
5825
|
+
/** The flat settings payload (`settingsToAuth` shape) of an entry; null while it is not filled in. */
|
|
5826
|
+
function entryToSettings(entry) {
|
|
5827
|
+
const auth = haAuthOf(entry);
|
|
5828
|
+
return auth === null ? null : authToSettings(auth);
|
|
5829
|
+
}
|
|
5786
5830
|
function authToSettings(auth) {
|
|
5787
5831
|
if (auth.kind === "supervisor") return { supervisorToken: auth.supervisorToken };
|
|
5788
5832
|
return {
|
|
@@ -5845,13 +5889,13 @@ var HaBrokerRegistry = class {
|
|
|
5845
5889
|
* started concurrently; transient errors don't block bootstrap. */
|
|
5846
5890
|
async restore(entries) {
|
|
5847
5891
|
this.seedCounterFromEntries(entries);
|
|
5848
|
-
await Promise.all(entries.map(async (entry) => {
|
|
5892
|
+
await Promise.all(entries.filter((entry) => entry.enabled !== false && haAuthOf(entry) !== null).map(async (entry) => {
|
|
5849
5893
|
try {
|
|
5850
5894
|
await this.startManagerForEntry(entry);
|
|
5851
5895
|
} catch (err) {
|
|
5852
5896
|
this.logger.warn("ha-broker bootstrap failed", { meta: {
|
|
5853
5897
|
brokerId: entry.id,
|
|
5854
|
-
error:
|
|
5898
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
5855
5899
|
} });
|
|
5856
5900
|
}
|
|
5857
5901
|
}));
|
|
@@ -5906,7 +5950,7 @@ var HaBrokerRegistry = class {
|
|
|
5906
5950
|
const entry = {
|
|
5907
5951
|
id: options.id !== void 0 ? this.adoptId(options.id) : this.allocateId(),
|
|
5908
5952
|
name,
|
|
5909
|
-
auth,
|
|
5953
|
+
...authToEntryFields(auth),
|
|
5910
5954
|
...options.integrationId !== void 0 ? { integrationId: options.integrationId } : {}
|
|
5911
5955
|
};
|
|
5912
5956
|
await this.startManagerForEntry(entry);
|
|
@@ -5927,7 +5971,7 @@ var HaBrokerRegistry = class {
|
|
|
5927
5971
|
return {
|
|
5928
5972
|
id,
|
|
5929
5973
|
name: existing.getInfo().name,
|
|
5930
|
-
auth
|
|
5974
|
+
...authToEntryFields(auth)
|
|
5931
5975
|
};
|
|
5932
5976
|
}
|
|
5933
5977
|
/**
|
|
@@ -5945,7 +5989,7 @@ var HaBrokerRegistry = class {
|
|
|
5945
5989
|
} catch (err) {
|
|
5946
5990
|
return {
|
|
5947
5991
|
ok: false,
|
|
5948
|
-
error:
|
|
5992
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
5949
5993
|
};
|
|
5950
5994
|
}
|
|
5951
5995
|
const startedAt = Date.now();
|
|
@@ -5983,7 +6027,7 @@ var HaBrokerRegistry = class {
|
|
|
5983
6027
|
client?.stop();
|
|
5984
6028
|
resolve({
|
|
5985
6029
|
ok: false,
|
|
5986
|
-
error:
|
|
6030
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
5987
6031
|
});
|
|
5988
6032
|
}
|
|
5989
6033
|
}
|
|
@@ -5995,7 +6039,7 @@ var HaBrokerRegistry = class {
|
|
|
5995
6039
|
client?.stop();
|
|
5996
6040
|
resolve({
|
|
5997
6041
|
ok: false,
|
|
5998
|
-
error:
|
|
6042
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
5999
6043
|
});
|
|
6000
6044
|
});
|
|
6001
6045
|
});
|
|
@@ -6110,10 +6154,12 @@ var HaBrokerRegistry = class {
|
|
|
6110
6154
|
this.logger.debug("ha-broker already active — reusing existing connection", { meta: { brokerId: entry.id } });
|
|
6111
6155
|
return;
|
|
6112
6156
|
}
|
|
6157
|
+
const auth = haAuthOf(entry);
|
|
6158
|
+
if (auth === null) throw new Error(`ha-broker '${entry.id}' has no complete credentials`);
|
|
6113
6159
|
const manager = new HaIntegrationManager({
|
|
6114
6160
|
id: entry.id,
|
|
6115
6161
|
name: entry.name,
|
|
6116
|
-
auth
|
|
6162
|
+
auth,
|
|
6117
6163
|
logger: this.logger,
|
|
6118
6164
|
eventBus: this.eventBus,
|
|
6119
6165
|
onConnected: (brokerId) => this.onBrokerConnected?.(brokerId),
|
|
@@ -6190,27 +6236,27 @@ function mapHaEntityToCamStack(input) {
|
|
|
6190
6236
|
const { domain, deviceClass, attributes } = input;
|
|
6191
6237
|
switch (domain) {
|
|
6192
6238
|
case "switch": return {
|
|
6193
|
-
deviceType:
|
|
6194
|
-
role: deviceClass === "outlet" ?
|
|
6239
|
+
deviceType: require_ha_instance_settings.DeviceType.Switch,
|
|
6240
|
+
role: deviceClass === "outlet" ? require_ha_instance_settings.DeviceRole.Outlet : null,
|
|
6195
6241
|
capabilities: ["switch"]
|
|
6196
6242
|
};
|
|
6197
6243
|
case "input_boolean": return {
|
|
6198
|
-
deviceType:
|
|
6199
|
-
role:
|
|
6244
|
+
deviceType: require_ha_instance_settings.DeviceType.Switch,
|
|
6245
|
+
role: require_ha_instance_settings.DeviceRole.BinaryHelper,
|
|
6200
6246
|
capabilities: ["switch"]
|
|
6201
6247
|
};
|
|
6202
6248
|
case "remote": return {
|
|
6203
|
-
deviceType:
|
|
6249
|
+
deviceType: require_ha_instance_settings.DeviceType.Switch,
|
|
6204
6250
|
role: null,
|
|
6205
6251
|
capabilities: ["switch"]
|
|
6206
6252
|
};
|
|
6207
6253
|
case "siren": return {
|
|
6208
|
-
deviceType:
|
|
6254
|
+
deviceType: require_ha_instance_settings.DeviceType.Siren,
|
|
6209
6255
|
role: null,
|
|
6210
6256
|
capabilities: ["switch"]
|
|
6211
6257
|
};
|
|
6212
6258
|
case "update": return {
|
|
6213
|
-
deviceType:
|
|
6259
|
+
deviceType: require_ha_instance_settings.DeviceType.Update,
|
|
6214
6260
|
role: null,
|
|
6215
6261
|
capabilities: ["update"]
|
|
6216
6262
|
};
|
|
@@ -6228,7 +6274,7 @@ function mapHaEntityToCamStack(input) {
|
|
|
6228
6274
|
case "alarm_control_panel": return mapAlarmPanel(attributes);
|
|
6229
6275
|
case "person":
|
|
6230
6276
|
case "device_tracker": return {
|
|
6231
|
-
deviceType:
|
|
6277
|
+
deviceType: require_ha_instance_settings.DeviceType.Presence,
|
|
6232
6278
|
role: null,
|
|
6233
6279
|
capabilities: ["presence"]
|
|
6234
6280
|
};
|
|
@@ -6237,49 +6283,49 @@ function mapHaEntityToCamStack(input) {
|
|
|
6237
6283
|
case "camera": return mapImage(attributes);
|
|
6238
6284
|
case "number":
|
|
6239
6285
|
case "input_number": return {
|
|
6240
|
-
deviceType:
|
|
6241
|
-
role:
|
|
6286
|
+
deviceType: require_ha_instance_settings.DeviceType.Control,
|
|
6287
|
+
role: require_ha_instance_settings.DeviceRole.NumericControl,
|
|
6242
6288
|
capabilities: ["control"]
|
|
6243
6289
|
};
|
|
6244
6290
|
case "select":
|
|
6245
6291
|
case "input_select": return {
|
|
6246
|
-
deviceType:
|
|
6247
|
-
role:
|
|
6292
|
+
deviceType: require_ha_instance_settings.DeviceType.Control,
|
|
6293
|
+
role: require_ha_instance_settings.DeviceRole.SelectControl,
|
|
6248
6294
|
capabilities: ["control"]
|
|
6249
6295
|
};
|
|
6250
6296
|
case "text":
|
|
6251
6297
|
case "input_text": return {
|
|
6252
|
-
deviceType:
|
|
6253
|
-
role:
|
|
6298
|
+
deviceType: require_ha_instance_settings.DeviceType.Control,
|
|
6299
|
+
role: require_ha_instance_settings.DeviceRole.TextControl,
|
|
6254
6300
|
capabilities: ["control"]
|
|
6255
6301
|
};
|
|
6256
6302
|
case "input_datetime":
|
|
6257
6303
|
case "date":
|
|
6258
6304
|
case "time":
|
|
6259
6305
|
case "datetime": return {
|
|
6260
|
-
deviceType:
|
|
6261
|
-
role:
|
|
6306
|
+
deviceType: require_ha_instance_settings.DeviceType.Control,
|
|
6307
|
+
role: require_ha_instance_settings.DeviceRole.DateTimeControl,
|
|
6262
6308
|
capabilities: ["control"]
|
|
6263
6309
|
};
|
|
6264
6310
|
case "button":
|
|
6265
6311
|
case "input_button":
|
|
6266
6312
|
case "scene": return {
|
|
6267
|
-
deviceType:
|
|
6313
|
+
deviceType: require_ha_instance_settings.DeviceType.Button,
|
|
6268
6314
|
role: null,
|
|
6269
6315
|
capabilities: ["button"]
|
|
6270
6316
|
};
|
|
6271
6317
|
case "event": return {
|
|
6272
|
-
deviceType:
|
|
6318
|
+
deviceType: require_ha_instance_settings.DeviceType.EventEmitter,
|
|
6273
6319
|
role: null,
|
|
6274
6320
|
capabilities: ["event-emitter"]
|
|
6275
6321
|
};
|
|
6276
6322
|
case "script": return {
|
|
6277
|
-
deviceType:
|
|
6323
|
+
deviceType: require_ha_instance_settings.DeviceType.Script,
|
|
6278
6324
|
role: null,
|
|
6279
6325
|
capabilities: ["script-runner"]
|
|
6280
6326
|
};
|
|
6281
6327
|
case "automation": return {
|
|
6282
|
-
deviceType:
|
|
6328
|
+
deviceType: require_ha_instance_settings.DeviceType.Automation,
|
|
6283
6329
|
role: null,
|
|
6284
6330
|
capabilities: ["automation-control"]
|
|
6285
6331
|
};
|
|
@@ -6305,12 +6351,12 @@ function mapLight(attributes) {
|
|
|
6305
6351
|
const hasMired = modes.some((m) => m === "color_temp");
|
|
6306
6352
|
if (hasRgb || hasMired) {
|
|
6307
6353
|
capabilities.push("color");
|
|
6308
|
-
if (modes.includes("rgb") || modes.includes("rgbw") || modes.includes("rgbww") || modes.includes("xy")) features.push(
|
|
6309
|
-
if (modes.includes("hs")) features.push(
|
|
6310
|
-
if (hasMired) features.push(
|
|
6354
|
+
if (modes.includes("rgb") || modes.includes("rgbw") || modes.includes("rgbww") || modes.includes("xy")) features.push(require_ha_instance_settings.DeviceFeature.LightColorRgb);
|
|
6355
|
+
if (modes.includes("hs")) features.push(require_ha_instance_settings.DeviceFeature.LightColorHsv);
|
|
6356
|
+
if (hasMired) features.push(require_ha_instance_settings.DeviceFeature.LightColorMired);
|
|
6311
6357
|
}
|
|
6312
6358
|
return {
|
|
6313
|
-
deviceType:
|
|
6359
|
+
deviceType: require_ha_instance_settings.DeviceType.Light,
|
|
6314
6360
|
role: null,
|
|
6315
6361
|
capabilities,
|
|
6316
6362
|
features
|
|
@@ -6318,11 +6364,11 @@ function mapLight(attributes) {
|
|
|
6318
6364
|
}
|
|
6319
6365
|
function mapLock(attributes) {
|
|
6320
6366
|
const features = [];
|
|
6321
|
-
if (attributes["code_format"] !== void 0 && attributes["code_format"] !== null) features.push(
|
|
6367
|
+
if (attributes["code_format"] !== void 0 && attributes["code_format"] !== null) features.push(require_ha_instance_settings.DeviceFeature.LockPinRequired);
|
|
6322
6368
|
const sf = readNumericAttr(attributes, "supported_features");
|
|
6323
|
-
if (sf !== null && (sf & 1) !== 0) features.push(
|
|
6369
|
+
if (sf !== null && (sf & 1) !== 0) features.push(require_ha_instance_settings.DeviceFeature.LockOpen);
|
|
6324
6370
|
return {
|
|
6325
|
-
deviceType:
|
|
6371
|
+
deviceType: require_ha_instance_settings.DeviceType.Lock,
|
|
6326
6372
|
role: null,
|
|
6327
6373
|
capabilities: ["lock-control"],
|
|
6328
6374
|
features
|
|
@@ -6331,11 +6377,11 @@ function mapLock(attributes) {
|
|
|
6331
6377
|
function mapCover(attributes, deviceClass) {
|
|
6332
6378
|
const features = [];
|
|
6333
6379
|
const sf = readNumericAttr(attributes, "supported_features");
|
|
6334
|
-
if (sf !== null && (sf & 4) !== 0) features.push(
|
|
6335
|
-
if (sf !== null && (sf & 8) !== 0) features.push(
|
|
6336
|
-
if (sf !== null && (sf & 128) !== 0) features.push(
|
|
6380
|
+
if (sf !== null && (sf & 4) !== 0) features.push(require_ha_instance_settings.DeviceFeature.CoverPositionable);
|
|
6381
|
+
if (sf !== null && (sf & 8) !== 0) features.push(require_ha_instance_settings.DeviceFeature.CoverStoppable);
|
|
6382
|
+
if (sf !== null && (sf & 128) !== 0) features.push(require_ha_instance_settings.DeviceFeature.CoverTilt);
|
|
6337
6383
|
return {
|
|
6338
|
-
deviceType:
|
|
6384
|
+
deviceType: require_ha_instance_settings.DeviceType.Cover,
|
|
6339
6385
|
role: coverRoleFor(deviceClass),
|
|
6340
6386
|
capabilities: ["cover"],
|
|
6341
6387
|
features
|
|
@@ -6345,23 +6391,23 @@ function mapCover(attributes, deviceClass) {
|
|
|
6345
6391
|
function coverRoleFor(deviceClass) {
|
|
6346
6392
|
switch (deviceClass) {
|
|
6347
6393
|
case "blind":
|
|
6348
|
-
case "shade": return
|
|
6349
|
-
case "shutter": return
|
|
6350
|
-
case "awning": return
|
|
6351
|
-
case "curtain": return
|
|
6352
|
-
case "garage": return
|
|
6353
|
-
case "gate": return
|
|
6354
|
-
case "door": return
|
|
6355
|
-
case "window": return
|
|
6394
|
+
case "shade": return require_ha_instance_settings.DeviceRole.CoverBlind;
|
|
6395
|
+
case "shutter": return require_ha_instance_settings.DeviceRole.CoverShutter;
|
|
6396
|
+
case "awning": return require_ha_instance_settings.DeviceRole.CoverAwning;
|
|
6397
|
+
case "curtain": return require_ha_instance_settings.DeviceRole.CoverCurtain;
|
|
6398
|
+
case "garage": return require_ha_instance_settings.DeviceRole.CoverGarage;
|
|
6399
|
+
case "gate": return require_ha_instance_settings.DeviceRole.CoverGate;
|
|
6400
|
+
case "door": return require_ha_instance_settings.DeviceRole.CoverDoor;
|
|
6401
|
+
case "window": return require_ha_instance_settings.DeviceRole.CoverWindow;
|
|
6356
6402
|
default: return null;
|
|
6357
6403
|
}
|
|
6358
6404
|
}
|
|
6359
6405
|
function mapValve(attributes, _deviceClass) {
|
|
6360
6406
|
const features = [];
|
|
6361
6407
|
const sf = readNumericAttr(attributes, "supported_features");
|
|
6362
|
-
if (sf !== null && (sf & 4) !== 0) features.push(
|
|
6408
|
+
if (sf !== null && (sf & 4) !== 0) features.push(require_ha_instance_settings.DeviceFeature.ValvePositionable);
|
|
6363
6409
|
return {
|
|
6364
|
-
deviceType:
|
|
6410
|
+
deviceType: require_ha_instance_settings.DeviceType.Valve,
|
|
6365
6411
|
role: null,
|
|
6366
6412
|
capabilities: ["valve"],
|
|
6367
6413
|
features
|
|
@@ -6369,7 +6415,7 @@ function mapValve(attributes, _deviceClass) {
|
|
|
6369
6415
|
}
|
|
6370
6416
|
function mapHumidifier(_attributes, _deviceClass) {
|
|
6371
6417
|
return {
|
|
6372
|
-
deviceType:
|
|
6418
|
+
deviceType: require_ha_instance_settings.DeviceType.Humidifier,
|
|
6373
6419
|
role: null,
|
|
6374
6420
|
capabilities: ["humidifier"],
|
|
6375
6421
|
features: []
|
|
@@ -6377,7 +6423,7 @@ function mapHumidifier(_attributes, _deviceClass) {
|
|
|
6377
6423
|
}
|
|
6378
6424
|
function mapWaterHeater(_attributes, _deviceClass) {
|
|
6379
6425
|
return {
|
|
6380
|
-
deviceType:
|
|
6426
|
+
deviceType: require_ha_instance_settings.DeviceType.WaterHeater,
|
|
6381
6427
|
role: null,
|
|
6382
6428
|
capabilities: ["water-heater"],
|
|
6383
6429
|
features: []
|
|
@@ -6387,13 +6433,13 @@ function mapFan(attributes) {
|
|
|
6387
6433
|
const features = [];
|
|
6388
6434
|
const sf = readNumericAttr(attributes, "supported_features");
|
|
6389
6435
|
if (sf !== null) {
|
|
6390
|
-
if ((sf & 1) !== 0) features.push(
|
|
6391
|
-
if ((sf & 2) !== 0) features.push(
|
|
6392
|
-
if ((sf & 4) !== 0) features.push(
|
|
6393
|
-
if ((sf & 8) !== 0) features.push(
|
|
6436
|
+
if ((sf & 1) !== 0) features.push(require_ha_instance_settings.DeviceFeature.FanSpeed);
|
|
6437
|
+
if ((sf & 2) !== 0) features.push(require_ha_instance_settings.DeviceFeature.FanOscillating);
|
|
6438
|
+
if ((sf & 4) !== 0) features.push(require_ha_instance_settings.DeviceFeature.FanDirection);
|
|
6439
|
+
if ((sf & 8) !== 0) features.push(require_ha_instance_settings.DeviceFeature.FanPreset);
|
|
6394
6440
|
}
|
|
6395
6441
|
return {
|
|
6396
|
-
deviceType:
|
|
6442
|
+
deviceType: require_ha_instance_settings.DeviceType.Fan,
|
|
6397
6443
|
role: null,
|
|
6398
6444
|
capabilities: ["switch", "fan-control"],
|
|
6399
6445
|
features
|
|
@@ -6401,7 +6447,7 @@ function mapFan(attributes) {
|
|
|
6401
6447
|
}
|
|
6402
6448
|
function mapVacuum(_attributes) {
|
|
6403
6449
|
return {
|
|
6404
|
-
deviceType:
|
|
6450
|
+
deviceType: require_ha_instance_settings.DeviceType.Vacuum,
|
|
6405
6451
|
role: null,
|
|
6406
6452
|
capabilities: ["vacuum-control"],
|
|
6407
6453
|
features: []
|
|
@@ -6409,7 +6455,7 @@ function mapVacuum(_attributes) {
|
|
|
6409
6455
|
}
|
|
6410
6456
|
function mapLawnMower(_attributes) {
|
|
6411
6457
|
return {
|
|
6412
|
-
deviceType:
|
|
6458
|
+
deviceType: require_ha_instance_settings.DeviceType.LawnMower,
|
|
6413
6459
|
role: null,
|
|
6414
6460
|
capabilities: ["lawn-mower-control"],
|
|
6415
6461
|
features: []
|
|
@@ -6419,13 +6465,13 @@ function mapClimate(attributes) {
|
|
|
6419
6465
|
const features = [];
|
|
6420
6466
|
const sf = readNumericAttr(attributes, "supported_features");
|
|
6421
6467
|
if (sf !== null) {
|
|
6422
|
-
if ((sf & 2) !== 0) features.push(
|
|
6423
|
-
if ((sf & 4) !== 0) features.push(
|
|
6424
|
-
if ((sf & 8) !== 0) features.push(
|
|
6425
|
-
if ((sf & 16) !== 0) features.push(
|
|
6468
|
+
if ((sf & 2) !== 0) features.push(require_ha_instance_settings.DeviceFeature.ClimateDualSetpoint);
|
|
6469
|
+
if ((sf & 4) !== 0) features.push(require_ha_instance_settings.DeviceFeature.ClimateHumidity);
|
|
6470
|
+
if ((sf & 8) !== 0) features.push(require_ha_instance_settings.DeviceFeature.ClimateFanMode);
|
|
6471
|
+
if ((sf & 16) !== 0) features.push(require_ha_instance_settings.DeviceFeature.ClimatePreset);
|
|
6426
6472
|
}
|
|
6427
6473
|
return {
|
|
6428
|
-
deviceType:
|
|
6474
|
+
deviceType: require_ha_instance_settings.DeviceType.Thermostat,
|
|
6429
6475
|
role: null,
|
|
6430
6476
|
capabilities: ["climate-control"],
|
|
6431
6477
|
features
|
|
@@ -6435,19 +6481,21 @@ function mapMediaPlayer(attributes) {
|
|
|
6435
6481
|
const features = [];
|
|
6436
6482
|
const sf = readNumericAttr(attributes, "supported_features");
|
|
6437
6483
|
if (sf !== null) {
|
|
6438
|
-
if ((sf & 2) !== 0) features.push(
|
|
6439
|
-
if ((sf & 4) !== 0) features.push(
|
|
6440
|
-
if ((sf & 8) !== 0) features.push(
|
|
6441
|
-
if ((sf & 16) !== 0) features.push(
|
|
6442
|
-
if ((sf & 32) !== 0) features.push(
|
|
6443
|
-
if ((sf &
|
|
6444
|
-
if ((sf &
|
|
6445
|
-
if ((sf &
|
|
6446
|
-
if ((sf &
|
|
6447
|
-
if ((sf &
|
|
6484
|
+
if ((sf & 2) !== 0) features.push(require_ha_instance_settings.DeviceFeature.MediaPlayerSeek);
|
|
6485
|
+
if ((sf & 4) !== 0) features.push(require_ha_instance_settings.DeviceFeature.MediaPlayerVolume);
|
|
6486
|
+
if ((sf & 8) !== 0) features.push(require_ha_instance_settings.DeviceFeature.MediaPlayerMute);
|
|
6487
|
+
if ((sf & 16) !== 0) features.push(require_ha_instance_settings.DeviceFeature.MediaPlayerPrevious);
|
|
6488
|
+
if ((sf & 32) !== 0) features.push(require_ha_instance_settings.DeviceFeature.MediaPlayerNext);
|
|
6489
|
+
if ((sf & 384) === 384) features.push(require_ha_instance_settings.DeviceFeature.MediaPlayerPower);
|
|
6490
|
+
if ((sf & 1024) !== 0) features.push(require_ha_instance_settings.DeviceFeature.MediaPlayerVolumeStep);
|
|
6491
|
+
if ((sf & 512) !== 0) features.push(require_ha_instance_settings.DeviceFeature.MediaPlayerPlayMedia);
|
|
6492
|
+
if ((sf & 2048) !== 0) features.push(require_ha_instance_settings.DeviceFeature.MediaPlayerSelectSource);
|
|
6493
|
+
if ((sf & 4096) !== 0) features.push(require_ha_instance_settings.DeviceFeature.MediaPlayerStop);
|
|
6494
|
+
if ((sf & 32768) !== 0) features.push(require_ha_instance_settings.DeviceFeature.MediaPlayerShuffle);
|
|
6495
|
+
if ((sf & 262144) !== 0) features.push(require_ha_instance_settings.DeviceFeature.MediaPlayerRepeat);
|
|
6448
6496
|
}
|
|
6449
6497
|
return {
|
|
6450
|
-
deviceType:
|
|
6498
|
+
deviceType: require_ha_instance_settings.DeviceType.MediaPlayer,
|
|
6451
6499
|
role: null,
|
|
6452
6500
|
capabilities: ["media-player"],
|
|
6453
6501
|
features
|
|
@@ -6455,9 +6503,9 @@ function mapMediaPlayer(attributes) {
|
|
|
6455
6503
|
}
|
|
6456
6504
|
function mapAlarmPanel(attributes) {
|
|
6457
6505
|
const features = [];
|
|
6458
|
-
if (attributes["code_format"] !== void 0 && attributes["code_format"] !== null) features.push(
|
|
6506
|
+
if (attributes["code_format"] !== void 0 && attributes["code_format"] !== null) features.push(require_ha_instance_settings.DeviceFeature.AlarmPinRequired);
|
|
6459
6507
|
return {
|
|
6460
|
-
deviceType:
|
|
6508
|
+
deviceType: require_ha_instance_settings.DeviceType.AlarmPanel,
|
|
6461
6509
|
role: null,
|
|
6462
6510
|
capabilities: ["alarm-panel"],
|
|
6463
6511
|
features
|
|
@@ -6465,21 +6513,21 @@ function mapAlarmPanel(attributes) {
|
|
|
6465
6513
|
}
|
|
6466
6514
|
function mapNotifier(_attributes) {
|
|
6467
6515
|
return {
|
|
6468
|
-
deviceType:
|
|
6469
|
-
role:
|
|
6516
|
+
deviceType: require_ha_instance_settings.DeviceType.Notifier,
|
|
6517
|
+
role: require_ha_instance_settings.DeviceRole.GenericNotifier,
|
|
6470
6518
|
capabilities: ["notifier"]
|
|
6471
6519
|
};
|
|
6472
6520
|
}
|
|
6473
6521
|
function mapWeather(_attributes) {
|
|
6474
6522
|
return {
|
|
6475
|
-
deviceType:
|
|
6523
|
+
deviceType: require_ha_instance_settings.DeviceType.Weather,
|
|
6476
6524
|
role: null,
|
|
6477
6525
|
capabilities: ["weather"]
|
|
6478
6526
|
};
|
|
6479
6527
|
}
|
|
6480
6528
|
function mapImage(_attributes) {
|
|
6481
6529
|
return {
|
|
6482
|
-
deviceType:
|
|
6530
|
+
deviceType: require_ha_instance_settings.DeviceType.Image,
|
|
6483
6531
|
role: null,
|
|
6484
6532
|
capabilities: ["image"]
|
|
6485
6533
|
};
|
|
@@ -6493,60 +6541,60 @@ function isTemperatureUnit(unit) {
|
|
|
6493
6541
|
function mapNumericSensor(deviceClass, _attributes) {
|
|
6494
6542
|
switch (deviceClass) {
|
|
6495
6543
|
case "temperature": return {
|
|
6496
|
-
deviceType:
|
|
6497
|
-
role:
|
|
6544
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6545
|
+
role: require_ha_instance_settings.DeviceRole.TemperatureSensor,
|
|
6498
6546
|
capabilities: ["temperature-sensor"]
|
|
6499
6547
|
};
|
|
6500
6548
|
case "humidity": return {
|
|
6501
|
-
deviceType:
|
|
6502
|
-
role:
|
|
6549
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6550
|
+
role: require_ha_instance_settings.DeviceRole.HumiditySensor,
|
|
6503
6551
|
capabilities: ["humidity-sensor"]
|
|
6504
6552
|
};
|
|
6505
6553
|
case "illuminance": return {
|
|
6506
|
-
deviceType:
|
|
6507
|
-
role:
|
|
6554
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6555
|
+
role: require_ha_instance_settings.DeviceRole.AmbientLightSensor,
|
|
6508
6556
|
capabilities: ["ambient-light-sensor"]
|
|
6509
6557
|
};
|
|
6510
6558
|
case "pressure":
|
|
6511
6559
|
case "atmospheric_pressure": return {
|
|
6512
|
-
deviceType:
|
|
6513
|
-
role:
|
|
6560
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6561
|
+
role: require_ha_instance_settings.DeviceRole.PressureSensor,
|
|
6514
6562
|
capabilities: ["pressure-sensor"]
|
|
6515
6563
|
};
|
|
6516
6564
|
case "wind_speed": return {
|
|
6517
|
-
deviceType:
|
|
6518
|
-
role:
|
|
6565
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6566
|
+
role: require_ha_instance_settings.DeviceRole.WindSensor,
|
|
6519
6567
|
capabilities: ["numeric-sensor"]
|
|
6520
6568
|
};
|
|
6521
6569
|
case "precipitation":
|
|
6522
6570
|
case "precipitation_intensity": return {
|
|
6523
|
-
deviceType:
|
|
6524
|
-
role:
|
|
6571
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6572
|
+
role: require_ha_instance_settings.DeviceRole.RainSensor,
|
|
6525
6573
|
capabilities: ["numeric-sensor"]
|
|
6526
6574
|
};
|
|
6527
6575
|
case "irradiance": return {
|
|
6528
|
-
deviceType:
|
|
6529
|
-
role:
|
|
6576
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6577
|
+
role: require_ha_instance_settings.DeviceRole.SolarRadiationSensor,
|
|
6530
6578
|
capabilities: ["numeric-sensor"]
|
|
6531
6579
|
};
|
|
6532
6580
|
case "power": return {
|
|
6533
|
-
deviceType:
|
|
6534
|
-
role:
|
|
6581
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6582
|
+
role: require_ha_instance_settings.DeviceRole.PowerSensor,
|
|
6535
6583
|
capabilities: ["power-meter"]
|
|
6536
6584
|
};
|
|
6537
6585
|
case "energy": return {
|
|
6538
|
-
deviceType:
|
|
6539
|
-
role:
|
|
6586
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6587
|
+
role: require_ha_instance_settings.DeviceRole.EnergySensor,
|
|
6540
6588
|
capabilities: ["power-meter"]
|
|
6541
6589
|
};
|
|
6542
6590
|
case "voltage": return {
|
|
6543
|
-
deviceType:
|
|
6544
|
-
role:
|
|
6591
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6592
|
+
role: require_ha_instance_settings.DeviceRole.VoltageSensor,
|
|
6545
6593
|
capabilities: ["power-meter"]
|
|
6546
6594
|
};
|
|
6547
6595
|
case "current": return {
|
|
6548
|
-
deviceType:
|
|
6549
|
-
role:
|
|
6596
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6597
|
+
role: require_ha_instance_settings.DeviceRole.CurrentSensor,
|
|
6550
6598
|
capabilities: ["power-meter"]
|
|
6551
6599
|
};
|
|
6552
6600
|
case "pm1":
|
|
@@ -6559,46 +6607,46 @@ function mapNumericSensor(deviceClass, _attributes) {
|
|
|
6559
6607
|
case "sulphur_dioxide":
|
|
6560
6608
|
case "volatile_organic_compounds":
|
|
6561
6609
|
case "aqi": return {
|
|
6562
|
-
deviceType:
|
|
6563
|
-
role:
|
|
6610
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6611
|
+
role: require_ha_instance_settings.DeviceRole.AirQualitySensor,
|
|
6564
6612
|
capabilities: ["air-quality-sensor"]
|
|
6565
6613
|
};
|
|
6566
6614
|
case "battery": return {
|
|
6567
|
-
deviceType:
|
|
6568
|
-
role:
|
|
6615
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6616
|
+
role: require_ha_instance_settings.DeviceRole.BatterySensor,
|
|
6569
6617
|
capabilities: ["numeric-sensor"]
|
|
6570
6618
|
};
|
|
6571
6619
|
case "enum": return {
|
|
6572
|
-
deviceType:
|
|
6573
|
-
role:
|
|
6620
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6621
|
+
role: require_ha_instance_settings.DeviceRole.EnumSensor,
|
|
6574
6622
|
capabilities: ["enum-sensor"]
|
|
6575
6623
|
};
|
|
6576
6624
|
case "timestamp": return {
|
|
6577
|
-
deviceType:
|
|
6578
|
-
role:
|
|
6625
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6626
|
+
role: require_ha_instance_settings.DeviceRole.DateTimeSensor,
|
|
6579
6627
|
capabilities: ["enum-sensor"],
|
|
6580
6628
|
dateTimeFormat: "datetime"
|
|
6581
6629
|
};
|
|
6582
6630
|
case "date": return {
|
|
6583
|
-
deviceType:
|
|
6584
|
-
role:
|
|
6631
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6632
|
+
role: require_ha_instance_settings.DeviceRole.DateTimeSensor,
|
|
6585
6633
|
capabilities: ["enum-sensor"],
|
|
6586
6634
|
dateTimeFormat: "date"
|
|
6587
6635
|
};
|
|
6588
6636
|
case null:
|
|
6589
6637
|
if (isTemperatureUnit(_attributes["unit_of_measurement"])) return {
|
|
6590
|
-
deviceType:
|
|
6591
|
-
role:
|
|
6638
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6639
|
+
role: require_ha_instance_settings.DeviceRole.TemperatureSensor,
|
|
6592
6640
|
capabilities: ["temperature-sensor"]
|
|
6593
6641
|
};
|
|
6594
6642
|
return {
|
|
6595
|
-
deviceType:
|
|
6596
|
-
role:
|
|
6643
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6644
|
+
role: require_ha_instance_settings.DeviceRole.EnumSensor,
|
|
6597
6645
|
capabilities: ["enum-sensor"]
|
|
6598
6646
|
};
|
|
6599
6647
|
default: return {
|
|
6600
|
-
deviceType:
|
|
6601
|
-
role:
|
|
6648
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6649
|
+
role: require_ha_instance_settings.DeviceRole.NumericSensor,
|
|
6602
6650
|
capabilities: ["numeric-sensor"]
|
|
6603
6651
|
};
|
|
6604
6652
|
}
|
|
@@ -6609,67 +6657,67 @@ function mapBinarySensor(deviceClass) {
|
|
|
6609
6657
|
case "occupancy":
|
|
6610
6658
|
case "presence":
|
|
6611
6659
|
case "moving": return {
|
|
6612
|
-
deviceType:
|
|
6613
|
-
role:
|
|
6660
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6661
|
+
role: require_ha_instance_settings.DeviceRole.MotionSensor,
|
|
6614
6662
|
capabilities: ["motion"]
|
|
6615
6663
|
};
|
|
6616
6664
|
case "door":
|
|
6617
6665
|
case "window":
|
|
6618
6666
|
case "garage_door":
|
|
6619
6667
|
case "opening": return {
|
|
6620
|
-
deviceType:
|
|
6621
|
-
role:
|
|
6668
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6669
|
+
role: require_ha_instance_settings.DeviceRole.ContactSensor,
|
|
6622
6670
|
capabilities: ["contact"]
|
|
6623
6671
|
};
|
|
6624
6672
|
case "moisture": return {
|
|
6625
|
-
deviceType:
|
|
6626
|
-
role:
|
|
6673
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6674
|
+
role: require_ha_instance_settings.DeviceRole.LeakSensor,
|
|
6627
6675
|
capabilities: ["flood"]
|
|
6628
6676
|
};
|
|
6629
6677
|
case "smoke": return {
|
|
6630
|
-
deviceType:
|
|
6631
|
-
role:
|
|
6678
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6679
|
+
role: require_ha_instance_settings.DeviceRole.SmokeSensor,
|
|
6632
6680
|
capabilities: ["smoke"]
|
|
6633
6681
|
};
|
|
6634
6682
|
case "carbon_monoxide": return {
|
|
6635
|
-
deviceType:
|
|
6636
|
-
role:
|
|
6683
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6684
|
+
role: require_ha_instance_settings.DeviceRole.COSensor,
|
|
6637
6685
|
capabilities: ["carbon-monoxide"]
|
|
6638
6686
|
};
|
|
6639
6687
|
case "gas": return {
|
|
6640
|
-
deviceType:
|
|
6641
|
-
role:
|
|
6688
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6689
|
+
role: require_ha_instance_settings.DeviceRole.GasSensor,
|
|
6642
6690
|
capabilities: ["gas"]
|
|
6643
6691
|
};
|
|
6644
6692
|
case "tamper":
|
|
6645
6693
|
case "safety": return {
|
|
6646
|
-
deviceType:
|
|
6647
|
-
role:
|
|
6694
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6695
|
+
role: require_ha_instance_settings.DeviceRole.TamperSensor,
|
|
6648
6696
|
capabilities: ["tamper"]
|
|
6649
6697
|
};
|
|
6650
6698
|
case "vibration": return {
|
|
6651
|
-
deviceType:
|
|
6652
|
-
role:
|
|
6699
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6700
|
+
role: require_ha_instance_settings.DeviceRole.VibrationSensor,
|
|
6653
6701
|
capabilities: ["vibration"]
|
|
6654
6702
|
};
|
|
6655
6703
|
case "connectivity": return {
|
|
6656
|
-
deviceType:
|
|
6657
|
-
role:
|
|
6704
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6705
|
+
role: require_ha_instance_settings.DeviceRole.ConnectivitySensor,
|
|
6658
6706
|
capabilities: ["connectivity"]
|
|
6659
6707
|
};
|
|
6660
6708
|
case "sound": return {
|
|
6661
|
-
deviceType:
|
|
6662
|
-
role:
|
|
6709
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6710
|
+
role: require_ha_instance_settings.DeviceRole.SoundSensor,
|
|
6663
6711
|
capabilities: ["binary"]
|
|
6664
6712
|
};
|
|
6665
6713
|
case "battery": return {
|
|
6666
|
-
deviceType:
|
|
6667
|
-
role:
|
|
6714
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6715
|
+
role: require_ha_instance_settings.DeviceRole.BatterySensor,
|
|
6668
6716
|
capabilities: ["binary"]
|
|
6669
6717
|
};
|
|
6670
6718
|
default: return {
|
|
6671
|
-
deviceType:
|
|
6672
|
-
role:
|
|
6719
|
+
deviceType: require_ha_instance_settings.DeviceType.Sensor,
|
|
6720
|
+
role: require_ha_instance_settings.DeviceRole.BinarySensor,
|
|
6673
6721
|
capabilities: ["binary"]
|
|
6674
6722
|
};
|
|
6675
6723
|
}
|
|
@@ -6692,40 +6740,40 @@ function readNumericAttr(attributes, key) {
|
|
|
6692
6740
|
* Sensor; an HA-tracked light with motion sensor becomes Light).
|
|
6693
6741
|
*/
|
|
6694
6742
|
var PARENT_TYPE_PRIORITY = [
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6743
|
+
require_ha_instance_settings.DeviceType.MediaPlayer,
|
|
6744
|
+
require_ha_instance_settings.DeviceType.AlarmPanel,
|
|
6745
|
+
require_ha_instance_settings.DeviceType.Thermostat,
|
|
6746
|
+
require_ha_instance_settings.DeviceType.Climate,
|
|
6747
|
+
require_ha_instance_settings.DeviceType.Humidifier,
|
|
6748
|
+
require_ha_instance_settings.DeviceType.WaterHeater,
|
|
6749
|
+
require_ha_instance_settings.DeviceType.Lock,
|
|
6750
|
+
require_ha_instance_settings.DeviceType.Cover,
|
|
6751
|
+
require_ha_instance_settings.DeviceType.Valve,
|
|
6752
|
+
require_ha_instance_settings.DeviceType.Fan,
|
|
6753
|
+
require_ha_instance_settings.DeviceType.Vacuum,
|
|
6754
|
+
require_ha_instance_settings.DeviceType.LawnMower,
|
|
6755
|
+
require_ha_instance_settings.DeviceType.Washer,
|
|
6756
|
+
require_ha_instance_settings.DeviceType.Dryer,
|
|
6757
|
+
require_ha_instance_settings.DeviceType.Dishwasher,
|
|
6758
|
+
require_ha_instance_settings.DeviceType.Light,
|
|
6759
|
+
require_ha_instance_settings.DeviceType.Switch,
|
|
6760
|
+
require_ha_instance_settings.DeviceType.Siren,
|
|
6761
|
+
require_ha_instance_settings.DeviceType.Button,
|
|
6762
|
+
require_ha_instance_settings.DeviceType.Control,
|
|
6763
|
+
require_ha_instance_settings.DeviceType.Notifier,
|
|
6764
|
+
require_ha_instance_settings.DeviceType.Script,
|
|
6765
|
+
require_ha_instance_settings.DeviceType.Automation,
|
|
6766
|
+
require_ha_instance_settings.DeviceType.Update,
|
|
6767
|
+
require_ha_instance_settings.DeviceType.Presence,
|
|
6768
|
+
require_ha_instance_settings.DeviceType.Weather,
|
|
6769
|
+
require_ha_instance_settings.DeviceType.Image,
|
|
6770
|
+
require_ha_instance_settings.DeviceType.Sensor
|
|
6723
6771
|
];
|
|
6724
6772
|
function inferParentDeviceType(mappings) {
|
|
6725
6773
|
const set = /* @__PURE__ */ new Set();
|
|
6726
6774
|
for (const m of mappings) if (m) set.add(m.deviceType);
|
|
6727
6775
|
for (const dt of PARENT_TYPE_PRIORITY) if (set.has(dt)) return dt;
|
|
6728
|
-
return
|
|
6776
|
+
return require_ha_instance_settings.DeviceType.Generic;
|
|
6729
6777
|
}
|
|
6730
6778
|
//#endregion
|
|
6731
6779
|
//#region src/ha-candidate-mapper.ts
|
|
@@ -6747,7 +6795,7 @@ function haDeviceToCandidate(dev) {
|
|
|
6747
6795
|
return {
|
|
6748
6796
|
childNativeId: e.entityId,
|
|
6749
6797
|
name: e.name,
|
|
6750
|
-
type: e.mappedTo?.deviceType ??
|
|
6798
|
+
type: e.mappedTo?.deviceType ?? require_ha_instance_settings.DeviceType.Generic,
|
|
6751
6799
|
status: entityStatus$1(e.state),
|
|
6752
6800
|
metadata: e.area !== null ? { externalLocation: e.area } : {},
|
|
6753
6801
|
alreadyAdopted: e.alreadyAdopted,
|
|
@@ -6894,7 +6942,7 @@ function buildDiscoveryCandidates(input) {
|
|
|
6894
6942
|
};
|
|
6895
6943
|
candidates.push({
|
|
6896
6944
|
stableId,
|
|
6897
|
-
type:
|
|
6945
|
+
type: require_ha_instance_settings.DeviceType.Container,
|
|
6898
6946
|
suggestedName,
|
|
6899
6947
|
prefilledConfig,
|
|
6900
6948
|
sourceInfo
|
|
@@ -6922,52 +6970,52 @@ function buildDiscoveryCandidates(input) {
|
|
|
6922
6970
|
* shown as a Devices-tab candidate.
|
|
6923
6971
|
*/
|
|
6924
6972
|
var HA_ORPHAN_ENTITIES_DEVICE_ID = "__ha_orphan_entities__";
|
|
6925
|
-
var HaEntityMappingSchema =
|
|
6973
|
+
var HaEntityMappingSchema = require_ha_instance_settings.object({
|
|
6926
6974
|
/** CamStack DeviceType for the accessory child. */
|
|
6927
|
-
deviceType:
|
|
6975
|
+
deviceType: require_ha_instance_settings._enum(require_ha_instance_settings.DeviceType),
|
|
6928
6976
|
/** Optional CamStack DeviceRole sub-role (e.g. `ContactSensor` for
|
|
6929
6977
|
* a binary_sensor with `device_class: door`). */
|
|
6930
|
-
role:
|
|
6978
|
+
role: require_ha_instance_settings._enum(require_ha_instance_settings.DeviceRole).nullable(),
|
|
6931
6979
|
/** Capability names the accessory child will register
|
|
6932
6980
|
* (`switch` / `temperature-sensor` / …). UI may render the cap
|
|
6933
6981
|
* badges to help the operator decide what to hide. */
|
|
6934
|
-
capabilities:
|
|
6982
|
+
capabilities: require_ha_instance_settings.array(require_ha_instance_settings.string()).readonly(),
|
|
6935
6983
|
/** Mapping-derived DeviceFeature flags (e.g. `lock-open`). Persisted onto
|
|
6936
6984
|
* the child's `device.features` so the UI gates feature affordances. */
|
|
6937
|
-
features:
|
|
6985
|
+
features: require_ha_instance_settings.array(require_ha_instance_settings.string()).readonly().optional(),
|
|
6938
6986
|
/** Locale-render shape for a `DateTimeSensor`-role enum sensor
|
|
6939
6987
|
* (`device_class=timestamp` → `'datetime'`, `date` → `'date'`). Threaded
|
|
6940
6988
|
* onto the child config so the enum-sensor device seeds it into the cap
|
|
6941
6989
|
* slice. Absent for every non-DateTimeSensor mapping. */
|
|
6942
|
-
dateTimeFormat:
|
|
6990
|
+
dateTimeFormat: require_ha_instance_settings.EnumSensorDateTimeFormatSchema.optional()
|
|
6943
6991
|
}).nullable();
|
|
6944
|
-
var HaDiscoveredEntitySchema =
|
|
6992
|
+
var HaDiscoveredEntitySchema = require_ha_instance_settings.object({
|
|
6945
6993
|
/** Fully-qualified HA entity id (`switch.kitchen`). */
|
|
6946
|
-
entityId:
|
|
6994
|
+
entityId: require_ha_instance_settings.string(),
|
|
6947
6995
|
/** HA domain — first segment of entity_id (`switch` / `light` /
|
|
6948
6996
|
* `sensor` / `binary_sensor` / `cover` / …). */
|
|
6949
|
-
domain:
|
|
6997
|
+
domain: require_ha_instance_settings.string(),
|
|
6950
6998
|
/** Operator-visible friendly name (HA `friendly_name` attribute or
|
|
6951
6999
|
* the entity_id when missing). */
|
|
6952
|
-
name:
|
|
7000
|
+
name: require_ha_instance_settings.string(),
|
|
6953
7001
|
/** Current state value (e.g. `'on'`, `'21.5'`, `'open'`). */
|
|
6954
|
-
state:
|
|
7002
|
+
state: require_ha_instance_settings.string(),
|
|
6955
7003
|
/** HA `device_class` attribute when present — drives the role +
|
|
6956
7004
|
* cap mapping for sensors / binary_sensors / covers. */
|
|
6957
|
-
deviceClass:
|
|
7005
|
+
deviceClass: require_ha_instance_settings.string().nullable(),
|
|
6958
7006
|
/** HA `unit_of_measurement` attribute when present. */
|
|
6959
|
-
unitOfMeasurement:
|
|
7007
|
+
unitOfMeasurement: require_ha_instance_settings.string().nullable(),
|
|
6960
7008
|
/** Area name for this entity — the entity's own HA area if set, else its
|
|
6961
7009
|
* parent device's area. Null when neither is assigned. */
|
|
6962
|
-
area:
|
|
7010
|
+
area: require_ha_instance_settings.string().nullable().default(null),
|
|
6963
7011
|
/** HA `options` attribute — present on `select` / `input_select` entities.
|
|
6964
7012
|
* Captured at discovery so the adopt handler can persist them alongside
|
|
6965
7013
|
* other entity config fields. Absent on non-select entities. */
|
|
6966
|
-
options:
|
|
7014
|
+
options: require_ha_instance_settings.array(require_ha_instance_settings.string()).nullable().default(null),
|
|
6967
7015
|
/** HA `entity_category` — `config` / `diagnostic` / null (a normal control).
|
|
6968
7016
|
* Drives the accordion section the adopted child renders in. Null/absent for
|
|
6969
7017
|
* the common control case. */
|
|
6970
|
-
entityCategory:
|
|
7018
|
+
entityCategory: require_ha_instance_settings._enum(["config", "diagnostic"]).nullable().default(null),
|
|
6971
7019
|
/** Pre-computed CamStack mapping. `null` when the entity's domain
|
|
6972
7020
|
* has no supported mapping (the operator may still adopt the
|
|
6973
7021
|
* parent — the entity will be skipped or surfaced as a fallback). */
|
|
@@ -6975,48 +7023,48 @@ var HaDiscoveredEntitySchema = require_ha_broker_source.object({
|
|
|
6975
7023
|
/** When `true`, the entity was already materialised as a CamStack
|
|
6976
7024
|
* accessory child under a prior adoption — surfaced so the UI can
|
|
6977
7025
|
* highlight existing wiring. */
|
|
6978
|
-
alreadyAdopted:
|
|
7026
|
+
alreadyAdopted: require_ha_instance_settings.boolean(),
|
|
6979
7027
|
/** When `alreadyAdopted === true`, the kernel-assigned accessory
|
|
6980
7028
|
* child id. */
|
|
6981
|
-
adoptedCamDeviceId:
|
|
7029
|
+
adoptedCamDeviceId: require_ha_instance_settings.number().int().nonnegative().nullable()
|
|
6982
7030
|
});
|
|
6983
|
-
|
|
7031
|
+
require_ha_instance_settings.object({
|
|
6984
7032
|
/** Stable HA `device_registry` id — survives renames + restarts. */
|
|
6985
|
-
haDeviceId:
|
|
7033
|
+
haDeviceId: require_ha_instance_settings.string(),
|
|
6986
7034
|
/** Operator-visible name (HA device `name` or first entity's
|
|
6987
7035
|
* friendly_name when device has none). */
|
|
6988
|
-
name:
|
|
7036
|
+
name: require_ha_instance_settings.string(),
|
|
6989
7037
|
/** Area name from HA `area_registry` join. Null when unassigned. */
|
|
6990
|
-
area:
|
|
6991
|
-
manufacturer:
|
|
6992
|
-
model:
|
|
6993
|
-
swVersion:
|
|
7038
|
+
area: require_ha_instance_settings.string().nullable(),
|
|
7039
|
+
manufacturer: require_ha_instance_settings.string().nullable(),
|
|
7040
|
+
model: require_ha_instance_settings.string().nullable(),
|
|
7041
|
+
swVersion: require_ha_instance_settings.string().nullable(),
|
|
6994
7042
|
/** Dominant integration / platform domain for this device, derived from its
|
|
6995
7043
|
* entities' HA `entity_registry.platform` (e.g. `dreame_vacuum`). Generic /
|
|
6996
7044
|
* helper platforms (`unifi` / `hacs` / `hassio`) are excluded when a real
|
|
6997
7045
|
* platform is available. Null when the device has no entities. */
|
|
6998
|
-
integration:
|
|
7046
|
+
integration: require_ha_instance_settings.string().nullable().default(null),
|
|
6999
7047
|
/** Convenience field — count of `entities[]`. */
|
|
7000
|
-
entityCount:
|
|
7048
|
+
entityCount: require_ha_instance_settings.number().int().nonnegative(),
|
|
7001
7049
|
/** Every entity HA's `entity_registry` links to this device. The
|
|
7002
7050
|
* list survives row collapse — full devices are returned so
|
|
7003
7051
|
* accordion expansion is a no-roundtrip render. */
|
|
7004
|
-
entities:
|
|
7052
|
+
entities: require_ha_instance_settings.array(HaDiscoveredEntitySchema).readonly(),
|
|
7005
7053
|
/** Whether this HA device has an active CamStack adoption. */
|
|
7006
|
-
alreadyAdopted:
|
|
7054
|
+
alreadyAdopted: require_ha_instance_settings.boolean(),
|
|
7007
7055
|
/** When `alreadyAdopted === true`, the kernel-assigned parent id. */
|
|
7008
|
-
adoptedCamDeviceId:
|
|
7056
|
+
adoptedCamDeviceId: require_ha_instance_settings.number().int().nonnegative().nullable()
|
|
7009
7057
|
});
|
|
7010
|
-
|
|
7058
|
+
require_ha_instance_settings.object({
|
|
7011
7059
|
/** Last refresh timestamp (ms epoch) — null when never refreshed. */
|
|
7012
|
-
lastDiscoveryAt:
|
|
7060
|
+
lastDiscoveryAt: require_ha_instance_settings.number().int().nonnegative().nullable(),
|
|
7013
7061
|
/** Count of HA devices in the discovery cache (across all brokers). */
|
|
7014
|
-
deviceCount:
|
|
7062
|
+
deviceCount: require_ha_instance_settings.number().int().nonnegative(),
|
|
7015
7063
|
/** Count of HA devices currently materialised as CamStack parent
|
|
7016
7064
|
* devices. */
|
|
7017
|
-
adoptedCount:
|
|
7065
|
+
adoptedCount: require_ha_instance_settings.number().int().nonnegative(),
|
|
7018
7066
|
/** Last error message from a refresh attempt. */
|
|
7019
|
-
lastError:
|
|
7067
|
+
lastError: require_ha_instance_settings.string().nullable()
|
|
7020
7068
|
});
|
|
7021
7069
|
//#endregion
|
|
7022
7070
|
//#region src/ha-integration-domain.ts
|
|
@@ -7155,7 +7203,7 @@ function negotiateEncoding(acceptEncoding) {
|
|
|
7155
7203
|
}
|
|
7156
7204
|
/** Build the `addon-routes` provider that serves the bundled Home Assistant icon. */
|
|
7157
7205
|
function createHaIconRouteProvider() {
|
|
7158
|
-
return
|
|
7206
|
+
return require_ha_instance_settings.buildAddonRouteProvider(HA_ROUTE_ID, [{
|
|
7159
7207
|
method: "GET",
|
|
7160
7208
|
path: `/icons/${HA_ICON_KIND}`,
|
|
7161
7209
|
access: "public",
|
|
@@ -7172,16 +7220,24 @@ function createHaIconRouteProvider() {
|
|
|
7172
7220
|
//#region src/broker-record-kinds.ts
|
|
7173
7221
|
/**
|
|
7174
7222
|
* Broker entry kind (B2): credentials live in records; integrations keep brokerId.
|
|
7223
|
+
*
|
|
7224
|
+
* The body is FLAT (planning#408 P3): the form the instances page edits is the stored shape,
|
|
7225
|
+
* with the two tokens at the top level where the records door keeps them in the keychain.
|
|
7175
7226
|
*/
|
|
7176
7227
|
var HA_BROKERS_KIND = "brokers";
|
|
7177
|
-
var HaBrokerBodySchema =
|
|
7178
|
-
|
|
7179
|
-
|
|
7228
|
+
var HaBrokerBodySchema = require_ha_instance_settings.entryBody({
|
|
7229
|
+
authKind: HaBrokerEntrySchema.shape.authKind,
|
|
7230
|
+
baseUrl: HaBrokerEntrySchema.shape.baseUrl,
|
|
7231
|
+
accessToken: HaBrokerEntrySchema.shape.accessToken,
|
|
7232
|
+
supervisorToken: HaBrokerEntrySchema.shape.supervisorToken,
|
|
7233
|
+
integrationId: HaBrokerEntrySchema.shape.integrationId,
|
|
7234
|
+
enabled: HaBrokerEntrySchema.shape.enabled
|
|
7180
7235
|
});
|
|
7181
|
-
var haBrokersEntryKind =
|
|
7236
|
+
var haBrokersEntryKind = require_ha_instance_settings.defineEntryKind(HA_BROKERS_KIND, {
|
|
7182
7237
|
title: "Home Assistant brokers",
|
|
7183
7238
|
schema: HaBrokerBodySchema,
|
|
7184
|
-
writeTrigger: "an operator adds, edits or removes a Home Assistant broker connection"
|
|
7239
|
+
writeTrigger: "an operator adds, edits or removes a Home Assistant broker connection",
|
|
7240
|
+
retiredSecretPaths: ["auth.accessToken", "auth.supervisorToken"]
|
|
7185
7241
|
});
|
|
7186
7242
|
//#endregion
|
|
7187
7243
|
//#region src/record-kinds.ts
|
|
@@ -7192,8 +7248,8 @@ var haBrokersEntryKind = require_ha_broker_source.defineEntryKind(HA_BROKERS_KIN
|
|
|
7192
7248
|
* credential — delivery reuses the broker WebSocket connection (the token
|
|
7193
7249
|
* lives in the broker config, not in the target).
|
|
7194
7250
|
*/
|
|
7195
|
-
var HaTargetBodySchema =
|
|
7196
|
-
var haRecordKinds =
|
|
7251
|
+
var HaTargetBodySchema = require_ha_instance_settings.TargetSchema.omit({ id: true });
|
|
7252
|
+
var haRecordKinds = require_ha_instance_settings.defineRecordKinds({
|
|
7197
7253
|
/**
|
|
7198
7254
|
* One row per configured Home Assistant push target. Operator-managed via the
|
|
7199
7255
|
* notification-output cap CRUD; no TTL.
|
|
@@ -7259,7 +7315,7 @@ function haConfigSchema() {
|
|
|
7259
7315
|
labelKey: "provider-homeassistant:haConfig.s_connection.brokerId.label",
|
|
7260
7316
|
description: "The Home Assistant connection the notification goes through.",
|
|
7261
7317
|
descriptionKey: "provider-homeassistant:haConfig.s_connection.brokerId.description",
|
|
7262
|
-
brokerKind:
|
|
7318
|
+
brokerKind: require_ha_instance_settings.HA_BROKER_KIND
|
|
7263
7319
|
}, {
|
|
7264
7320
|
type: "select",
|
|
7265
7321
|
key: "service",
|
|
@@ -7499,7 +7555,7 @@ var HaTargetStore = class HaTargetStore {
|
|
|
7499
7555
|
const page = await this.kind.query({ limit: 1e3 });
|
|
7500
7556
|
const out = [];
|
|
7501
7557
|
for (const record of page.records) {
|
|
7502
|
-
const parsed =
|
|
7558
|
+
const parsed = require_ha_instance_settings.TargetSchema.safeParse({
|
|
7503
7559
|
id: record.id,
|
|
7504
7560
|
...record.data
|
|
7505
7561
|
});
|
|
@@ -7510,7 +7566,7 @@ var HaTargetStore = class HaTargetStore {
|
|
|
7510
7566
|
async getById(id) {
|
|
7511
7567
|
const row = await this.kind.get(id);
|
|
7512
7568
|
if (row === null) return null;
|
|
7513
|
-
const parsed =
|
|
7569
|
+
const parsed = require_ha_instance_settings.TargetSchema.safeParse({
|
|
7514
7570
|
id: row.id,
|
|
7515
7571
|
...row.data
|
|
7516
7572
|
});
|
|
@@ -7571,12 +7627,12 @@ function createHaNotificationOutputProvider(deps) {
|
|
|
7571
7627
|
}
|
|
7572
7628
|
/** Degrade → build → publish. NEVER throws through the cap. */
|
|
7573
7629
|
async function dispatch(target, raw) {
|
|
7574
|
-
const notification = deps.readPublicEndpoint === void 0 ? raw : await
|
|
7630
|
+
const notification = deps.readPublicEndpoint === void 0 ? raw : await require_ha_instance_settings.resolveAttachmentUrls({
|
|
7575
7631
|
notification: raw,
|
|
7576
7632
|
readPublicEndpoint: deps.readPublicEndpoint,
|
|
7577
7633
|
...logger !== void 0 ? { logger } : {}
|
|
7578
7634
|
});
|
|
7579
|
-
const prepared =
|
|
7635
|
+
const prepared = require_ha_instance_settings.prepareNotification(descriptor.caps, notification);
|
|
7580
7636
|
const service = readService(target.config);
|
|
7581
7637
|
if (!service) return {
|
|
7582
7638
|
success: false,
|
|
@@ -7600,11 +7656,11 @@ function createHaNotificationOutputProvider(deps) {
|
|
|
7600
7656
|
logger?.warn("ha notify publish failed", { meta: {
|
|
7601
7657
|
brokerId,
|
|
7602
7658
|
service,
|
|
7603
|
-
error:
|
|
7659
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
7604
7660
|
} });
|
|
7605
7661
|
return {
|
|
7606
7662
|
success: false,
|
|
7607
|
-
error:
|
|
7663
|
+
error: require_ha_instance_settings.errMsg(err),
|
|
7608
7664
|
renderedAs: prepared.renderedAs
|
|
7609
7665
|
};
|
|
7610
7666
|
}
|
|
@@ -7643,7 +7699,7 @@ function createHaNotificationOutputProvider(deps) {
|
|
|
7643
7699
|
} catch (err) {
|
|
7644
7700
|
logger?.warn("ha device registry read failed; naming services from their ids", { meta: {
|
|
7645
7701
|
brokerId,
|
|
7646
|
-
error:
|
|
7702
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
7647
7703
|
} });
|
|
7648
7704
|
return /* @__PURE__ */ new Map();
|
|
7649
7705
|
}
|
|
@@ -7657,7 +7713,7 @@ function createHaNotificationOutputProvider(deps) {
|
|
|
7657
7713
|
} catch (err) {
|
|
7658
7714
|
logger?.warn("ha get_services discovery failed", { meta: {
|
|
7659
7715
|
brokerId,
|
|
7660
|
-
error:
|
|
7716
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
7661
7717
|
} });
|
|
7662
7718
|
}
|
|
7663
7719
|
return out;
|
|
@@ -7713,8 +7769,12 @@ function rowToEntry(id, data) {
|
|
|
7713
7769
|
const parsed = HaBrokerEntrySchema.safeParse({
|
|
7714
7770
|
id,
|
|
7715
7771
|
name: data.name,
|
|
7716
|
-
|
|
7717
|
-
|
|
7772
|
+
authKind: data.authKind,
|
|
7773
|
+
baseUrl: data.baseUrl,
|
|
7774
|
+
accessToken: data.accessToken,
|
|
7775
|
+
supervisorToken: data.supervisorToken,
|
|
7776
|
+
integrationId: data.integrationId,
|
|
7777
|
+
enabled: data.enabled
|
|
7718
7778
|
});
|
|
7719
7779
|
return parsed.success ? parsed.data : null;
|
|
7720
7780
|
}
|
|
@@ -7735,7 +7795,7 @@ var HaBrokerStore = class HaBrokerStore {
|
|
|
7735
7795
|
const resolved = await this.kind.getResolved(record.id);
|
|
7736
7796
|
if (resolved !== null) data = resolved.data;
|
|
7737
7797
|
} catch (err) {
|
|
7738
|
-
if (!(err instanceof
|
|
7798
|
+
if (!(err instanceof require_ha_instance_settings.RecordSecretUnavailableError)) throw err;
|
|
7739
7799
|
}
|
|
7740
7800
|
const entry = rowToEntry(record.id, data);
|
|
7741
7801
|
if (entry !== null) out.push(entry);
|
|
@@ -7748,15 +7808,14 @@ var HaBrokerStore = class HaBrokerStore {
|
|
|
7748
7808
|
if (row === null) return null;
|
|
7749
7809
|
return rowToEntry(row.id, row.data);
|
|
7750
7810
|
} catch (err) {
|
|
7751
|
-
if (err instanceof
|
|
7811
|
+
if (err instanceof require_ha_instance_settings.RecordSecretUnavailableError) return null;
|
|
7752
7812
|
throw err;
|
|
7753
7813
|
}
|
|
7754
7814
|
}
|
|
7755
7815
|
async upsert(entry) {
|
|
7756
|
-
const { id,
|
|
7816
|
+
const { id, integrationId, ...fields } = entry;
|
|
7757
7817
|
const body = {
|
|
7758
|
-
|
|
7759
|
-
auth,
|
|
7818
|
+
...Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== void 0)),
|
|
7760
7819
|
...integrationId === void 0 ? {} : { integrationId }
|
|
7761
7820
|
};
|
|
7762
7821
|
const parsed = haBrokersEntryKind.schema.safeParse(body);
|
|
@@ -7777,6 +7836,97 @@ var HaBrokerStore = class HaBrokerStore {
|
|
|
7777
7836
|
}
|
|
7778
7837
|
};
|
|
7779
7838
|
//#endregion
|
|
7839
|
+
//#region src/ha-instances.ts
|
|
7840
|
+
/**
|
|
7841
|
+
* The Home Assistant connections as `integration-instances` (planning#408 P3): one instance per
|
|
7842
|
+
* `brokers` record, over the SAME records and ids (`ha_NNN`) the `broker` cap serves. The body is
|
|
7843
|
+
* flat, so the form the instances page edits is the stored shape.
|
|
7844
|
+
*/
|
|
7845
|
+
var HA_INSTANCE_KIND = "home-assistant";
|
|
7846
|
+
function isHaBodyConfigured(body) {
|
|
7847
|
+
return body.authKind === "supervisor" ? (body.supervisorToken ?? "").length > 0 : (body.baseUrl ?? "").length > 0 && (body.accessToken ?? "").length > 0;
|
|
7848
|
+
}
|
|
7849
|
+
function statusOf(info) {
|
|
7850
|
+
if (info === null) return {
|
|
7851
|
+
state: "unknown",
|
|
7852
|
+
label: "Not connected"
|
|
7853
|
+
};
|
|
7854
|
+
if (info.status === "connected") return {
|
|
7855
|
+
state: "ok",
|
|
7856
|
+
label: "Connected"
|
|
7857
|
+
};
|
|
7858
|
+
if (info.status === "connecting") return {
|
|
7859
|
+
state: "unknown",
|
|
7860
|
+
label: "Connecting"
|
|
7861
|
+
};
|
|
7862
|
+
return {
|
|
7863
|
+
state: "error",
|
|
7864
|
+
label: info.status,
|
|
7865
|
+
...info.error !== null ? { detail: info.error } : {}
|
|
7866
|
+
};
|
|
7867
|
+
}
|
|
7868
|
+
function buildHaInstances(records, deps) {
|
|
7869
|
+
return new require_ha_instance_settings.RecordIntegrationInstances(records, haBrokersEntryKind, {
|
|
7870
|
+
description: {
|
|
7871
|
+
instanceMode: "multiple",
|
|
7872
|
+
kind: "connection",
|
|
7873
|
+
canTest: true,
|
|
7874
|
+
kinds: [{
|
|
7875
|
+
id: HA_INSTANCE_KIND,
|
|
7876
|
+
label: "Home Assistant"
|
|
7877
|
+
}],
|
|
7878
|
+
allowRestoreId: true
|
|
7879
|
+
},
|
|
7880
|
+
formSchema: () => haBrokerSettingsSchema,
|
|
7881
|
+
initialBody: (name) => ({
|
|
7882
|
+
name,
|
|
7883
|
+
authKind: "long-lived"
|
|
7884
|
+
}),
|
|
7885
|
+
mintId: () => deps.mintId(),
|
|
7886
|
+
kindOf: () => HA_INSTANCE_KIND,
|
|
7887
|
+
health: (_body, id) => {
|
|
7888
|
+
const info = deps.infoOf(id);
|
|
7889
|
+
return {
|
|
7890
|
+
status: statusOf(info),
|
|
7891
|
+
...info?.lastCheckedAt != null ? { lastCheckedAt: info.lastCheckedAt } : {}
|
|
7892
|
+
};
|
|
7893
|
+
},
|
|
7894
|
+
configured: isHaBodyConfigured,
|
|
7895
|
+
onChanged: deps.onChanged,
|
|
7896
|
+
test: async ({ id, values }) => {
|
|
7897
|
+
if (["accessToken", "supervisorToken"].some((key) => {
|
|
7898
|
+
const v = values[key];
|
|
7899
|
+
return typeof v === "string" && v.length > 0 && v !== "__camstack_redacted__";
|
|
7900
|
+
})) {
|
|
7901
|
+
const r = await deps.probe(values);
|
|
7902
|
+
return r.ok ? {
|
|
7903
|
+
success: true,
|
|
7904
|
+
status: "validated",
|
|
7905
|
+
...r.latencyMs !== void 0 ? { latencyMs: r.latencyMs } : {}
|
|
7906
|
+
} : {
|
|
7907
|
+
success: false,
|
|
7908
|
+
status: "rejected",
|
|
7909
|
+
error: r.error ?? "Home Assistant rejected the connection"
|
|
7910
|
+
};
|
|
7911
|
+
}
|
|
7912
|
+
const info = id === null ? null : deps.infoOf(id);
|
|
7913
|
+
if (info === null) return {
|
|
7914
|
+
success: false,
|
|
7915
|
+
status: "inconclusive",
|
|
7916
|
+
error: "Not connected: save the connection first"
|
|
7917
|
+
};
|
|
7918
|
+
return info.status === "connected" ? {
|
|
7919
|
+
success: true,
|
|
7920
|
+
status: "validated"
|
|
7921
|
+
} : {
|
|
7922
|
+
success: false,
|
|
7923
|
+
status: "rejected",
|
|
7924
|
+
error: info.error ?? `status: ${info.status}`
|
|
7925
|
+
};
|
|
7926
|
+
}
|
|
7927
|
+
});
|
|
7928
|
+
}
|
|
7929
|
+
//#endregion
|
|
7780
7930
|
//#region src/ha-board-key-source.ts
|
|
7781
7931
|
/**
|
|
7782
7932
|
* `board-key-source` for Home Assistant: reads an ESPHome board's API
|
|
@@ -7799,19 +7949,19 @@ var HaBrokerStore = class HaBrokerStore {
|
|
|
7799
7949
|
*/
|
|
7800
7950
|
var INGRESS_PREFIX = "/api/hassio_ingress/";
|
|
7801
7951
|
var ESPHOME_SERVICE = "esphome";
|
|
7802
|
-
var DiscoverySchema =
|
|
7803
|
-
addon:
|
|
7804
|
-
service:
|
|
7952
|
+
var DiscoverySchema = require_ha_instance_settings.object({ discovery: require_ha_instance_settings.array(require_ha_instance_settings.object({
|
|
7953
|
+
addon: require_ha_instance_settings.string(),
|
|
7954
|
+
service: require_ha_instance_settings.string()
|
|
7805
7955
|
})) });
|
|
7806
|
-
var AddonInfoSchema =
|
|
7807
|
-
var SessionSchema =
|
|
7808
|
-
var DevicesSchema =
|
|
7809
|
-
name:
|
|
7810
|
-
configuration:
|
|
7811
|
-
address:
|
|
7956
|
+
var AddonInfoSchema = require_ha_instance_settings.object({ ingress_url: require_ha_instance_settings.string().nullable().optional() });
|
|
7957
|
+
var SessionSchema = require_ha_instance_settings.object({ session: require_ha_instance_settings.string().min(1) });
|
|
7958
|
+
var DevicesSchema = require_ha_instance_settings.object({ configured: require_ha_instance_settings.array(require_ha_instance_settings.object({
|
|
7959
|
+
name: require_ha_instance_settings.string(),
|
|
7960
|
+
configuration: require_ha_instance_settings.string(),
|
|
7961
|
+
address: require_ha_instance_settings.string().nullable().optional()
|
|
7812
7962
|
})) });
|
|
7813
7963
|
/** The ONLY part of `json-config` that is read; every other field is dropped by the parse. */
|
|
7814
|
-
var KeyConfigSchema =
|
|
7964
|
+
var KeyConfigSchema = require_ha_instance_settings.object({ api: require_ha_instance_settings.object({ encryption: require_ha_instance_settings.object({ key: require_ha_instance_settings.string().min(1) }).optional() }).optional() });
|
|
7815
7965
|
var ADMIN_CODES = new Set([
|
|
7816
7966
|
"unauthorized",
|
|
7817
7967
|
"forbidden",
|
|
@@ -7877,7 +8027,7 @@ var HaBoardKeySource = class {
|
|
|
7877
8027
|
}
|
|
7878
8028
|
}
|
|
7879
8029
|
fail(sourceId, op, err) {
|
|
7880
|
-
const failure = err instanceof SourceFailure ? err : new SourceFailure("unreachable",
|
|
8030
|
+
const failure = err instanceof SourceFailure ? err : new SourceFailure("unreachable", require_ha_instance_settings.errMsg(err));
|
|
7881
8031
|
this.deps.logger.warn(`board-key-source ${op} refused`, { meta: {
|
|
7882
8032
|
sourceId,
|
|
7883
8033
|
reason: failure.reason
|
|
@@ -7901,7 +8051,7 @@ var HaBoardKeySource = class {
|
|
|
7901
8051
|
} catch (err) {
|
|
7902
8052
|
const code = errorCode(err);
|
|
7903
8053
|
if (code !== null && ADMIN_CODES.has(code)) throw new SourceFailure("not-admin", "the Home Assistant token is not an administrator");
|
|
7904
|
-
throw new SourceFailure("unreachable",
|
|
8054
|
+
throw new SourceFailure("unreachable", require_ha_instance_settings.errMsg(err));
|
|
7905
8055
|
}
|
|
7906
8056
|
}
|
|
7907
8057
|
async openIngress(sourceId) {
|
|
@@ -7929,7 +8079,7 @@ var HaBoardKeySource = class {
|
|
|
7929
8079
|
Accept: "application/json"
|
|
7930
8080
|
} });
|
|
7931
8081
|
} catch (err) {
|
|
7932
|
-
throw new SourceFailure("unreachable",
|
|
8082
|
+
throw new SourceFailure("unreachable", require_ha_instance_settings.errMsg(err));
|
|
7933
8083
|
}
|
|
7934
8084
|
if (response.status === 404 && configuration !== void 0) throw new SourceFailure("unknown-board", configuration);
|
|
7935
8085
|
if (response.status === 401 || response.status === 403) throw new SourceFailure("not-admin", `the builder answered ${response.status}`);
|
|
@@ -7950,6 +8100,178 @@ function createHaBoardKeyProvider(source) {
|
|
|
7950
8100
|
};
|
|
7951
8101
|
}
|
|
7952
8102
|
//#endregion
|
|
8103
|
+
//#region src/migrations/flatten-broker-records.ts
|
|
8104
|
+
/**
|
|
8105
|
+
* The Home Assistant broker records keep their connection flat (planning#408 P3): the nested
|
|
8106
|
+
* `auth` object (`{ kind, baseUrl, accessToken }` or `{ kind, supervisorToken }`) becomes the top
|
|
8107
|
+
* level `authKind`, `baseUrl`, `accessToken` and `supervisorToken`, under the SAME record id, so
|
|
8108
|
+
* the devices that carry `ha_NNN` keep their broker. The work is `flattenNestedRecords`: every
|
|
8109
|
+
* secret is read before anything is written, read back from its new keychain ref before the
|
|
8110
|
+
* nested object goes, and the old refs are deleted last.
|
|
8111
|
+
*/
|
|
8112
|
+
var FLATTEN_BROKER_RECORDS_ID = "provider-homeassistant/2026-11-04-flatten-broker-records";
|
|
8113
|
+
function text(value) {
|
|
8114
|
+
return typeof value === "string" ? value : void 0;
|
|
8115
|
+
}
|
|
8116
|
+
async function flattenBrokerRecords(ports) {
|
|
8117
|
+
return require_ha_instance_settings.flattenNestedRecords(ports, {
|
|
8118
|
+
legacyKey: "auth",
|
|
8119
|
+
flatSecretKeys: ["accessToken", "supervisorToken"],
|
|
8120
|
+
toFlat: (legacy) => {
|
|
8121
|
+
const kind = legacy["kind"] === "supervisor" ? "supervisor" : "long-lived";
|
|
8122
|
+
const baseUrl = text(legacy["baseUrl"]);
|
|
8123
|
+
const accessToken = text(legacy["accessToken"]);
|
|
8124
|
+
const supervisorToken = text(legacy["supervisorToken"]);
|
|
8125
|
+
return {
|
|
8126
|
+
authKind: kind,
|
|
8127
|
+
...baseUrl !== void 0 ? { baseUrl } : {},
|
|
8128
|
+
...accessToken !== void 0 ? { accessToken } : {},
|
|
8129
|
+
...supervisorToken !== void 0 ? { supervisorToken } : {}
|
|
8130
|
+
};
|
|
8131
|
+
}
|
|
8132
|
+
});
|
|
8133
|
+
}
|
|
8134
|
+
//#endregion
|
|
8135
|
+
//#region src/migrations/calendar-ids-to-integration.ts
|
|
8136
|
+
/**
|
|
8137
|
+
* The HA calendars an instance imports are a fact of THAT instance (D985): each Home Assistant
|
|
8138
|
+
* integration carries its own `calendarIds` in its settings, and the addon-level `calendarIds`
|
|
8139
|
+
* setting (one set of `calendar.*` entity ids for every broker) goes away.
|
|
8140
|
+
*
|
|
8141
|
+
* Two migrations, in this order:
|
|
8142
|
+
*
|
|
8143
|
+
* 1. `calendar-ids-to-integration` (procedure) copies the shared set to EVERY integration that
|
|
8144
|
+
* has no `calendarIds` yet. An integration that already has one is never overwritten; one
|
|
8145
|
+
* deleted meanwhile is skipped, never recreated; a stored value that is not a list of strings
|
|
8146
|
+
* is an ambiguity and throws (nothing is guessed).
|
|
8147
|
+
* 2. `drop-shared-calendar-ids` (plan) deletes the shared setting after a read-back: every
|
|
8148
|
+
* integration must show its own set. When one holds a set that differs from the shared one,
|
|
8149
|
+
* the shared value is the loser and stays PARKED in place, never deleted.
|
|
8150
|
+
*
|
|
8151
|
+
* Both are idempotent.
|
|
8152
|
+
*/
|
|
8153
|
+
var HA_PROVIDER_ID = "provider-homeassistant";
|
|
8154
|
+
/** The canonical settings table: one row per key, id `<addonId>.<key>`. */
|
|
8155
|
+
var ADDON_SETTINGS_TABLE = "addon-settings";
|
|
8156
|
+
var SHARED_CALENDAR_IDS_KEY = "calendarIds";
|
|
8157
|
+
async function readSharedSet(ports) {
|
|
8158
|
+
const answer = await ports.readStored(SHARED_CALENDAR_IDS_KEY);
|
|
8159
|
+
if (answer.kind === "unreadable") throw new Error(`provider-homeassistant: the shared calendarIds cannot be read (${answer.reason})`);
|
|
8160
|
+
if (answer.kind === "absent") return null;
|
|
8161
|
+
const value = answer.value;
|
|
8162
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) throw new Error("provider-homeassistant: the shared calendarIds is not a list of entity ids — nothing copied");
|
|
8163
|
+
return value;
|
|
8164
|
+
}
|
|
8165
|
+
async function copyCalendarIdsToIntegrations(ports) {
|
|
8166
|
+
const shared = await readSharedSet(ports);
|
|
8167
|
+
if (shared === null) return {
|
|
8168
|
+
outcome: "already-done",
|
|
8169
|
+
summary: { reason: "no-shared-set" }
|
|
8170
|
+
};
|
|
8171
|
+
let copied = 0;
|
|
8172
|
+
let kept = 0;
|
|
8173
|
+
let gone = 0;
|
|
8174
|
+
for (const instance of await ports.listInstances()) {
|
|
8175
|
+
if ("calendarIds" in instance.settings) {
|
|
8176
|
+
kept += 1;
|
|
8177
|
+
continue;
|
|
8178
|
+
}
|
|
8179
|
+
if (await ports.patchInstanceSettings(instance.integrationId, { calendarIds: [...shared] })) copied += 1;
|
|
8180
|
+
else gone += 1;
|
|
8181
|
+
}
|
|
8182
|
+
return {
|
|
8183
|
+
outcome: copied === 0 ? "already-done" : "done",
|
|
8184
|
+
summary: {
|
|
8185
|
+
copied,
|
|
8186
|
+
kept,
|
|
8187
|
+
gone
|
|
8188
|
+
}
|
|
8189
|
+
};
|
|
8190
|
+
}
|
|
8191
|
+
var asIds = (value) => Array.isArray(value) && value.every((item) => typeof item === "string") ? value : null;
|
|
8192
|
+
var sameSet = (a, b) => {
|
|
8193
|
+
const left = new Set(a);
|
|
8194
|
+
const right = new Set(b);
|
|
8195
|
+
return left.size === right.size && [...left].every((id) => right.has(id));
|
|
8196
|
+
};
|
|
8197
|
+
async function planDropSharedCalendarIds(ports) {
|
|
8198
|
+
const shared = await readSharedSet(ports);
|
|
8199
|
+
if (shared === null) return { edits: [] };
|
|
8200
|
+
const instances = await ports.listInstances();
|
|
8201
|
+
if (instances.length === 0) return { edits: [] };
|
|
8202
|
+
const own = instances.map((instance) => ({
|
|
8203
|
+
integrationId: instance.integrationId,
|
|
8204
|
+
ids: asIds(instance.settings[SHARED_CALENDAR_IDS_KEY])
|
|
8205
|
+
}));
|
|
8206
|
+
const missing = own.filter((entry) => entry.ids === null);
|
|
8207
|
+
if (missing.length > 0) throw new Error(`provider-homeassistant: the shared calendarIds is not dropped, ${missing.map((m) => m.integrationId).join(", ")} has no set of its own`);
|
|
8208
|
+
if (!own.every((entry) => sameSet(entry.ids ?? [], shared))) return { edits: [] };
|
|
8209
|
+
const rowId = `${HA_PROVIDER_ID}.${SHARED_CALENDAR_IDS_KEY}`;
|
|
8210
|
+
return { edits: [{
|
|
8211
|
+
op: "deleteKeys",
|
|
8212
|
+
scope: ADDON_SETTINGS_TABLE,
|
|
8213
|
+
keys: [rowId],
|
|
8214
|
+
precondition: require_ha_instance_settings.preconditionOf({ [rowId]: {
|
|
8215
|
+
addonId: HA_PROVIDER_ID,
|
|
8216
|
+
key: SHARED_CALENDAR_IDS_KEY,
|
|
8217
|
+
value: shared
|
|
8218
|
+
} })
|
|
8219
|
+
}] };
|
|
8220
|
+
}
|
|
8221
|
+
//#endregion
|
|
8222
|
+
//#region src/migrations/index.ts
|
|
8223
|
+
/** The `provider-homeassistant` catalogue (D987): offered to the engine when the addon initialises. */
|
|
8224
|
+
var ADDON_MIGRATIONS = [
|
|
8225
|
+
{
|
|
8226
|
+
meta: {
|
|
8227
|
+
id: "provider-homeassistant/2026-11-03-calendar-ids-to-integration",
|
|
8228
|
+
owner: "provider-homeassistant",
|
|
8229
|
+
level: "addon-state",
|
|
8230
|
+
seq: 1,
|
|
8231
|
+
dependsOn: [],
|
|
8232
|
+
store: "settings",
|
|
8233
|
+
scope: "cluster",
|
|
8234
|
+
onFailure: "tolerate",
|
|
8235
|
+
bodyKind: "procedure",
|
|
8236
|
+
description: "Copy the shared calendar set to the settings of every Home Assistant integration that has none",
|
|
8237
|
+
fixtureMark: "the broker link stored beside the calendars of an integration"
|
|
8238
|
+
},
|
|
8239
|
+
procedure: copyCalendarIdsToIntegrations
|
|
8240
|
+
},
|
|
8241
|
+
{
|
|
8242
|
+
meta: {
|
|
8243
|
+
id: "provider-homeassistant/2026-11-03-drop-shared-calendar-ids",
|
|
8244
|
+
owner: "provider-homeassistant",
|
|
8245
|
+
level: "addon-state",
|
|
8246
|
+
seq: 2,
|
|
8247
|
+
dependsOn: ["provider-homeassistant/2026-11-03-calendar-ids-to-integration"],
|
|
8248
|
+
store: "settings",
|
|
8249
|
+
scope: "cluster",
|
|
8250
|
+
onFailure: "tolerate",
|
|
8251
|
+
bodyKind: "plan",
|
|
8252
|
+
description: "Delete the shared calendar set once every integration shows its own",
|
|
8253
|
+
fixtureMark: "the shared set parked when an integration holds a different one"
|
|
8254
|
+
},
|
|
8255
|
+
plan: planDropSharedCalendarIds
|
|
8256
|
+
},
|
|
8257
|
+
{
|
|
8258
|
+
meta: {
|
|
8259
|
+
id: FLATTEN_BROKER_RECORDS_ID,
|
|
8260
|
+
owner: "provider-homeassistant",
|
|
8261
|
+
level: "addon-state",
|
|
8262
|
+
seq: 3,
|
|
8263
|
+
dependsOn: [],
|
|
8264
|
+
store: "settings",
|
|
8265
|
+
scope: "cluster",
|
|
8266
|
+
onFailure: "block-owner",
|
|
8267
|
+
bodyKind: "procedure",
|
|
8268
|
+
description: "Flatten the nested auth of every Home Assistant broker record under its own id, moving each token to a top-level keychain secret",
|
|
8269
|
+
fixtureMark: "a non-secret field stored beside the auth of a broker record"
|
|
8270
|
+
},
|
|
8271
|
+
procedure: flattenBrokerRecords
|
|
8272
|
+
}
|
|
8273
|
+
];
|
|
8274
|
+
//#endregion
|
|
7953
8275
|
//#region src/ha-calendar-source.ts
|
|
7954
8276
|
function isCalendarEntity(entityId) {
|
|
7955
8277
|
return entityId.startsWith("calendar.");
|
|
@@ -8060,9 +8382,9 @@ function isSelected(entityId, selected) {
|
|
|
8060
8382
|
function createHaCalendarSource(deps) {
|
|
8061
8383
|
return {
|
|
8062
8384
|
listCalendars: async () => {
|
|
8063
|
-
const selected = deps.selectedCalendarIds();
|
|
8064
8385
|
const out = [];
|
|
8065
|
-
for (const { manager } of deps.brokers.listManagers()) {
|
|
8386
|
+
for (const { brokerId, manager } of deps.brokers.listManagers()) {
|
|
8387
|
+
const selected = deps.selectedCalendarIds(brokerId);
|
|
8066
8388
|
const states = manager.snapshotAllStates();
|
|
8067
8389
|
for (const [entityId, raw] of Object.entries(states)) {
|
|
8068
8390
|
if (!isCalendarEntity(entityId)) continue;
|
|
@@ -8082,10 +8404,10 @@ function createHaCalendarSource(deps) {
|
|
|
8082
8404
|
listEvents: async (input) => {
|
|
8083
8405
|
const startIso = new Date(input.startMs).toISOString();
|
|
8084
8406
|
const endIso = new Date(input.endMs).toISOString();
|
|
8085
|
-
const selected = deps.selectedCalendarIds();
|
|
8086
8407
|
const wanted = input.calendarIds !== void 0 && input.calendarIds.length > 0 ? new Set(input.calendarIds) : null;
|
|
8087
8408
|
const out = [];
|
|
8088
8409
|
for (const { brokerId, manager } of deps.brokers.listManagers()) {
|
|
8410
|
+
const selected = deps.selectedCalendarIds(brokerId);
|
|
8089
8411
|
const states = manager.snapshotAllStates();
|
|
8090
8412
|
const entityIds = Object.keys(states).filter((id) => {
|
|
8091
8413
|
if (!isCalendarEntity(id)) return false;
|
|
@@ -8109,10 +8431,11 @@ function createHaCalendarSource(deps) {
|
|
|
8109
8431
|
};
|
|
8110
8432
|
}
|
|
8111
8433
|
/** Discover every HA calendar entity (ignores the import filter) — for settings catalog. */
|
|
8112
|
-
function listHaCalendarCatalog(brokers) {
|
|
8434
|
+
function listHaCalendarCatalog(brokers, onlyBrokerId) {
|
|
8113
8435
|
const out = [];
|
|
8114
8436
|
const seen = /* @__PURE__ */ new Set();
|
|
8115
|
-
for (const { manager } of brokers.listManagers()) {
|
|
8437
|
+
for (const { brokerId, manager } of brokers.listManagers()) {
|
|
8438
|
+
if (onlyBrokerId !== void 0 && brokerId !== onlyBrokerId) continue;
|
|
8116
8439
|
const states = manager.snapshotAllStates();
|
|
8117
8440
|
for (const [entityId, raw] of Object.entries(states)) {
|
|
8118
8441
|
if (!isCalendarEntity(entityId) || seen.has(entityId)) continue;
|
|
@@ -8283,7 +8606,7 @@ function entityToStandaloneCandidate(entity, alreadyAdopted) {
|
|
|
8283
8606
|
return {
|
|
8284
8607
|
childNativeId: entity.entityId,
|
|
8285
8608
|
name: entity.name,
|
|
8286
|
-
type: entity.mappedTo?.deviceType ??
|
|
8609
|
+
type: entity.mappedTo?.deviceType ?? require_ha_instance_settings.DeviceType.Generic,
|
|
8287
8610
|
status: entityStatus(entity.state),
|
|
8288
8611
|
metadata: entity.area !== null ? { externalLocation: entity.area } : {},
|
|
8289
8612
|
alreadyAdopted,
|
|
@@ -8418,7 +8741,7 @@ function readPersistedStandaloneSpec(device) {
|
|
|
8418
8741
|
* adoption-proof source field), so it is projected directly here, mirroring the
|
|
8419
8742
|
* Container's `EnumSensorDateTimeFormatSchema.safeParse` guard. */
|
|
8420
8743
|
function readDateTimeFormat(config) {
|
|
8421
|
-
const parsed =
|
|
8744
|
+
const parsed = require_ha_instance_settings.EnumSensorDateTimeFormatSchema.safeParse(config["dateTimeFormat"]);
|
|
8422
8745
|
return parsed.success ? parsed.data : void 0;
|
|
8423
8746
|
}
|
|
8424
8747
|
/** Flatten a live device's config into a plain blob for `readHaSource`. */
|
|
@@ -8561,10 +8884,7 @@ function planStandaloneReconcile(devices, freshById, brokerId) {
|
|
|
8561
8884
|
var ADOPTION_FILTER_DEVICES = "devices";
|
|
8562
8885
|
var ADOPTION_FILTER_ENTITIES = "entities";
|
|
8563
8886
|
var HA_KIND = "home-assistant";
|
|
8564
|
-
var DEFAULT_CONFIG = {
|
|
8565
|
-
brokers: [],
|
|
8566
|
-
calendarIds: []
|
|
8567
|
-
};
|
|
8887
|
+
var DEFAULT_CONFIG = { calendarIds: [] };
|
|
8568
8888
|
/**
|
|
8569
8889
|
* Home Assistant provider addon.
|
|
8570
8890
|
*
|
|
@@ -8585,12 +8905,12 @@ var DEFAULT_CONFIG = {
|
|
|
8585
8905
|
* …) and route HA state pushes into runtime-state slices + cap
|
|
8586
8906
|
* mutations into HA call_service.
|
|
8587
8907
|
*/
|
|
8588
|
-
var HaProviderAddon = class HaProviderAddon extends
|
|
8908
|
+
var HaProviderAddon = class HaProviderAddon extends require_ha_instance_settings.BaseDeviceProvider {
|
|
8589
8909
|
addonId = "provider-homeassistant";
|
|
8590
8910
|
providerName = "Home Assistant";
|
|
8591
8911
|
deviceClasses = {
|
|
8592
|
-
[
|
|
8593
|
-
[
|
|
8912
|
+
[require_ha_instance_settings.DeviceType.Container]: HaContainerDevice,
|
|
8913
|
+
[require_ha_instance_settings.DeviceType.EventEmitter]: HaEventBusDevice
|
|
8594
8914
|
};
|
|
8595
8915
|
async onGetCreationSchema(_type) {
|
|
8596
8916
|
return null;
|
|
@@ -8611,6 +8931,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
8611
8931
|
}
|
|
8612
8932
|
registry = null;
|
|
8613
8933
|
brokerStore = null;
|
|
8934
|
+
instances = null;
|
|
8614
8935
|
/** Runtime broker rows (records when B2 is live, else settings). */
|
|
8615
8936
|
activeBrokers = [];
|
|
8616
8937
|
/** Periodic re-sync sweep handle (cleared on shutdown). */
|
|
@@ -8625,11 +8946,26 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
8625
8946
|
constructor() {
|
|
8626
8947
|
super({ ...DEFAULT_CONFIG });
|
|
8627
8948
|
}
|
|
8949
|
+
flattenPorts(records) {
|
|
8950
|
+
if (this.ctx.secrets === void 0) throw new Error("provider-homeassistant: ctx.secrets is required");
|
|
8951
|
+
return {
|
|
8952
|
+
readStored: (key) => this.readStoredGlobalKey(key),
|
|
8953
|
+
...require_ha_instance_settings.haInstanceSettingsPortsOver(this.ctx.api.integrations),
|
|
8954
|
+
...require_ha_instance_settings.flattenPortsOver(records.kind(haBrokersEntryKind))
|
|
8955
|
+
};
|
|
8956
|
+
}
|
|
8628
8957
|
async onInitialize() {
|
|
8629
8958
|
const records = this.ctx.records;
|
|
8630
8959
|
if (records === void 0) throw new Error("provider-homeassistant: ctx.records is required (hub-only addon)");
|
|
8631
8960
|
await records.declare(haAllRecordKinds);
|
|
8632
8961
|
this.brokerStore = HaBrokerStore.of(records);
|
|
8962
|
+
this.instances = buildHaInstances(records, {
|
|
8963
|
+
mintId: () => this.requireRegistry().allocateId(),
|
|
8964
|
+
infoOf: (id) => this.registry?.get(id) ?? null,
|
|
8965
|
+
onChanged: (id) => this.syncBrokerRuntime(id),
|
|
8966
|
+
probe: (settings) => this.requireRegistry().testSettings(settings)
|
|
8967
|
+
});
|
|
8968
|
+
await this.runMigrations(require_ha_instance_settings.defineAddonMigrations(ADDON_MIGRATIONS.filter((m) => m.meta.id === FLATTEN_BROKER_RECORDS_ID), this.flattenPorts(records)));
|
|
8633
8969
|
this.activeBrokers = await this.loadPersistedBrokers();
|
|
8634
8970
|
this.registry = new HaBrokerRegistry(this.ctx.logger, this.ctx.eventBus);
|
|
8635
8971
|
this.registry.setOnBrokerConnected((brokerId) => {
|
|
@@ -8646,35 +8982,40 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
8646
8982
|
this.reconcileAllConnectedBrokers();
|
|
8647
8983
|
}, HaProviderAddon.RESYNC_INTERVAL_MS);
|
|
8648
8984
|
this.ctx.logger.info("ha provider initialised", { meta: { brokerCount: this.activeBrokers.length } });
|
|
8985
|
+
await this.runMigrations(require_ha_instance_settings.defineAddonMigrations(ADDON_MIGRATIONS.filter((m) => m.meta.id !== FLATTEN_BROKER_RECORDS_ID), this.flattenPorts(records)));
|
|
8649
8986
|
await this.reconcileIntegrationsToBrokers();
|
|
8650
8987
|
this.subscribeIntegrationLifecycle();
|
|
8651
8988
|
return [
|
|
8652
8989
|
{
|
|
8653
|
-
capability:
|
|
8990
|
+
capability: require_ha_instance_settings.brokerCapability,
|
|
8654
8991
|
provider: this.buildBrokerProvider()
|
|
8655
8992
|
},
|
|
8656
8993
|
{
|
|
8657
|
-
capability:
|
|
8994
|
+
capability: require_ha_instance_settings.integrationInstancesCapability,
|
|
8995
|
+
provider: this.requireInstances().toProvider()
|
|
8996
|
+
},
|
|
8997
|
+
{
|
|
8998
|
+
capability: require_ha_instance_settings.deviceAdoptionCapability,
|
|
8658
8999
|
provider: this.buildAdoptionProvider()
|
|
8659
9000
|
},
|
|
8660
9001
|
{
|
|
8661
|
-
capability:
|
|
9002
|
+
capability: require_ha_instance_settings.notificationOutputCapability,
|
|
8662
9003
|
provider: await this.buildNotificationOutputProvider()
|
|
8663
9004
|
},
|
|
8664
9005
|
{
|
|
8665
|
-
capability:
|
|
9006
|
+
capability: require_ha_instance_settings.addonRoutesCapability,
|
|
8666
9007
|
provider: createHaIconRouteProvider()
|
|
8667
9008
|
},
|
|
8668
9009
|
{
|
|
8669
|
-
capability:
|
|
9010
|
+
capability: require_ha_instance_settings.boardKeySourceCapability,
|
|
8670
9011
|
provider: this.buildBoardKeyProvider()
|
|
8671
9012
|
},
|
|
8672
9013
|
{
|
|
8673
|
-
capability:
|
|
9014
|
+
capability: require_ha_instance_settings.calendarSourceCapability,
|
|
8674
9015
|
provider: createHaCalendarSource({
|
|
8675
9016
|
brokers: this.requireRegistry(),
|
|
8676
9017
|
logger: this.ctx.logger.child("calendar-source"),
|
|
8677
|
-
selectedCalendarIds: () =>
|
|
9018
|
+
selectedCalendarIds: (brokerId) => this.calendarIdsOf(brokerId)
|
|
8678
9019
|
})
|
|
8679
9020
|
}
|
|
8680
9021
|
];
|
|
@@ -8722,7 +9063,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
8722
9063
|
return manager.sendMessage({ type: "config/device_registry/list" });
|
|
8723
9064
|
},
|
|
8724
9065
|
listBrokerIds: () => this.activeBrokers.map((b) => b.id),
|
|
8725
|
-
readPublicEndpoint:
|
|
9066
|
+
readPublicEndpoint: require_ha_instance_settings.publicEndpointReaderFrom(this.ctx.api.localNetwork),
|
|
8726
9067
|
logger: this.ctx.logger.child("notification-output")
|
|
8727
9068
|
});
|
|
8728
9069
|
}
|
|
@@ -8772,6 +9113,20 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
8772
9113
|
*
|
|
8773
9114
|
* Idempotent — runs on boot + on every integration.* event.
|
|
8774
9115
|
*/
|
|
9116
|
+
/**
|
|
9117
|
+
* Calendars to import, per broker (D985): the integration's own selection, filled by the
|
|
9118
|
+
* reconcile that already reads each integration's settings. A broker whose integration has no
|
|
9119
|
+
* selection yet (the move has not run) uses the legacy shared set, so a boot mid-move never
|
|
9120
|
+
* changes what is imported.
|
|
9121
|
+
*/
|
|
9122
|
+
calendarIdsByBroker = /* @__PURE__ */ new Map();
|
|
9123
|
+
noteInstanceCalendars(brokerId, raw) {
|
|
9124
|
+
if (Array.isArray(raw) && raw.every((id) => typeof id === "string")) this.calendarIdsByBroker.set(brokerId, new Set(raw));
|
|
9125
|
+
else this.calendarIdsByBroker.delete(brokerId);
|
|
9126
|
+
}
|
|
9127
|
+
calendarIdsOf(brokerId) {
|
|
9128
|
+
return this.calendarIdsByBroker.get(brokerId) ?? new Set(this.config.calendarIds ?? []);
|
|
9129
|
+
}
|
|
8775
9130
|
async reconcileIntegrationsToBrokers() {
|
|
8776
9131
|
const api = this.ctx.api;
|
|
8777
9132
|
if (!api.integrations?.list?.query || !api.integrations.getSettings?.query) return;
|
|
@@ -8783,6 +9138,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
8783
9138
|
const settings = await api.integrations.getSettings.query({ id: integration.id });
|
|
8784
9139
|
const brokerId = typeof settings["brokerId"] === "string" ? settings["brokerId"] : null;
|
|
8785
9140
|
if (!brokerId) continue;
|
|
9141
|
+
this.noteInstanceCalendars(brokerId, settings["calendarIds"]);
|
|
8786
9142
|
if (!this.activeBrokers.find((b) => b.id === brokerId)) {
|
|
8787
9143
|
this.ctx.logger.warn("integration→broker: linked broker not found", { meta: {
|
|
8788
9144
|
integrationId: integration.id,
|
|
@@ -8809,7 +9165,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
8809
9165
|
integrationCount: haIntegrations.length
|
|
8810
9166
|
} });
|
|
8811
9167
|
} catch (err) {
|
|
8812
|
-
this.ctx.logger.warn("integration→broker reconcile failed", { meta: { error:
|
|
9168
|
+
this.ctx.logger.warn("integration→broker reconcile failed", { meta: { error: require_ha_instance_settings.errMsg(err) } });
|
|
8813
9169
|
}
|
|
8814
9170
|
}
|
|
8815
9171
|
subscribeIntegrationLifecycle() {
|
|
@@ -8822,23 +9178,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
8822
9178
|
eb.subscribe({ category: "integration.disabled" }, handler);
|
|
8823
9179
|
eb.subscribe({ category: "integration.deleted" }, handler);
|
|
8824
9180
|
}
|
|
8825
|
-
globalSettingsSchema(
|
|
8826
|
-
if (cap === "calendar-source") return this.schema({ sections: [{
|
|
8827
|
-
id: "ha-calendars-import",
|
|
8828
|
-
title: "Calendars to import",
|
|
8829
|
-
titleKey: "provider-homeassistant:globalSettings.s_ha-calendars-import.title",
|
|
8830
|
-
columns: 1,
|
|
8831
|
-
fields: [{
|
|
8832
|
-
type: "multiselect",
|
|
8833
|
-
key: "calendarIds",
|
|
8834
|
-
label: "Calendars",
|
|
8835
|
-
labelKey: "provider-homeassistant:globalSettings.s_ha-calendars-import.calendarIds.label",
|
|
8836
|
-
options: [],
|
|
8837
|
-
optionsFrom: "_calendarCatalog",
|
|
8838
|
-
description: "Discovered from connected HA brokers. Select at least one to import; leave empty to import none.",
|
|
8839
|
-
descriptionKey: "provider-homeassistant:globalSettings.s_ha-calendars-import.calendarIds.description"
|
|
8840
|
-
}]
|
|
8841
|
-
}] });
|
|
9181
|
+
globalSettingsSchema(_cap) {
|
|
8842
9182
|
return this.schema({ sections: [{
|
|
8843
9183
|
id: "ha-broker",
|
|
8844
9184
|
title: "Home Assistant broker",
|
|
@@ -8853,14 +9193,39 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
8853
9193
|
}]
|
|
8854
9194
|
}] });
|
|
8855
9195
|
}
|
|
8856
|
-
|
|
8857
|
-
|
|
8858
|
-
|
|
8859
|
-
|
|
8860
|
-
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
9196
|
+
/**
|
|
9197
|
+
* The calendars of ONE Home Assistant instance (D985): picked on its integration, offered from
|
|
9198
|
+
* that broker's own `calendar.*` entities.
|
|
9199
|
+
*/
|
|
9200
|
+
async integrationSettingsSchema(instance) {
|
|
9201
|
+
if (instance.integrationId === null || !this.registry) return null;
|
|
9202
|
+
let brokerId;
|
|
9203
|
+
try {
|
|
9204
|
+
brokerId = (await this.ctx.api.integrations.getSettings.query({ id: instance.integrationId }))["brokerId"];
|
|
9205
|
+
} catch (err) {
|
|
9206
|
+
this.ctx.logger.warn("HA integration form: instance lookup failed — broker form shown", { meta: {
|
|
9207
|
+
integrationId: instance.integrationId,
|
|
9208
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
9209
|
+
} });
|
|
9210
|
+
return null;
|
|
9211
|
+
}
|
|
9212
|
+
if (typeof brokerId !== "string") return null;
|
|
9213
|
+
const options = listHaCalendarCatalog(this.registry, brokerId);
|
|
9214
|
+
return this.schema({ sections: [{
|
|
9215
|
+
id: "ha-calendars-import",
|
|
9216
|
+
title: "Calendars to import",
|
|
9217
|
+
titleKey: "provider-homeassistant:globalSettings.s_ha-calendars-import.title",
|
|
9218
|
+
columns: 1,
|
|
9219
|
+
fields: [{
|
|
9220
|
+
type: "multiselect",
|
|
9221
|
+
key: "calendarIds",
|
|
9222
|
+
label: "Calendars",
|
|
9223
|
+
labelKey: "provider-homeassistant:globalSettings.s_ha-calendars-import.calendarIds.label",
|
|
9224
|
+
options,
|
|
9225
|
+
description: "Discovered from this Home Assistant instance. Select at least one to import; leave empty to import none.",
|
|
9226
|
+
descriptionKey: "provider-homeassistant:globalSettings.s_ha-calendars-import.calendarIds.description"
|
|
9227
|
+
}]
|
|
9228
|
+
}] });
|
|
8864
9229
|
}
|
|
8865
9230
|
async onShutdown() {
|
|
8866
9231
|
if (this.resyncTimer) {
|
|
@@ -8870,7 +9235,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
8870
9235
|
try {
|
|
8871
9236
|
await this.registry?.shutdown();
|
|
8872
9237
|
} catch (err) {
|
|
8873
|
-
this.ctx.logger.warn("ha provider shutdown error", { meta: { error:
|
|
9238
|
+
this.ctx.logger.warn("ha provider shutdown error", { meta: { error: require_ha_instance_settings.errMsg(err) } });
|
|
8874
9239
|
}
|
|
8875
9240
|
this.registry = null;
|
|
8876
9241
|
await super.onShutdown();
|
|
@@ -8902,30 +9267,25 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
8902
9267
|
} catch (err) {
|
|
8903
9268
|
this.ctx.logger.warn("ha onConfigChanged: removeEntry failed", { meta: {
|
|
8904
9269
|
brokerId: liveId,
|
|
8905
|
-
error:
|
|
9270
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
8906
9271
|
} });
|
|
8907
9272
|
}
|
|
8908
9273
|
for (const entry of persisted) {
|
|
8909
9274
|
if (entry.id && liveIds.has(entry.id)) {
|
|
8910
9275
|
try {
|
|
8911
|
-
const settings = entry
|
|
8912
|
-
|
|
8913
|
-
accessToken: entry.auth.accessToken
|
|
8914
|
-
};
|
|
8915
|
-
await reg.updateEntry(entry.id, settings);
|
|
9276
|
+
const settings = entryToSettings(entry);
|
|
9277
|
+
if (settings !== null) await reg.updateEntry(entry.id, settings);
|
|
8916
9278
|
} catch (err) {
|
|
8917
9279
|
this.ctx.logger.warn("ha onConfigChanged: updateEntry failed", { meta: {
|
|
8918
9280
|
brokerId: entry.id,
|
|
8919
|
-
error:
|
|
9281
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
8920
9282
|
} });
|
|
8921
9283
|
}
|
|
8922
9284
|
continue;
|
|
8923
9285
|
}
|
|
8924
9286
|
try {
|
|
8925
|
-
const settings = entry
|
|
8926
|
-
|
|
8927
|
-
accessToken: entry.auth.accessToken
|
|
8928
|
-
};
|
|
9287
|
+
const settings = entryToSettings(entry);
|
|
9288
|
+
if (settings === null) continue;
|
|
8929
9289
|
const created = await reg.createEntry(entry.name, settings);
|
|
8930
9290
|
if (created.id !== entry.id) {
|
|
8931
9291
|
const next = persisted.map((b) => b === entry ? {
|
|
@@ -8937,27 +9297,65 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
8937
9297
|
} catch (err) {
|
|
8938
9298
|
this.ctx.logger.warn("ha onConfigChanged: failed to start new broker", { meta: {
|
|
8939
9299
|
brokerName: entry.name,
|
|
8940
|
-
error:
|
|
9300
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
8941
9301
|
} });
|
|
8942
9302
|
}
|
|
8943
9303
|
}
|
|
8944
9304
|
}
|
|
8945
9305
|
async loadPersistedBrokers() {
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
9306
|
+
return this.requireBrokerStore().listResolved();
|
|
9307
|
+
}
|
|
9308
|
+
requireInstances() {
|
|
9309
|
+
if (this.instances === null) throw new Error("provider-homeassistant: instances not ready");
|
|
9310
|
+
return this.instances;
|
|
9311
|
+
}
|
|
9312
|
+
/**
|
|
9313
|
+
* After an instance changed (created, edited, switched, deleted) the runtime follows the record:
|
|
9314
|
+
* a missing record removes the manager and its adopted devices, an off or unfilled one stops the
|
|
9315
|
+
* manager but keeps the devices, a filled one starts or re-authenticates it.
|
|
9316
|
+
*/
|
|
9317
|
+
async syncBrokerRuntime(id) {
|
|
9318
|
+
const reg = this.requireRegistry();
|
|
9319
|
+
const entry = await this.requireBrokerStore().getById(id);
|
|
9320
|
+
this.activeBrokers = await this.loadPersistedBrokers();
|
|
9321
|
+
if (entry === null) {
|
|
9322
|
+
await reg.removeEntry(id);
|
|
9323
|
+
const deviceRegistry = this.ctx.kernel.deviceRegistry;
|
|
9324
|
+
const devices = this.ctx.kernel.devices;
|
|
9325
|
+
if (deviceRegistry && devices) await cascadeRemoveDevicesForBroker({
|
|
9326
|
+
reg: deviceRegistry,
|
|
9327
|
+
devices,
|
|
9328
|
+
addonId: this.ctx.id,
|
|
9329
|
+
brokerId: id,
|
|
9330
|
+
logger: this.ctx.logger
|
|
9331
|
+
});
|
|
9332
|
+
return;
|
|
9333
|
+
}
|
|
9334
|
+
const settings = entry.enabled === false ? null : entryToSettings(entry);
|
|
9335
|
+
if (settings === null) {
|
|
9336
|
+
await reg.removeEntry(id);
|
|
9337
|
+
return;
|
|
8950
9338
|
}
|
|
8951
|
-
|
|
9339
|
+
try {
|
|
9340
|
+
if (reg.get(id) !== null) await reg.updateEntry(id, settings);
|
|
9341
|
+
else await reg.createEntry(entry.name, settings, {
|
|
9342
|
+
id,
|
|
9343
|
+
...entry.integrationId !== void 0 ? { integrationId: entry.integrationId } : {}
|
|
9344
|
+
});
|
|
9345
|
+
} catch (err) {
|
|
9346
|
+
this.ctx.logger.warn("ha instance: could not start the broker", { meta: {
|
|
9347
|
+
brokerId: id,
|
|
9348
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
9349
|
+
} });
|
|
9350
|
+
}
|
|
9351
|
+
}
|
|
9352
|
+
requireBrokerStore() {
|
|
9353
|
+
if (this.brokerStore === null) throw new Error("provider-homeassistant: broker store not ready");
|
|
9354
|
+
return this.brokerStore;
|
|
8952
9355
|
}
|
|
8953
9356
|
async persistBrokerEntries(entries) {
|
|
8954
9357
|
this.activeBrokers = [...entries];
|
|
8955
|
-
|
|
8956
|
-
if (store !== null) {
|
|
8957
|
-
await store.replaceAll(entries);
|
|
8958
|
-
return;
|
|
8959
|
-
}
|
|
8960
|
-
await this.updateGlobalSettings({ brokers: entries });
|
|
9358
|
+
await this.requireBrokerStore().replaceAll(entries);
|
|
8961
9359
|
}
|
|
8962
9360
|
buildBrokerProvider() {
|
|
8963
9361
|
const ownerAddonId = this.ctx.id;
|
|
@@ -9031,7 +9429,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
9031
9429
|
getSettings: async ({ id }) => {
|
|
9032
9430
|
const entry = this.activeBrokers.find((b) => b.id === id);
|
|
9033
9431
|
if (!entry) return null;
|
|
9034
|
-
return
|
|
9432
|
+
return entryToSettings(entry);
|
|
9035
9433
|
},
|
|
9036
9434
|
setSettings: async ({ id, settings }) => {
|
|
9037
9435
|
if (!owns(id)) return;
|
|
@@ -9042,7 +9440,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
9042
9440
|
getBrokerConfig: async ({ id }) => {
|
|
9043
9441
|
const entry = this.activeBrokers.find((b) => b.id === id);
|
|
9044
9442
|
if (!entry) return null;
|
|
9045
|
-
return
|
|
9443
|
+
return entryToSettings(entry);
|
|
9046
9444
|
},
|
|
9047
9445
|
getSettingsSchema: async ({ kind }) => {
|
|
9048
9446
|
if (kind !== HA_KIND) return null;
|
|
@@ -9087,7 +9485,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
9087
9485
|
const refreshedAt = Date.now();
|
|
9088
9486
|
if (lastDiscoveryAt === null || refreshedAt > lastDiscoveryAt) lastDiscoveryAt = refreshedAt;
|
|
9089
9487
|
} catch (err) {
|
|
9090
|
-
lastError =
|
|
9488
|
+
lastError = require_ha_instance_settings.errMsg(err);
|
|
9091
9489
|
}
|
|
9092
9490
|
const adoptedCount = (await this.ctx.kernel.devices.getAll()).filter((d) => d.parentDeviceId === null && String(d.config.get("system") ?? "") === "homeassistant").length;
|
|
9093
9491
|
return {
|
|
@@ -9233,7 +9631,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
9233
9631
|
name: deviceName
|
|
9234
9632
|
};
|
|
9235
9633
|
const summary = await this.createDevice({
|
|
9236
|
-
type:
|
|
9634
|
+
type: require_ha_instance_settings.DeviceType.Container,
|
|
9237
9635
|
config: containerConfig
|
|
9238
9636
|
});
|
|
9239
9637
|
const accessoryDeviceIds = (await this.ctx.kernel.devices.getChildren(summary.id)).map((c) => c.id);
|
|
@@ -9263,7 +9661,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
9263
9661
|
meta: {
|
|
9264
9662
|
brokerId,
|
|
9265
9663
|
haDeviceId,
|
|
9266
|
-
error:
|
|
9664
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
9267
9665
|
}
|
|
9268
9666
|
});
|
|
9269
9667
|
}
|
|
@@ -9410,7 +9808,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
9410
9808
|
meta: {
|
|
9411
9809
|
brokerId,
|
|
9412
9810
|
entityId,
|
|
9413
|
-
error:
|
|
9811
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
9414
9812
|
}
|
|
9415
9813
|
});
|
|
9416
9814
|
}
|
|
@@ -9431,7 +9829,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
9431
9829
|
*/
|
|
9432
9830
|
async assignImportedLocation(deviceId, externalLocation, knownLocations) {
|
|
9433
9831
|
try {
|
|
9434
|
-
const match =
|
|
9832
|
+
const match = require_ha_instance_settings.bestLocationMatch(externalLocation, knownLocations);
|
|
9435
9833
|
const name = match ?? externalLocation;
|
|
9436
9834
|
let locations = knownLocations;
|
|
9437
9835
|
if (match === null) {
|
|
@@ -9447,7 +9845,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
9447
9845
|
this.ctx.logger.warn("ha-adoption: location import failed (non-fatal)", { meta: {
|
|
9448
9846
|
deviceId,
|
|
9449
9847
|
externalLocation,
|
|
9450
|
-
error:
|
|
9848
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
9451
9849
|
} });
|
|
9452
9850
|
return knownLocations;
|
|
9453
9851
|
}
|
|
@@ -9648,7 +10046,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
9648
10046
|
} catch (err) {
|
|
9649
10047
|
this.ctx.logger.warn("ha bus-event aggregator ensure failed", { meta: {
|
|
9650
10048
|
brokerId,
|
|
9651
|
-
error:
|
|
10049
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
9652
10050
|
} });
|
|
9653
10051
|
}
|
|
9654
10052
|
await reconcileBroker(brokerId, {
|
|
@@ -9658,7 +10056,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
9658
10056
|
onFetchError: (id, error) => {
|
|
9659
10057
|
this.ctx.logger.warn("ha re-sync: inventory fetch failed", { meta: {
|
|
9660
10058
|
brokerId: id,
|
|
9661
|
-
error:
|
|
10059
|
+
error: require_ha_instance_settings.errMsg(error)
|
|
9662
10060
|
} });
|
|
9663
10061
|
}
|
|
9664
10062
|
});
|
|
@@ -9696,7 +10094,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
9696
10094
|
meta: {
|
|
9697
10095
|
brokerId,
|
|
9698
10096
|
haDeviceId,
|
|
9699
|
-
error:
|
|
10097
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
9700
10098
|
}
|
|
9701
10099
|
});
|
|
9702
10100
|
}
|
|
@@ -9741,7 +10139,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
9741
10139
|
tags: { deviceId: action.deviceId },
|
|
9742
10140
|
meta: {
|
|
9743
10141
|
brokerId,
|
|
9744
|
-
error:
|
|
10142
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
9745
10143
|
}
|
|
9746
10144
|
});
|
|
9747
10145
|
}
|
|
@@ -9822,7 +10220,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
9822
10220
|
brokerId,
|
|
9823
10221
|
kind: "event-bus"
|
|
9824
10222
|
}, null, {
|
|
9825
|
-
type:
|
|
10223
|
+
type: require_ha_instance_settings.DeviceType.EventEmitter,
|
|
9826
10224
|
name: "Home Assistant Events"
|
|
9827
10225
|
});
|
|
9828
10226
|
this.eventBusDeviceIds.set(brokerId, device.id);
|
|
@@ -9852,7 +10250,7 @@ var HaProviderAddon = class HaProviderAddon extends require_ha_broker_source.Bas
|
|
|
9852
10250
|
this.ctx.logger.debug("ha bus-event dropped — aggregator not ready", { meta: {
|
|
9853
10251
|
brokerId,
|
|
9854
10252
|
eventType: ev.eventType,
|
|
9855
|
-
error:
|
|
10253
|
+
error: require_ha_instance_settings.errMsg(err)
|
|
9856
10254
|
} });
|
|
9857
10255
|
return;
|
|
9858
10256
|
}
|