@camstack/addon-provider-homeassistant 1.2.649 → 1.2.651
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-D3FBA6uG.mjs → ha-broker-source-CCmZ9I6P.mjs} +142 -31
- package/dist/{ha-broker-source-CE3nHkyW.js → ha-broker-source-D4_TtF2V.js} +142 -31
- package/dist/ha-export/ha-export.addon.js +174 -42
- package/dist/ha-export/ha-export.addon.mjs +174 -42
- 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-D4_TtF2V.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
|
*
|
|
@@ -5462,6 +5491,10 @@ var PushClient = class {
|
|
|
5462
5491
|
disposed = false;
|
|
5463
5492
|
lastFailure = null;
|
|
5464
5493
|
suspendedUntil = 0;
|
|
5494
|
+
/** When the current unbroken streak of 404s began; 0 when there is none. */
|
|
5495
|
+
first404At = 0;
|
|
5496
|
+
/** A suspension was entered and not yet logged as left. */
|
|
5497
|
+
suspensionLogged = false;
|
|
5465
5498
|
constructor(options) {
|
|
5466
5499
|
this.transport = options.transport;
|
|
5467
5500
|
this.logger = options.logger;
|
|
@@ -5480,9 +5513,20 @@ var PushClient = class {
|
|
|
5480
5513
|
get suspended() {
|
|
5481
5514
|
return this.suspendedUntil > Date.now();
|
|
5482
5515
|
}
|
|
5516
|
+
/** Epoch ms of the next probe while suspended; 0 otherwise. */
|
|
5517
|
+
get recheckAt() {
|
|
5518
|
+
return this.suspended ? this.suspendedUntil : 0;
|
|
5519
|
+
}
|
|
5483
5520
|
/** Probe again at once — the operator changed something. */
|
|
5484
5521
|
resetBackoff() {
|
|
5485
5522
|
this.suspendedUntil = 0;
|
|
5523
|
+
this.first404At = 0;
|
|
5524
|
+
this.logSuspensionEnded("reset by a settings change");
|
|
5525
|
+
}
|
|
5526
|
+
logSuspensionEnded(why) {
|
|
5527
|
+
if (!this.suspensionLogged) return;
|
|
5528
|
+
this.suspensionLogged = false;
|
|
5529
|
+
this.logger.info(`ha-export: push link no longer suspended (${why}), probing again`, { meta: { brokerId: this.brokerId } });
|
|
5486
5530
|
}
|
|
5487
5531
|
/** Messages discarded because HA did not answer. Never silent. */
|
|
5488
5532
|
get droppedCount() {
|
|
@@ -5563,8 +5607,13 @@ var PushClient = class {
|
|
|
5563
5607
|
this.dropped += message.type === "batch" ? message.items.length : 1;
|
|
5564
5608
|
return false;
|
|
5565
5609
|
}
|
|
5610
|
+
if (this.suspendedUntil !== 0 && !this.suspended) {
|
|
5611
|
+
this.suspendedUntil = 0;
|
|
5612
|
+
this.logSuspensionEnded("recheck time reached");
|
|
5613
|
+
}
|
|
5566
5614
|
try {
|
|
5567
5615
|
await this.transport(message);
|
|
5616
|
+
this.first404At = 0;
|
|
5568
5617
|
this.onReachable();
|
|
5569
5618
|
return true;
|
|
5570
5619
|
} catch (err) {
|
|
@@ -5591,6 +5640,7 @@ var PushClient = class {
|
|
|
5591
5640
|
this.available = true;
|
|
5592
5641
|
this.lastFailure = null;
|
|
5593
5642
|
this.suspendedUntil = 0;
|
|
5643
|
+
this.logSuspensionEnded("Home Assistant answered");
|
|
5594
5644
|
/**
|
|
5595
5645
|
* The link came back. Drop the dedup cache so the reconcile that
|
|
5596
5646
|
* follows re-pushes every value: HA may have restarted and lost its
|
|
@@ -5628,11 +5678,29 @@ var PushClient = class {
|
|
|
5628
5678
|
*/
|
|
5629
5679
|
onUnreachable(message, err) {
|
|
5630
5680
|
this.available = false;
|
|
5631
|
-
const
|
|
5681
|
+
const now = Date.now();
|
|
5682
|
+
const described = describePushFailure(err);
|
|
5683
|
+
let failure = described;
|
|
5684
|
+
if (described.kind === "component-missing") {
|
|
5685
|
+
if (this.first404At === 0) this.first404At = now;
|
|
5686
|
+
if (now - this.first404At < 6e4) failure = {
|
|
5687
|
+
kind: "other",
|
|
5688
|
+
permanent: false,
|
|
5689
|
+
text: "HTTP 404 — Home Assistant has not registered the CamStack endpoint (yet); retrying"
|
|
5690
|
+
};
|
|
5691
|
+
} else this.first404At = 0;
|
|
5632
5692
|
this.lastFailure = failure;
|
|
5633
|
-
if (failure.permanent) this.suspendedUntil = Date.now() + COMPONENT_RECHECK_MS;
|
|
5634
5693
|
this.dropped += message.type === "batch" ? message.items.length : 1;
|
|
5635
|
-
|
|
5694
|
+
if (failure.permanent) {
|
|
5695
|
+
this.suspendedUntil = now + COMPONENT_RECHECK_MS;
|
|
5696
|
+
this.suspensionLogged = true;
|
|
5697
|
+
this.logger.warn(`ha-export: push link SUSPENDED (${failure.text}); next probe at ${new Date(this.suspendedUntil).toISOString()}`, { meta: {
|
|
5698
|
+
brokerId: this.brokerId,
|
|
5699
|
+
failureKind: failure.kind,
|
|
5700
|
+
recheckAt: this.suspendedUntil,
|
|
5701
|
+
recheckInMs: COMPONENT_RECHECK_MS
|
|
5702
|
+
} });
|
|
5703
|
+
}
|
|
5636
5704
|
if (now - this.droppedLoggedAt < DROP_LOG_INTERVAL_MS) return;
|
|
5637
5705
|
this.droppedLoggedAt = now;
|
|
5638
5706
|
this.logger.warn(`ha-export: push to Home Assistant failed (${failure.text}), messages dropped`, { meta: {
|
|
@@ -5644,6 +5712,19 @@ var PushClient = class {
|
|
|
5644
5712
|
} });
|
|
5645
5713
|
}
|
|
5646
5714
|
};
|
|
5715
|
+
//#endregion
|
|
5716
|
+
//#region src/ha-export/link-gaps.ts
|
|
5717
|
+
/** A parked link is reported, never silently excluded. */
|
|
5718
|
+
function summariseLinkGaps(links) {
|
|
5719
|
+
return {
|
|
5720
|
+
failing: links.filter((l) => !l.healthy && !l.suspended).map((l) => l.brokerId),
|
|
5721
|
+
suspended: links.filter((l) => l.suspended).map((l) => ({
|
|
5722
|
+
brokerId: l.brokerId,
|
|
5723
|
+
recheckAt: l.recheckAt
|
|
5724
|
+
})),
|
|
5725
|
+
anyLive: links.some((l) => !l.suspended)
|
|
5726
|
+
};
|
|
5727
|
+
}
|
|
5647
5728
|
var ReachabilityReconciler = class {
|
|
5648
5729
|
options;
|
|
5649
5730
|
minIntervalMs;
|
|
@@ -8115,13 +8196,21 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
8115
8196
|
const delivered = await this.announce(exported, fullResync);
|
|
8116
8197
|
await this.pushFullState(exported, snapshots);
|
|
8117
8198
|
if (synthetic !== null) await this.pushSynthetic(projectSynthetic(synthetic, Date.now()), [...enabled]);
|
|
8118
|
-
const
|
|
8119
|
-
|
|
8120
|
-
|
|
8199
|
+
const gaps = summariseLinkGaps([...this.links.entries()].map(([brokerId, link]) => ({
|
|
8200
|
+
brokerId,
|
|
8201
|
+
healthy: link.client.healthy,
|
|
8202
|
+
suspended: link.client.suspended,
|
|
8203
|
+
recheckAt: link.client.recheckAt
|
|
8204
|
+
})));
|
|
8205
|
+
if (gaps.failing.length > 0 || !delivered && gaps.anyLive) {
|
|
8121
8206
|
this.resync.markGap("push-not-delivered");
|
|
8122
8207
|
this.lastStructureFingerprint = void 0;
|
|
8123
8208
|
this.ctx.logger.warn("ha-export: a push was not delivered, the next pass is a full resync");
|
|
8124
8209
|
}
|
|
8210
|
+
if (gaps.suspended.length > 0) this.ctx.logger.warn("ha-export: push links suspended, Home Assistant receives nothing", { meta: { suspended: gaps.suspended.map((s) => ({
|
|
8211
|
+
brokerId: s.brokerId,
|
|
8212
|
+
recheckAt: new Date(s.recheckAt).toISOString()
|
|
8213
|
+
})) } });
|
|
8125
8214
|
this.ctx.logger.info("ha-export: reconciled", { meta: {
|
|
8126
8215
|
reason,
|
|
8127
8216
|
fullResync,
|
|
@@ -8129,6 +8218,7 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
8129
8218
|
devices: exported.size,
|
|
8130
8219
|
entities: entityCount,
|
|
8131
8220
|
unclassifiedCaps: this.unclassified.length,
|
|
8221
|
+
suspendedLinks: gaps.suspended.length,
|
|
8132
8222
|
durationMs: Date.now() - startedAt
|
|
8133
8223
|
} });
|
|
8134
8224
|
if (this.unclassified.length > 0) this.ctx.logger.warn("ha-export: capabilities in neither the map nor the exclusion list", { meta: { caps: this.unclassified } });
|
|
@@ -9168,9 +9258,23 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9168
9258
|
reply.send({ error: `unroutable command (${resolution.reason})` });
|
|
9169
9259
|
return;
|
|
9170
9260
|
}
|
|
9171
|
-
const
|
|
9172
|
-
|
|
9173
|
-
|
|
9261
|
+
const outcome = await this.dispatch(resolution.command);
|
|
9262
|
+
if (outcome.ok) {
|
|
9263
|
+
reply.status(200);
|
|
9264
|
+
reply.send({});
|
|
9265
|
+
return;
|
|
9266
|
+
}
|
|
9267
|
+
this.ctx.logger.warn("ha-export: command answered 422", {
|
|
9268
|
+
tags: { deviceId: exported.deviceId },
|
|
9269
|
+
meta: {
|
|
9270
|
+
topic,
|
|
9271
|
+
entity: parsed.entity,
|
|
9272
|
+
reason: outcome.reason,
|
|
9273
|
+
message: outcome.message
|
|
9274
|
+
}
|
|
9275
|
+
});
|
|
9276
|
+
reply.status(422);
|
|
9277
|
+
reply.send(commandNotAppliedBody(outcome));
|
|
9174
9278
|
}
|
|
9175
9279
|
async dispatch(command) {
|
|
9176
9280
|
try {
|
|
@@ -9184,7 +9288,7 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9184
9288
|
*/
|
|
9185
9289
|
if (isCapCommand(command)) {
|
|
9186
9290
|
const outcome = await applyCapCommand(await this.ctx.fetchDevice(command.deviceId), command);
|
|
9187
|
-
if (outcome.ok) return
|
|
9291
|
+
if (outcome.ok) return APPLIED;
|
|
9188
9292
|
if (outcome.reason === "no-provider") return this.noProvider(command.deviceId, outcome.capName);
|
|
9189
9293
|
this.ctx.logger.warn("ha-export: the device refused the command", {
|
|
9190
9294
|
tags: { deviceId: command.deviceId },
|
|
@@ -9194,7 +9298,11 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9194
9298
|
...outcome.error !== void 0 ? { error: outcome.error } : {}
|
|
9195
9299
|
}
|
|
9196
9300
|
});
|
|
9197
|
-
return
|
|
9301
|
+
return {
|
|
9302
|
+
ok: false,
|
|
9303
|
+
reason: outcome.reason,
|
|
9304
|
+
...outcome.error !== void 0 ? { message: outcome.error } : {}
|
|
9305
|
+
};
|
|
9198
9306
|
}
|
|
9199
9307
|
switch (command.kind) {
|
|
9200
9308
|
case "camera-function": {
|
|
@@ -9204,7 +9312,11 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9204
9312
|
tags: { deviceId: command.deviceId },
|
|
9205
9313
|
meta: { switchId: command.switchId }
|
|
9206
9314
|
});
|
|
9207
|
-
return
|
|
9315
|
+
return {
|
|
9316
|
+
ok: false,
|
|
9317
|
+
reason: "unknown-function",
|
|
9318
|
+
message: "the switch id is not a camera function"
|
|
9319
|
+
};
|
|
9208
9320
|
}
|
|
9209
9321
|
/**
|
|
9210
9322
|
* A FRESH read for this one device — the pass-level caches are up to
|
|
@@ -9214,7 +9326,11 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9214
9326
|
const device = await this.ctx.api.deviceManager.getDevice.query({ deviceId: command.deviceId });
|
|
9215
9327
|
if (device === null) {
|
|
9216
9328
|
this.ctx.logger.warn("ha-export: dropping a camera-function command, the device is not in the registry", { tags: { deviceId: command.deviceId } });
|
|
9217
|
-
return
|
|
9329
|
+
return {
|
|
9330
|
+
ok: false,
|
|
9331
|
+
reason: "device-not-found",
|
|
9332
|
+
message: "the device is not in the registry"
|
|
9333
|
+
};
|
|
9218
9334
|
}
|
|
9219
9335
|
const privacySlice = await this.ctx.api.deviceState.getCapSlice.query({
|
|
9220
9336
|
deviceId: command.deviceId,
|
|
@@ -9250,22 +9366,26 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9250
9366
|
tags: { deviceId: command.deviceId },
|
|
9251
9367
|
meta: { switchId: parsed.data }
|
|
9252
9368
|
});
|
|
9253
|
-
return
|
|
9369
|
+
return {
|
|
9370
|
+
ok: false,
|
|
9371
|
+
reason: "function-unavailable",
|
|
9372
|
+
message: "the camera function is not available"
|
|
9373
|
+
};
|
|
9254
9374
|
}
|
|
9255
9375
|
await applyCameraFunctionWrite(this.ctx.api, command.deviceId, write, sourceNodeId);
|
|
9256
|
-
return
|
|
9376
|
+
return APPLIED;
|
|
9257
9377
|
}
|
|
9258
9378
|
case "reboot": {
|
|
9259
9379
|
const device = await this.ctx.fetchDevice(command.deviceId);
|
|
9260
9380
|
if (device.reboot === void 0) return this.noProvider(command.deviceId, "reboot");
|
|
9261
9381
|
await device.reboot.reboot({});
|
|
9262
|
-
return
|
|
9382
|
+
return APPLIED;
|
|
9263
9383
|
}
|
|
9264
9384
|
case "ptz-move": {
|
|
9265
9385
|
const device = await this.ctx.fetchDevice(command.deviceId);
|
|
9266
9386
|
if (device.ptz === void 0) return this.noProvider(command.deviceId, "ptz");
|
|
9267
9387
|
await device.ptz.move(PTZ_MOVE_BY_DIRECTION[command.direction] ?? {});
|
|
9268
|
-
return
|
|
9388
|
+
return APPLIED;
|
|
9269
9389
|
}
|
|
9270
9390
|
case "ptz-preset": {
|
|
9271
9391
|
const device = await this.ctx.fetchDevice(command.deviceId);
|
|
@@ -9276,10 +9396,14 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9276
9396
|
tags: { deviceId: command.deviceId },
|
|
9277
9397
|
meta: { preset: command.preset }
|
|
9278
9398
|
});
|
|
9279
|
-
return
|
|
9399
|
+
return {
|
|
9400
|
+
ok: false,
|
|
9401
|
+
reason: "no-such-preset",
|
|
9402
|
+
message: "no preset by that name"
|
|
9403
|
+
};
|
|
9280
9404
|
}
|
|
9281
9405
|
await device.ptz.goToPreset({ presetId: preset.id });
|
|
9282
|
-
return
|
|
9406
|
+
return APPLIED;
|
|
9283
9407
|
}
|
|
9284
9408
|
}
|
|
9285
9409
|
} catch (err) {
|
|
@@ -9295,7 +9419,11 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9295
9419
|
error: errMsg(err)
|
|
9296
9420
|
}
|
|
9297
9421
|
});
|
|
9298
|
-
return
|
|
9422
|
+
return {
|
|
9423
|
+
ok: false,
|
|
9424
|
+
reason: "error",
|
|
9425
|
+
message: errMsg(err)
|
|
9426
|
+
};
|
|
9299
9427
|
}
|
|
9300
9428
|
}
|
|
9301
9429
|
/** A cap the device does not provide. Refused, and never silently. */
|
|
@@ -9304,7 +9432,11 @@ var HaExportAddon = class extends require_ha_broker_source.BaseAddon {
|
|
|
9304
9432
|
tags: { deviceId },
|
|
9305
9433
|
meta: { capName }
|
|
9306
9434
|
});
|
|
9307
|
-
return
|
|
9435
|
+
return {
|
|
9436
|
+
ok: false,
|
|
9437
|
+
reason: "no-provider",
|
|
9438
|
+
message: `the device has no provider for ${capName}`
|
|
9439
|
+
};
|
|
9308
9440
|
}
|
|
9309
9441
|
/**
|
|
9310
9442
|
* A public, signed, expiring route Home Assistant can fetch bytes from.
|