@camstack/addon-provider-homeassistant 1.2.648 → 1.2.650
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 +13 -0
- package/dist/addon.js +2 -1
- package/dist/addon.mjs +2 -1
- package/dist/{ha-broker-source-iLA_DsW8.mjs → ha-broker-source-B2v2dnPe.mjs} +294 -105
- package/dist/{ha-broker-source-Ba20mhmb.js → ha-broker-source-DUj9pUWr.js} +294 -105
- package/dist/ha-export/ha-export.addon.js +123 -41
- package/dist/ha-export/ha-export.addon.mjs +123 -41
- package/package.json +2 -2
|
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
enumerable: true
|
|
25
25
|
}) : target, mod));
|
|
26
26
|
//#endregion
|
|
27
|
-
const require_ha_broker_source = require("../ha-broker-source-
|
|
27
|
+
const require_ha_broker_source = require("../ha-broker-source-DUj9pUWr.js");
|
|
28
28
|
let node_crypto = require("node:crypto");
|
|
29
29
|
require("node:zlib");
|
|
30
30
|
let node_fs = require("node:fs");
|
|
@@ -975,19 +975,22 @@ var NATIVE_CAP_PLATFORMS = {
|
|
|
975
975
|
*/
|
|
976
976
|
cover: {
|
|
977
977
|
platform: "cover",
|
|
978
|
+
availableWhenOnline: true,
|
|
978
979
|
primary: { from: null },
|
|
979
980
|
controls: {
|
|
980
981
|
position: {
|
|
981
982
|
from: "position",
|
|
982
983
|
min: 0,
|
|
983
984
|
max: 100,
|
|
984
|
-
step: 1
|
|
985
|
+
step: 1,
|
|
986
|
+
requiresFeature: "cover-positionable"
|
|
985
987
|
},
|
|
986
988
|
tilt: {
|
|
987
989
|
from: "tilt",
|
|
988
990
|
min: 0,
|
|
989
991
|
max: 100,
|
|
990
|
-
step: 1
|
|
992
|
+
step: 1,
|
|
993
|
+
requiresFeature: "cover-tilt"
|
|
991
994
|
},
|
|
992
995
|
open: {
|
|
993
996
|
from: "open",
|
|
@@ -999,7 +1002,8 @@ var NATIVE_CAP_PLATFORMS = {
|
|
|
999
1002
|
},
|
|
1000
1003
|
stop: {
|
|
1001
1004
|
from: "stop",
|
|
1002
|
-
commandOnly: true
|
|
1005
|
+
commandOnly: true,
|
|
1006
|
+
requiresFeature: "cover-stoppable"
|
|
1003
1007
|
}
|
|
1004
1008
|
}
|
|
1005
1009
|
},
|
|
@@ -1388,9 +1392,11 @@ function buildNativeComponent(input) {
|
|
|
1388
1392
|
if (native === void 0) return null;
|
|
1389
1393
|
const primary = resolveBinding(input, native.primary);
|
|
1390
1394
|
if (primary === null) return null;
|
|
1395
|
+
const features = input.features ?? [];
|
|
1391
1396
|
const absorbed = new Set([derivedEntityId(input.capName), ...primary.entities]);
|
|
1392
1397
|
const controls = {};
|
|
1393
1398
|
for (const [name, binding] of Object.entries(native.controls ?? {})) {
|
|
1399
|
+
if (binding.requiresFeature !== void 0 && !features.includes(binding.requiresFeature)) continue;
|
|
1394
1400
|
const resolved = resolveBinding(input, binding);
|
|
1395
1401
|
if (resolved === null) continue;
|
|
1396
1402
|
controls[name] = resolved.control;
|
|
@@ -1409,6 +1415,11 @@ function buildNativeComponent(input) {
|
|
|
1409
1415
|
...primary.control,
|
|
1410
1416
|
...native.deviceClass !== void 0 ? { device_class: native.deviceClass } : {},
|
|
1411
1417
|
...native.icon !== void 0 ? { icon: native.icon } : {},
|
|
1418
|
+
...native.availableWhenOnline === true ? {
|
|
1419
|
+
availability_topic: stateTopic(input.deviceKey, "online"),
|
|
1420
|
+
payload_available: "true",
|
|
1421
|
+
payload_not_available: "false"
|
|
1422
|
+
} : {},
|
|
1412
1423
|
...Object.keys(controls).length > 0 ? { controls } : {},
|
|
1413
1424
|
...native.constants,
|
|
1414
1425
|
...flags
|
|
@@ -2343,7 +2354,8 @@ var CAP_ENTITY_MAP = {
|
|
|
2343
2354
|
unit: "%",
|
|
2344
2355
|
icon: "mdi:arrow-up-down",
|
|
2345
2356
|
writable: true,
|
|
2346
|
-
range: PERCENT_RANGE
|
|
2357
|
+
range: PERCENT_RANGE,
|
|
2358
|
+
requiresFeature: "cover-positionable"
|
|
2347
2359
|
},
|
|
2348
2360
|
tilt: {
|
|
2349
2361
|
platform: "number",
|
|
@@ -2353,7 +2365,8 @@ var CAP_ENTITY_MAP = {
|
|
|
2353
2365
|
icon: "mdi:angle-acute",
|
|
2354
2366
|
writable: true,
|
|
2355
2367
|
range: PERCENT_RANGE,
|
|
2356
|
-
enabledByDefault: false
|
|
2368
|
+
enabledByDefault: false,
|
|
2369
|
+
requiresFeature: "cover-tilt"
|
|
2357
2370
|
}
|
|
2358
2371
|
},
|
|
2359
2372
|
commands: {
|
|
@@ -2368,7 +2381,8 @@ var CAP_ENTITY_MAP = {
|
|
|
2368
2381
|
stop: {
|
|
2369
2382
|
label: "Stop",
|
|
2370
2383
|
icon: "mdi:stop",
|
|
2371
|
-
enabledByDefault: false
|
|
2384
|
+
enabledByDefault: false,
|
|
2385
|
+
requiresFeature: "cover-stoppable"
|
|
2372
2386
|
}
|
|
2373
2387
|
}
|
|
2374
2388
|
},
|
|
@@ -3375,7 +3389,8 @@ function capEntities(capName) {
|
|
|
3375
3389
|
for (const [suffix, extra] of Object.entries(mapping.extras ?? {})) refs.push({
|
|
3376
3390
|
entity: derivedExtraEntityId(capName, suffix),
|
|
3377
3391
|
label: extra.label,
|
|
3378
|
-
descriptor: extra
|
|
3392
|
+
descriptor: extra,
|
|
3393
|
+
...extra.requiresFeature !== void 0 ? { requiresFeature: extra.requiresFeature } : {}
|
|
3379
3394
|
});
|
|
3380
3395
|
return refs;
|
|
3381
3396
|
}
|
|
@@ -3394,8 +3409,9 @@ function deriveCapValue(derivation, raw) {
|
|
|
3394
3409
|
return derivation.kind === "equals" ? raw === derivation.value : raw !== derivation.value;
|
|
3395
3410
|
}
|
|
3396
3411
|
/** A capability's entities as the catalog builders consume them. */
|
|
3397
|
-
function capEntitySpecs(capName) {
|
|
3398
|
-
const
|
|
3412
|
+
function capEntitySpecs(capName, features = []) {
|
|
3413
|
+
const declared = (required) => required === void 0 || features.includes(required);
|
|
3414
|
+
const specs = capEntities(capName).filter((ref) => declared(ref.requiresFeature)).map((ref) => ({
|
|
3399
3415
|
entity: ref.entity,
|
|
3400
3416
|
platform: ref.descriptor.platform,
|
|
3401
3417
|
label: ref.label,
|
|
@@ -3408,15 +3424,18 @@ function capEntitySpecs(capName) {
|
|
|
3408
3424
|
...ref.descriptor.entityCategory !== void 0 ? { entityCategory: ref.descriptor.entityCategory } : {},
|
|
3409
3425
|
...ref.descriptor.writable === true ? { writable: true } : {}
|
|
3410
3426
|
}));
|
|
3411
|
-
for (const ref of capCommandEntities(capName))
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3427
|
+
for (const ref of capCommandEntities(capName)) {
|
|
3428
|
+
if (!declared(ref.command.requiresFeature)) continue;
|
|
3429
|
+
specs.push({
|
|
3430
|
+
entity: ref.entity,
|
|
3431
|
+
platform: "button",
|
|
3432
|
+
label: ref.label,
|
|
3433
|
+
enabledByDefault: ref.command.enabledByDefault ?? true,
|
|
3434
|
+
writable: true,
|
|
3435
|
+
...ref.command.icon !== void 0 ? { icon: ref.command.icon } : {},
|
|
3436
|
+
...ref.command.entityCategory !== void 0 ? { entityCategory: ref.command.entityCategory } : {}
|
|
3437
|
+
});
|
|
3438
|
+
}
|
|
3420
3439
|
return specs;
|
|
3421
3440
|
}
|
|
3422
3441
|
function readControlOptions(slice) {
|
|
@@ -3794,11 +3813,12 @@ function buildDerivedPlan(device, options = UNNEGOTIATED_PLAN_OPTIONS) {
|
|
|
3794
3813
|
deviceId: device.id,
|
|
3795
3814
|
capName: cap,
|
|
3796
3815
|
label: humanise(cap),
|
|
3816
|
+
features: device.features,
|
|
3797
3817
|
slice: device.capSlices?.[cap],
|
|
3798
3818
|
mapping
|
|
3799
3819
|
});
|
|
3800
3820
|
if (plan === null) {
|
|
3801
|
-
specs.push(...capEntitySpecs(cap).map((spec) => degradeUnsupported(spec, options)));
|
|
3821
|
+
specs.push(...capEntitySpecs(cap, device.features).map((spec) => degradeUnsupported(spec, options)));
|
|
3802
3822
|
continue;
|
|
3803
3823
|
}
|
|
3804
3824
|
native[toComponentKey(plan.component.platform, derivedEntityId(cap))] = plan.component;
|
|
@@ -3809,7 +3829,7 @@ function buildDerivedPlan(device, options = UNNEGOTIATED_PLAN_OPTIONS) {
|
|
|
3809
3829
|
* to make the device look tidy would be a silent loss of a value an
|
|
3810
3830
|
* operator already automates on.
|
|
3811
3831
|
*/
|
|
3812
|
-
specs.push(...capEntitySpecs(cap).filter((spec) => !plan.absorbed.has(spec.entity)));
|
|
3832
|
+
specs.push(...capEntitySpecs(cap, device.features).filter((spec) => !plan.absorbed.has(spec.entity)));
|
|
3813
3833
|
}
|
|
3814
3834
|
const assembled = assemble(device, specs);
|
|
3815
3835
|
return {
|
|
@@ -4671,6 +4691,15 @@ function isCameraCommand(command) {
|
|
|
4671
4691
|
function isCapCommand(command) {
|
|
4672
4692
|
return !isCameraCommand(command);
|
|
4673
4693
|
}
|
|
4694
|
+
var APPLIED = { ok: true };
|
|
4695
|
+
/** The 422 body of a command that was not applied: the reason travels with it. */
|
|
4696
|
+
function commandNotAppliedBody(refusal) {
|
|
4697
|
+
return {
|
|
4698
|
+
error: "command not applied",
|
|
4699
|
+
reason: refusal.reason,
|
|
4700
|
+
...refusal.message !== void 0 ? { message: refusal.message } : {}
|
|
4701
|
+
};
|
|
4702
|
+
}
|
|
4674
4703
|
/**
|
|
4675
4704
|
* Make the call the command names.
|
|
4676
4705
|
*
|
|
@@ -4950,18 +4979,29 @@ async function call(slice, capName, make) {
|
|
|
4950
4979
|
*
|
|
4951
4980
|
* Idempotent: a store without the key yields an empty plan, which writes nothing.
|
|
4952
4981
|
*/
|
|
4953
|
-
/** The
|
|
4954
|
-
var
|
|
4982
|
+
/** The addon id, the prefix of its rows in the canonical settings table. */
|
|
4983
|
+
var HA_EXPORT_ADDON_ID = "homeassistant-export";
|
|
4984
|
+
/**
|
|
4985
|
+
* Where the addon's global settings live: the canonical `addon-settings` table, one row per
|
|
4986
|
+
* key, id `<addonId>.<key>`, data `{ addonId, key, value }` (never a per-addon scoped table,
|
|
4987
|
+
* which exists only when something lazily declared it).
|
|
4988
|
+
*/
|
|
4989
|
+
var ADDON_SETTINGS_TABLE = "addon-settings";
|
|
4955
4990
|
var RETIRED_URL_KEYS = ["publicBaseUrl"];
|
|
4956
4991
|
async function planDropPublicBaseUrl(ports) {
|
|
4957
4992
|
const stored = await ports.readStored();
|
|
4958
4993
|
const present = RETIRED_URL_KEYS.filter((key) => key in stored);
|
|
4959
4994
|
if (present.length === 0) return { edits: [] };
|
|
4995
|
+
const rowId = (key) => `${HA_EXPORT_ADDON_ID}.${key}`;
|
|
4960
4996
|
return { edits: [{
|
|
4961
4997
|
op: "deleteKeys",
|
|
4962
|
-
scope:
|
|
4963
|
-
keys:
|
|
4964
|
-
precondition: require_ha_broker_source.preconditionOf(Object.fromEntries(present.map((key) => [key,
|
|
4998
|
+
scope: ADDON_SETTINGS_TABLE,
|
|
4999
|
+
keys: present.map(rowId),
|
|
5000
|
+
precondition: require_ha_broker_source.preconditionOf(Object.fromEntries(present.map((key) => [rowId(key), {
|
|
5001
|
+
addonId: HA_EXPORT_ADDON_ID,
|
|
5002
|
+
key,
|
|
5003
|
+
value: stored[key]
|
|
5004
|
+
}])))
|
|
4965
5005
|
}] };
|
|
4966
5006
|
}
|
|
4967
5007
|
//#endregion
|
|
@@ -9157,9 +9197,23 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9157
9197
|
reply.send({ error: `unroutable command (${resolution.reason})` });
|
|
9158
9198
|
return;
|
|
9159
9199
|
}
|
|
9160
|
-
const
|
|
9161
|
-
|
|
9162
|
-
|
|
9200
|
+
const outcome = await this.dispatch(resolution.command);
|
|
9201
|
+
if (outcome.ok) {
|
|
9202
|
+
reply.status(200);
|
|
9203
|
+
reply.send({});
|
|
9204
|
+
return;
|
|
9205
|
+
}
|
|
9206
|
+
this.ctx.logger.warn("ha-export: command answered 422", {
|
|
9207
|
+
tags: { deviceId: exported.deviceId },
|
|
9208
|
+
meta: {
|
|
9209
|
+
topic,
|
|
9210
|
+
entity: parsed.entity,
|
|
9211
|
+
reason: outcome.reason,
|
|
9212
|
+
message: outcome.message
|
|
9213
|
+
}
|
|
9214
|
+
});
|
|
9215
|
+
reply.status(422);
|
|
9216
|
+
reply.send(commandNotAppliedBody(outcome));
|
|
9163
9217
|
}
|
|
9164
9218
|
async dispatch(command) {
|
|
9165
9219
|
try {
|
|
@@ -9173,7 +9227,7 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9173
9227
|
*/
|
|
9174
9228
|
if (isCapCommand(command)) {
|
|
9175
9229
|
const outcome = await applyCapCommand(await this.ctx.fetchDevice(command.deviceId), command);
|
|
9176
|
-
if (outcome.ok) return
|
|
9230
|
+
if (outcome.ok) return APPLIED;
|
|
9177
9231
|
if (outcome.reason === "no-provider") return this.noProvider(command.deviceId, outcome.capName);
|
|
9178
9232
|
this.ctx.logger.warn("ha-export: the device refused the command", {
|
|
9179
9233
|
tags: { deviceId: command.deviceId },
|
|
@@ -9183,7 +9237,11 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9183
9237
|
...outcome.error !== void 0 ? { error: outcome.error } : {}
|
|
9184
9238
|
}
|
|
9185
9239
|
});
|
|
9186
|
-
return
|
|
9240
|
+
return {
|
|
9241
|
+
ok: false,
|
|
9242
|
+
reason: outcome.reason,
|
|
9243
|
+
...outcome.error !== void 0 ? { message: outcome.error } : {}
|
|
9244
|
+
};
|
|
9187
9245
|
}
|
|
9188
9246
|
switch (command.kind) {
|
|
9189
9247
|
case "camera-function": {
|
|
@@ -9193,7 +9251,11 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9193
9251
|
tags: { deviceId: command.deviceId },
|
|
9194
9252
|
meta: { switchId: command.switchId }
|
|
9195
9253
|
});
|
|
9196
|
-
return
|
|
9254
|
+
return {
|
|
9255
|
+
ok: false,
|
|
9256
|
+
reason: "unknown-function",
|
|
9257
|
+
message: "the switch id is not a camera function"
|
|
9258
|
+
};
|
|
9197
9259
|
}
|
|
9198
9260
|
/**
|
|
9199
9261
|
* A FRESH read for this one device — the pass-level caches are up to
|
|
@@ -9203,7 +9265,11 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9203
9265
|
const device = await this.ctx.api.deviceManager.getDevice.query({ deviceId: command.deviceId });
|
|
9204
9266
|
if (device === null) {
|
|
9205
9267
|
this.ctx.logger.warn("ha-export: dropping a camera-function command, the device is not in the registry", { tags: { deviceId: command.deviceId } });
|
|
9206
|
-
return
|
|
9268
|
+
return {
|
|
9269
|
+
ok: false,
|
|
9270
|
+
reason: "device-not-found",
|
|
9271
|
+
message: "the device is not in the registry"
|
|
9272
|
+
};
|
|
9207
9273
|
}
|
|
9208
9274
|
const privacySlice = await this.ctx.api.deviceState.getCapSlice.query({
|
|
9209
9275
|
deviceId: command.deviceId,
|
|
@@ -9239,22 +9305,26 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9239
9305
|
tags: { deviceId: command.deviceId },
|
|
9240
9306
|
meta: { switchId: parsed.data }
|
|
9241
9307
|
});
|
|
9242
|
-
return
|
|
9308
|
+
return {
|
|
9309
|
+
ok: false,
|
|
9310
|
+
reason: "function-unavailable",
|
|
9311
|
+
message: "the camera function is not available"
|
|
9312
|
+
};
|
|
9243
9313
|
}
|
|
9244
9314
|
await applyCameraFunctionWrite(this.ctx.api, command.deviceId, write, sourceNodeId);
|
|
9245
|
-
return
|
|
9315
|
+
return APPLIED;
|
|
9246
9316
|
}
|
|
9247
9317
|
case "reboot": {
|
|
9248
9318
|
const device = await this.ctx.fetchDevice(command.deviceId);
|
|
9249
9319
|
if (device.reboot === void 0) return this.noProvider(command.deviceId, "reboot");
|
|
9250
9320
|
await device.reboot.reboot({});
|
|
9251
|
-
return
|
|
9321
|
+
return APPLIED;
|
|
9252
9322
|
}
|
|
9253
9323
|
case "ptz-move": {
|
|
9254
9324
|
const device = await this.ctx.fetchDevice(command.deviceId);
|
|
9255
9325
|
if (device.ptz === void 0) return this.noProvider(command.deviceId, "ptz");
|
|
9256
9326
|
await device.ptz.move(PTZ_MOVE_BY_DIRECTION[command.direction] ?? {});
|
|
9257
|
-
return
|
|
9327
|
+
return APPLIED;
|
|
9258
9328
|
}
|
|
9259
9329
|
case "ptz-preset": {
|
|
9260
9330
|
const device = await this.ctx.fetchDevice(command.deviceId);
|
|
@@ -9265,10 +9335,14 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9265
9335
|
tags: { deviceId: command.deviceId },
|
|
9266
9336
|
meta: { preset: command.preset }
|
|
9267
9337
|
});
|
|
9268
|
-
return
|
|
9338
|
+
return {
|
|
9339
|
+
ok: false,
|
|
9340
|
+
reason: "no-such-preset",
|
|
9341
|
+
message: "no preset by that name"
|
|
9342
|
+
};
|
|
9269
9343
|
}
|
|
9270
9344
|
await device.ptz.goToPreset({ presetId: preset.id });
|
|
9271
|
-
return
|
|
9345
|
+
return APPLIED;
|
|
9272
9346
|
}
|
|
9273
9347
|
}
|
|
9274
9348
|
} catch (err) {
|
|
@@ -9284,7 +9358,11 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9284
9358
|
error: errMsg(err)
|
|
9285
9359
|
}
|
|
9286
9360
|
});
|
|
9287
|
-
return
|
|
9361
|
+
return {
|
|
9362
|
+
ok: false,
|
|
9363
|
+
reason: "error",
|
|
9364
|
+
message: errMsg(err)
|
|
9365
|
+
};
|
|
9288
9366
|
}
|
|
9289
9367
|
}
|
|
9290
9368
|
/** A cap the device does not provide. Refused, and never silently. */
|
|
@@ -9293,7 +9371,11 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9293
9371
|
tags: { deviceId },
|
|
9294
9372
|
meta: { capName }
|
|
9295
9373
|
});
|
|
9296
|
-
return
|
|
9374
|
+
return {
|
|
9375
|
+
ok: false,
|
|
9376
|
+
reason: "no-provider",
|
|
9377
|
+
message: `the device has no provider for ${capName}`
|
|
9378
|
+
};
|
|
9297
9379
|
}
|
|
9298
9380
|
/**
|
|
9299
9381
|
* A public, signed, expiring route Home Assistant can fetch bytes from.
|