@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as consumableKindOf, Bt as BaseAddon, Dt as sendToExportedTarget, Ht as baseUnitOf, Jt as isReadingQuantity, K as deviceExportCapability, Wt as defineAddonMigrations, Yt as nodePin, Zt as array, _ as PLUG_APPLIANCE_TYPES, _t as pruneDeletedEntities, c as COCO_TO_MACRO, d as ConsumableItemSchema, f as ConsumableStatusSchema, fn as EventCategory, g as MediaPlayerRepeatSchema, h as HvacModeSchema, i as AlarmArmModeSchema, in as string, j as buildAddonRouteProvider, l as CameraSwitchIdSchema, m as FanDirectionSchema, nn as object, on as unknown, pt as preconditionOf, r as listHomeAssistantBrokers, s as CAMERA_SWITCH_ORDER, u as ConsumableFormSchema, ut as oauthIntegrationCapability, x as addonRoutesCapability, xt as runExportedAction, z as consumableFormOf } from "../ha-broker-source-
|
|
1
|
+
import { B as consumableKindOf, Bt as BaseAddon, Dt as sendToExportedTarget, Ht as baseUnitOf, Jt as isReadingQuantity, K as deviceExportCapability, Wt as defineAddonMigrations, Yt as nodePin, Zt as array, _ as PLUG_APPLIANCE_TYPES, _t as pruneDeletedEntities, c as COCO_TO_MACRO, d as ConsumableItemSchema, f as ConsumableStatusSchema, fn as EventCategory, g as MediaPlayerRepeatSchema, h as HvacModeSchema, i as AlarmArmModeSchema, in as string, j as buildAddonRouteProvider, l as CameraSwitchIdSchema, m as FanDirectionSchema, nn as object, on as unknown, pt as preconditionOf, r as listHomeAssistantBrokers, s as CAMERA_SWITCH_ORDER, u as ConsumableFormSchema, ut as oauthIntegrationCapability, x as addonRoutesCapability, xt as runExportedAction, z as consumableFormOf } from "../ha-broker-source-CCmZ9I6P.mjs";
|
|
2
2
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
3
3
|
import "node:zlib";
|
|
4
4
|
import "node:fs";
|
|
@@ -940,19 +940,22 @@ var NATIVE_CAP_PLATFORMS = {
|
|
|
940
940
|
*/
|
|
941
941
|
cover: {
|
|
942
942
|
platform: "cover",
|
|
943
|
+
availableWhenOnline: true,
|
|
943
944
|
primary: { from: null },
|
|
944
945
|
controls: {
|
|
945
946
|
position: {
|
|
946
947
|
from: "position",
|
|
947
948
|
min: 0,
|
|
948
949
|
max: 100,
|
|
949
|
-
step: 1
|
|
950
|
+
step: 1,
|
|
951
|
+
requiresFeature: "cover-positionable"
|
|
950
952
|
},
|
|
951
953
|
tilt: {
|
|
952
954
|
from: "tilt",
|
|
953
955
|
min: 0,
|
|
954
956
|
max: 100,
|
|
955
|
-
step: 1
|
|
957
|
+
step: 1,
|
|
958
|
+
requiresFeature: "cover-tilt"
|
|
956
959
|
},
|
|
957
960
|
open: {
|
|
958
961
|
from: "open",
|
|
@@ -964,7 +967,8 @@ var NATIVE_CAP_PLATFORMS = {
|
|
|
964
967
|
},
|
|
965
968
|
stop: {
|
|
966
969
|
from: "stop",
|
|
967
|
-
commandOnly: true
|
|
970
|
+
commandOnly: true,
|
|
971
|
+
requiresFeature: "cover-stoppable"
|
|
968
972
|
}
|
|
969
973
|
}
|
|
970
974
|
},
|
|
@@ -1353,9 +1357,11 @@ function buildNativeComponent(input) {
|
|
|
1353
1357
|
if (native === void 0) return null;
|
|
1354
1358
|
const primary = resolveBinding(input, native.primary);
|
|
1355
1359
|
if (primary === null) return null;
|
|
1360
|
+
const features = input.features ?? [];
|
|
1356
1361
|
const absorbed = new Set([derivedEntityId(input.capName), ...primary.entities]);
|
|
1357
1362
|
const controls = {};
|
|
1358
1363
|
for (const [name, binding] of Object.entries(native.controls ?? {})) {
|
|
1364
|
+
if (binding.requiresFeature !== void 0 && !features.includes(binding.requiresFeature)) continue;
|
|
1359
1365
|
const resolved = resolveBinding(input, binding);
|
|
1360
1366
|
if (resolved === null) continue;
|
|
1361
1367
|
controls[name] = resolved.control;
|
|
@@ -1374,6 +1380,11 @@ function buildNativeComponent(input) {
|
|
|
1374
1380
|
...primary.control,
|
|
1375
1381
|
...native.deviceClass !== void 0 ? { device_class: native.deviceClass } : {},
|
|
1376
1382
|
...native.icon !== void 0 ? { icon: native.icon } : {},
|
|
1383
|
+
...native.availableWhenOnline === true ? {
|
|
1384
|
+
availability_topic: stateTopic(input.deviceKey, "online"),
|
|
1385
|
+
payload_available: "true",
|
|
1386
|
+
payload_not_available: "false"
|
|
1387
|
+
} : {},
|
|
1377
1388
|
...Object.keys(controls).length > 0 ? { controls } : {},
|
|
1378
1389
|
...native.constants,
|
|
1379
1390
|
...flags
|
|
@@ -2308,7 +2319,8 @@ var CAP_ENTITY_MAP = {
|
|
|
2308
2319
|
unit: "%",
|
|
2309
2320
|
icon: "mdi:arrow-up-down",
|
|
2310
2321
|
writable: true,
|
|
2311
|
-
range: PERCENT_RANGE
|
|
2322
|
+
range: PERCENT_RANGE,
|
|
2323
|
+
requiresFeature: "cover-positionable"
|
|
2312
2324
|
},
|
|
2313
2325
|
tilt: {
|
|
2314
2326
|
platform: "number",
|
|
@@ -2318,7 +2330,8 @@ var CAP_ENTITY_MAP = {
|
|
|
2318
2330
|
icon: "mdi:angle-acute",
|
|
2319
2331
|
writable: true,
|
|
2320
2332
|
range: PERCENT_RANGE,
|
|
2321
|
-
enabledByDefault: false
|
|
2333
|
+
enabledByDefault: false,
|
|
2334
|
+
requiresFeature: "cover-tilt"
|
|
2322
2335
|
}
|
|
2323
2336
|
},
|
|
2324
2337
|
commands: {
|
|
@@ -2333,7 +2346,8 @@ var CAP_ENTITY_MAP = {
|
|
|
2333
2346
|
stop: {
|
|
2334
2347
|
label: "Stop",
|
|
2335
2348
|
icon: "mdi:stop",
|
|
2336
|
-
enabledByDefault: false
|
|
2349
|
+
enabledByDefault: false,
|
|
2350
|
+
requiresFeature: "cover-stoppable"
|
|
2337
2351
|
}
|
|
2338
2352
|
}
|
|
2339
2353
|
},
|
|
@@ -3340,7 +3354,8 @@ function capEntities(capName) {
|
|
|
3340
3354
|
for (const [suffix, extra] of Object.entries(mapping.extras ?? {})) refs.push({
|
|
3341
3355
|
entity: derivedExtraEntityId(capName, suffix),
|
|
3342
3356
|
label: extra.label,
|
|
3343
|
-
descriptor: extra
|
|
3357
|
+
descriptor: extra,
|
|
3358
|
+
...extra.requiresFeature !== void 0 ? { requiresFeature: extra.requiresFeature } : {}
|
|
3344
3359
|
});
|
|
3345
3360
|
return refs;
|
|
3346
3361
|
}
|
|
@@ -3359,8 +3374,9 @@ function deriveCapValue(derivation, raw) {
|
|
|
3359
3374
|
return derivation.kind === "equals" ? raw === derivation.value : raw !== derivation.value;
|
|
3360
3375
|
}
|
|
3361
3376
|
/** A capability's entities as the catalog builders consume them. */
|
|
3362
|
-
function capEntitySpecs(capName) {
|
|
3363
|
-
const
|
|
3377
|
+
function capEntitySpecs(capName, features = []) {
|
|
3378
|
+
const declared = (required) => required === void 0 || features.includes(required);
|
|
3379
|
+
const specs = capEntities(capName).filter((ref) => declared(ref.requiresFeature)).map((ref) => ({
|
|
3364
3380
|
entity: ref.entity,
|
|
3365
3381
|
platform: ref.descriptor.platform,
|
|
3366
3382
|
label: ref.label,
|
|
@@ -3373,15 +3389,18 @@ function capEntitySpecs(capName) {
|
|
|
3373
3389
|
...ref.descriptor.entityCategory !== void 0 ? { entityCategory: ref.descriptor.entityCategory } : {},
|
|
3374
3390
|
...ref.descriptor.writable === true ? { writable: true } : {}
|
|
3375
3391
|
}));
|
|
3376
|
-
for (const ref of capCommandEntities(capName))
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3392
|
+
for (const ref of capCommandEntities(capName)) {
|
|
3393
|
+
if (!declared(ref.command.requiresFeature)) continue;
|
|
3394
|
+
specs.push({
|
|
3395
|
+
entity: ref.entity,
|
|
3396
|
+
platform: "button",
|
|
3397
|
+
label: ref.label,
|
|
3398
|
+
enabledByDefault: ref.command.enabledByDefault ?? true,
|
|
3399
|
+
writable: true,
|
|
3400
|
+
...ref.command.icon !== void 0 ? { icon: ref.command.icon } : {},
|
|
3401
|
+
...ref.command.entityCategory !== void 0 ? { entityCategory: ref.command.entityCategory } : {}
|
|
3402
|
+
});
|
|
3403
|
+
}
|
|
3385
3404
|
return specs;
|
|
3386
3405
|
}
|
|
3387
3406
|
function readControlOptions(slice) {
|
|
@@ -3759,11 +3778,12 @@ function buildDerivedPlan(device, options = UNNEGOTIATED_PLAN_OPTIONS) {
|
|
|
3759
3778
|
deviceId: device.id,
|
|
3760
3779
|
capName: cap,
|
|
3761
3780
|
label: humanise(cap),
|
|
3781
|
+
features: device.features,
|
|
3762
3782
|
slice: device.capSlices?.[cap],
|
|
3763
3783
|
mapping
|
|
3764
3784
|
});
|
|
3765
3785
|
if (plan === null) {
|
|
3766
|
-
specs.push(...capEntitySpecs(cap).map((spec) => degradeUnsupported(spec, options)));
|
|
3786
|
+
specs.push(...capEntitySpecs(cap, device.features).map((spec) => degradeUnsupported(spec, options)));
|
|
3767
3787
|
continue;
|
|
3768
3788
|
}
|
|
3769
3789
|
native[toComponentKey(plan.component.platform, derivedEntityId(cap))] = plan.component;
|
|
@@ -3774,7 +3794,7 @@ function buildDerivedPlan(device, options = UNNEGOTIATED_PLAN_OPTIONS) {
|
|
|
3774
3794
|
* to make the device look tidy would be a silent loss of a value an
|
|
3775
3795
|
* operator already automates on.
|
|
3776
3796
|
*/
|
|
3777
|
-
specs.push(...capEntitySpecs(cap).filter((spec) => !plan.absorbed.has(spec.entity)));
|
|
3797
|
+
specs.push(...capEntitySpecs(cap, device.features).filter((spec) => !plan.absorbed.has(spec.entity)));
|
|
3778
3798
|
}
|
|
3779
3799
|
const assembled = assemble(device, specs);
|
|
3780
3800
|
return {
|
|
@@ -4636,6 +4656,15 @@ function isCameraCommand(command) {
|
|
|
4636
4656
|
function isCapCommand(command) {
|
|
4637
4657
|
return !isCameraCommand(command);
|
|
4638
4658
|
}
|
|
4659
|
+
var APPLIED = { ok: true };
|
|
4660
|
+
/** The 422 body of a command that was not applied: the reason travels with it. */
|
|
4661
|
+
function commandNotAppliedBody(refusal) {
|
|
4662
|
+
return {
|
|
4663
|
+
error: "command not applied",
|
|
4664
|
+
reason: refusal.reason,
|
|
4665
|
+
...refusal.message !== void 0 ? { message: refusal.message } : {}
|
|
4666
|
+
};
|
|
4667
|
+
}
|
|
4639
4668
|
/**
|
|
4640
4669
|
* Make the call the command names.
|
|
4641
4670
|
*
|
|
@@ -5427,6 +5456,10 @@ var PushClient = class {
|
|
|
5427
5456
|
disposed = false;
|
|
5428
5457
|
lastFailure = null;
|
|
5429
5458
|
suspendedUntil = 0;
|
|
5459
|
+
/** When the current unbroken streak of 404s began; 0 when there is none. */
|
|
5460
|
+
first404At = 0;
|
|
5461
|
+
/** A suspension was entered and not yet logged as left. */
|
|
5462
|
+
suspensionLogged = false;
|
|
5430
5463
|
constructor(options) {
|
|
5431
5464
|
this.transport = options.transport;
|
|
5432
5465
|
this.logger = options.logger;
|
|
@@ -5445,9 +5478,20 @@ var PushClient = class {
|
|
|
5445
5478
|
get suspended() {
|
|
5446
5479
|
return this.suspendedUntil > Date.now();
|
|
5447
5480
|
}
|
|
5481
|
+
/** Epoch ms of the next probe while suspended; 0 otherwise. */
|
|
5482
|
+
get recheckAt() {
|
|
5483
|
+
return this.suspended ? this.suspendedUntil : 0;
|
|
5484
|
+
}
|
|
5448
5485
|
/** Probe again at once — the operator changed something. */
|
|
5449
5486
|
resetBackoff() {
|
|
5450
5487
|
this.suspendedUntil = 0;
|
|
5488
|
+
this.first404At = 0;
|
|
5489
|
+
this.logSuspensionEnded("reset by a settings change");
|
|
5490
|
+
}
|
|
5491
|
+
logSuspensionEnded(why) {
|
|
5492
|
+
if (!this.suspensionLogged) return;
|
|
5493
|
+
this.suspensionLogged = false;
|
|
5494
|
+
this.logger.info(`ha-export: push link no longer suspended (${why}), probing again`, { meta: { brokerId: this.brokerId } });
|
|
5451
5495
|
}
|
|
5452
5496
|
/** Messages discarded because HA did not answer. Never silent. */
|
|
5453
5497
|
get droppedCount() {
|
|
@@ -5528,8 +5572,13 @@ var PushClient = class {
|
|
|
5528
5572
|
this.dropped += message.type === "batch" ? message.items.length : 1;
|
|
5529
5573
|
return false;
|
|
5530
5574
|
}
|
|
5575
|
+
if (this.suspendedUntil !== 0 && !this.suspended) {
|
|
5576
|
+
this.suspendedUntil = 0;
|
|
5577
|
+
this.logSuspensionEnded("recheck time reached");
|
|
5578
|
+
}
|
|
5531
5579
|
try {
|
|
5532
5580
|
await this.transport(message);
|
|
5581
|
+
this.first404At = 0;
|
|
5533
5582
|
this.onReachable();
|
|
5534
5583
|
return true;
|
|
5535
5584
|
} catch (err) {
|
|
@@ -5556,6 +5605,7 @@ var PushClient = class {
|
|
|
5556
5605
|
this.available = true;
|
|
5557
5606
|
this.lastFailure = null;
|
|
5558
5607
|
this.suspendedUntil = 0;
|
|
5608
|
+
this.logSuspensionEnded("Home Assistant answered");
|
|
5559
5609
|
/**
|
|
5560
5610
|
* The link came back. Drop the dedup cache so the reconcile that
|
|
5561
5611
|
* follows re-pushes every value: HA may have restarted and lost its
|
|
@@ -5593,11 +5643,29 @@ var PushClient = class {
|
|
|
5593
5643
|
*/
|
|
5594
5644
|
onUnreachable(message, err) {
|
|
5595
5645
|
this.available = false;
|
|
5596
|
-
const
|
|
5646
|
+
const now = Date.now();
|
|
5647
|
+
const described = describePushFailure(err);
|
|
5648
|
+
let failure = described;
|
|
5649
|
+
if (described.kind === "component-missing") {
|
|
5650
|
+
if (this.first404At === 0) this.first404At = now;
|
|
5651
|
+
if (now - this.first404At < 6e4) failure = {
|
|
5652
|
+
kind: "other",
|
|
5653
|
+
permanent: false,
|
|
5654
|
+
text: "HTTP 404 — Home Assistant has not registered the CamStack endpoint (yet); retrying"
|
|
5655
|
+
};
|
|
5656
|
+
} else this.first404At = 0;
|
|
5597
5657
|
this.lastFailure = failure;
|
|
5598
|
-
if (failure.permanent) this.suspendedUntil = Date.now() + COMPONENT_RECHECK_MS;
|
|
5599
5658
|
this.dropped += message.type === "batch" ? message.items.length : 1;
|
|
5600
|
-
|
|
5659
|
+
if (failure.permanent) {
|
|
5660
|
+
this.suspendedUntil = now + COMPONENT_RECHECK_MS;
|
|
5661
|
+
this.suspensionLogged = true;
|
|
5662
|
+
this.logger.warn(`ha-export: push link SUSPENDED (${failure.text}); next probe at ${new Date(this.suspendedUntil).toISOString()}`, { meta: {
|
|
5663
|
+
brokerId: this.brokerId,
|
|
5664
|
+
failureKind: failure.kind,
|
|
5665
|
+
recheckAt: this.suspendedUntil,
|
|
5666
|
+
recheckInMs: COMPONENT_RECHECK_MS
|
|
5667
|
+
} });
|
|
5668
|
+
}
|
|
5601
5669
|
if (now - this.droppedLoggedAt < DROP_LOG_INTERVAL_MS) return;
|
|
5602
5670
|
this.droppedLoggedAt = now;
|
|
5603
5671
|
this.logger.warn(`ha-export: push to Home Assistant failed (${failure.text}), messages dropped`, { meta: {
|
|
@@ -5609,6 +5677,19 @@ var PushClient = class {
|
|
|
5609
5677
|
} });
|
|
5610
5678
|
}
|
|
5611
5679
|
};
|
|
5680
|
+
//#endregion
|
|
5681
|
+
//#region src/ha-export/link-gaps.ts
|
|
5682
|
+
/** A parked link is reported, never silently excluded. */
|
|
5683
|
+
function summariseLinkGaps(links) {
|
|
5684
|
+
return {
|
|
5685
|
+
failing: links.filter((l) => !l.healthy && !l.suspended).map((l) => l.brokerId),
|
|
5686
|
+
suspended: links.filter((l) => l.suspended).map((l) => ({
|
|
5687
|
+
brokerId: l.brokerId,
|
|
5688
|
+
recheckAt: l.recheckAt
|
|
5689
|
+
})),
|
|
5690
|
+
anyLive: links.some((l) => !l.suspended)
|
|
5691
|
+
};
|
|
5692
|
+
}
|
|
5612
5693
|
var ReachabilityReconciler = class {
|
|
5613
5694
|
options;
|
|
5614
5695
|
minIntervalMs;
|
|
@@ -8080,13 +8161,21 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
8080
8161
|
const delivered = await this.announce(exported, fullResync);
|
|
8081
8162
|
await this.pushFullState(exported, snapshots);
|
|
8082
8163
|
if (synthetic !== null) await this.pushSynthetic(projectSynthetic(synthetic, Date.now()), [...enabled]);
|
|
8083
|
-
const
|
|
8084
|
-
|
|
8085
|
-
|
|
8164
|
+
const gaps = summariseLinkGaps([...this.links.entries()].map(([brokerId, link]) => ({
|
|
8165
|
+
brokerId,
|
|
8166
|
+
healthy: link.client.healthy,
|
|
8167
|
+
suspended: link.client.suspended,
|
|
8168
|
+
recheckAt: link.client.recheckAt
|
|
8169
|
+
})));
|
|
8170
|
+
if (gaps.failing.length > 0 || !delivered && gaps.anyLive) {
|
|
8086
8171
|
this.resync.markGap("push-not-delivered");
|
|
8087
8172
|
this.lastStructureFingerprint = void 0;
|
|
8088
8173
|
this.ctx.logger.warn("ha-export: a push was not delivered, the next pass is a full resync");
|
|
8089
8174
|
}
|
|
8175
|
+
if (gaps.suspended.length > 0) this.ctx.logger.warn("ha-export: push links suspended, Home Assistant receives nothing", { meta: { suspended: gaps.suspended.map((s) => ({
|
|
8176
|
+
brokerId: s.brokerId,
|
|
8177
|
+
recheckAt: new Date(s.recheckAt).toISOString()
|
|
8178
|
+
})) } });
|
|
8090
8179
|
this.ctx.logger.info("ha-export: reconciled", { meta: {
|
|
8091
8180
|
reason,
|
|
8092
8181
|
fullResync,
|
|
@@ -8094,6 +8183,7 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
8094
8183
|
devices: exported.size,
|
|
8095
8184
|
entities: entityCount,
|
|
8096
8185
|
unclassifiedCaps: this.unclassified.length,
|
|
8186
|
+
suspendedLinks: gaps.suspended.length,
|
|
8097
8187
|
durationMs: Date.now() - startedAt
|
|
8098
8188
|
} });
|
|
8099
8189
|
if (this.unclassified.length > 0) this.ctx.logger.warn("ha-export: capabilities in neither the map nor the exclusion list", { meta: { caps: this.unclassified } });
|
|
@@ -9133,9 +9223,23 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9133
9223
|
reply.send({ error: `unroutable command (${resolution.reason})` });
|
|
9134
9224
|
return;
|
|
9135
9225
|
}
|
|
9136
|
-
const
|
|
9137
|
-
|
|
9138
|
-
|
|
9226
|
+
const outcome = await this.dispatch(resolution.command);
|
|
9227
|
+
if (outcome.ok) {
|
|
9228
|
+
reply.status(200);
|
|
9229
|
+
reply.send({});
|
|
9230
|
+
return;
|
|
9231
|
+
}
|
|
9232
|
+
this.ctx.logger.warn("ha-export: command answered 422", {
|
|
9233
|
+
tags: { deviceId: exported.deviceId },
|
|
9234
|
+
meta: {
|
|
9235
|
+
topic,
|
|
9236
|
+
entity: parsed.entity,
|
|
9237
|
+
reason: outcome.reason,
|
|
9238
|
+
message: outcome.message
|
|
9239
|
+
}
|
|
9240
|
+
});
|
|
9241
|
+
reply.status(422);
|
|
9242
|
+
reply.send(commandNotAppliedBody(outcome));
|
|
9139
9243
|
}
|
|
9140
9244
|
async dispatch(command) {
|
|
9141
9245
|
try {
|
|
@@ -9149,7 +9253,7 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9149
9253
|
*/
|
|
9150
9254
|
if (isCapCommand(command)) {
|
|
9151
9255
|
const outcome = await applyCapCommand(await this.ctx.fetchDevice(command.deviceId), command);
|
|
9152
|
-
if (outcome.ok) return
|
|
9256
|
+
if (outcome.ok) return APPLIED;
|
|
9153
9257
|
if (outcome.reason === "no-provider") return this.noProvider(command.deviceId, outcome.capName);
|
|
9154
9258
|
this.ctx.logger.warn("ha-export: the device refused the command", {
|
|
9155
9259
|
tags: { deviceId: command.deviceId },
|
|
@@ -9159,7 +9263,11 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9159
9263
|
...outcome.error !== void 0 ? { error: outcome.error } : {}
|
|
9160
9264
|
}
|
|
9161
9265
|
});
|
|
9162
|
-
return
|
|
9266
|
+
return {
|
|
9267
|
+
ok: false,
|
|
9268
|
+
reason: outcome.reason,
|
|
9269
|
+
...outcome.error !== void 0 ? { message: outcome.error } : {}
|
|
9270
|
+
};
|
|
9163
9271
|
}
|
|
9164
9272
|
switch (command.kind) {
|
|
9165
9273
|
case "camera-function": {
|
|
@@ -9169,7 +9277,11 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9169
9277
|
tags: { deviceId: command.deviceId },
|
|
9170
9278
|
meta: { switchId: command.switchId }
|
|
9171
9279
|
});
|
|
9172
|
-
return
|
|
9280
|
+
return {
|
|
9281
|
+
ok: false,
|
|
9282
|
+
reason: "unknown-function",
|
|
9283
|
+
message: "the switch id is not a camera function"
|
|
9284
|
+
};
|
|
9173
9285
|
}
|
|
9174
9286
|
/**
|
|
9175
9287
|
* A FRESH read for this one device — the pass-level caches are up to
|
|
@@ -9179,7 +9291,11 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9179
9291
|
const device = await this.ctx.api.deviceManager.getDevice.query({ deviceId: command.deviceId });
|
|
9180
9292
|
if (device === null) {
|
|
9181
9293
|
this.ctx.logger.warn("ha-export: dropping a camera-function command, the device is not in the registry", { tags: { deviceId: command.deviceId } });
|
|
9182
|
-
return
|
|
9294
|
+
return {
|
|
9295
|
+
ok: false,
|
|
9296
|
+
reason: "device-not-found",
|
|
9297
|
+
message: "the device is not in the registry"
|
|
9298
|
+
};
|
|
9183
9299
|
}
|
|
9184
9300
|
const privacySlice = await this.ctx.api.deviceState.getCapSlice.query({
|
|
9185
9301
|
deviceId: command.deviceId,
|
|
@@ -9215,22 +9331,26 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9215
9331
|
tags: { deviceId: command.deviceId },
|
|
9216
9332
|
meta: { switchId: parsed.data }
|
|
9217
9333
|
});
|
|
9218
|
-
return
|
|
9334
|
+
return {
|
|
9335
|
+
ok: false,
|
|
9336
|
+
reason: "function-unavailable",
|
|
9337
|
+
message: "the camera function is not available"
|
|
9338
|
+
};
|
|
9219
9339
|
}
|
|
9220
9340
|
await applyCameraFunctionWrite(this.ctx.api, command.deviceId, write, sourceNodeId);
|
|
9221
|
-
return
|
|
9341
|
+
return APPLIED;
|
|
9222
9342
|
}
|
|
9223
9343
|
case "reboot": {
|
|
9224
9344
|
const device = await this.ctx.fetchDevice(command.deviceId);
|
|
9225
9345
|
if (device.reboot === void 0) return this.noProvider(command.deviceId, "reboot");
|
|
9226
9346
|
await device.reboot.reboot({});
|
|
9227
|
-
return
|
|
9347
|
+
return APPLIED;
|
|
9228
9348
|
}
|
|
9229
9349
|
case "ptz-move": {
|
|
9230
9350
|
const device = await this.ctx.fetchDevice(command.deviceId);
|
|
9231
9351
|
if (device.ptz === void 0) return this.noProvider(command.deviceId, "ptz");
|
|
9232
9352
|
await device.ptz.move(PTZ_MOVE_BY_DIRECTION[command.direction] ?? {});
|
|
9233
|
-
return
|
|
9353
|
+
return APPLIED;
|
|
9234
9354
|
}
|
|
9235
9355
|
case "ptz-preset": {
|
|
9236
9356
|
const device = await this.ctx.fetchDevice(command.deviceId);
|
|
@@ -9241,10 +9361,14 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9241
9361
|
tags: { deviceId: command.deviceId },
|
|
9242
9362
|
meta: { preset: command.preset }
|
|
9243
9363
|
});
|
|
9244
|
-
return
|
|
9364
|
+
return {
|
|
9365
|
+
ok: false,
|
|
9366
|
+
reason: "no-such-preset",
|
|
9367
|
+
message: "no preset by that name"
|
|
9368
|
+
};
|
|
9245
9369
|
}
|
|
9246
9370
|
await device.ptz.goToPreset({ presetId: preset.id });
|
|
9247
|
-
return
|
|
9371
|
+
return APPLIED;
|
|
9248
9372
|
}
|
|
9249
9373
|
}
|
|
9250
9374
|
} catch (err) {
|
|
@@ -9260,7 +9384,11 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9260
9384
|
error: errMsg(err)
|
|
9261
9385
|
}
|
|
9262
9386
|
});
|
|
9263
|
-
return
|
|
9387
|
+
return {
|
|
9388
|
+
ok: false,
|
|
9389
|
+
reason: "error",
|
|
9390
|
+
message: errMsg(err)
|
|
9391
|
+
};
|
|
9264
9392
|
}
|
|
9265
9393
|
}
|
|
9266
9394
|
/** A cap the device does not provide. Refused, and never silently. */
|
|
@@ -9269,7 +9397,11 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
9269
9397
|
tags: { deviceId },
|
|
9270
9398
|
meta: { capName }
|
|
9271
9399
|
});
|
|
9272
|
-
return
|
|
9400
|
+
return {
|
|
9401
|
+
ok: false,
|
|
9402
|
+
reason: "no-provider",
|
|
9403
|
+
message: `the device has no provider for ${capName}`
|
|
9404
|
+
};
|
|
9273
9405
|
}
|
|
9274
9406
|
/**
|
|
9275
9407
|
* A public, signed, expiring route Home Assistant can fetch bytes from.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-provider-homeassistant",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.651",
|
|
4
4
|
"description": "Home Assistant device provider addon for CamStack",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
"description": "Device provider (import) + native entity export (push). Two addons, one bundle, separate processes."
|
|
128
128
|
},
|
|
129
129
|
"requires": {
|
|
130
|
-
"server": ">=1.2.
|
|
130
|
+
"server": ">=1.2.741"
|
|
131
131
|
}
|
|
132
132
|
},
|
|
133
133
|
"files": [
|