@camstack/addon-provider-homeassistant 1.2.636 → 1.2.638
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 +18 -0
- package/dist/addon.js +1 -1
- package/dist/addon.mjs +1 -1
- package/dist/{dist-BS9DCd76.js → dist-C_XoeULr.js} +1388 -721
- package/dist/{dist-BusXPPvn.mjs → dist-Cvz76AD1.mjs} +1383 -734
- package/dist/ha-export/ha-export.addon.js +300 -3
- package/dist/ha-export/ha-export.addon.mjs +300 -3
- package/package.json +3 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { $t as object, Bt as baseUnitOf, Gt as isReadingQuantity, I as consumableFormOf, Jt as array, Kt as nodePin, L as consumableKindOf, O as buildAddonRouteProvider, Rt as BaseAddon, U as deviceExportCapability, a as COCO_TO_MACRO, an as EventCategory, c as ConsumableItemSchema, d as FanDirectionSchema, f as HvacModeSchema, ht as runExportedAction, i as CAMERA_SWITCH_ORDER, l as ConsumableStatusSchema, m as PLUG_APPLIANCE_TYPES, o as CameraSwitchIdSchema, p as MediaPlayerRepeatSchema, pt as pruneDeletedEntities, rn as unknown, s as ConsumableFormSchema, st as oauthIntegrationCapability, t as AlarmArmModeSchema, tn as string, v as addonRoutesCapability, xt as sendToExportedTarget } from "../dist-Cvz76AD1.mjs";
|
|
2
2
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
3
3
|
import "node:zlib";
|
|
4
4
|
import "node:fs";
|
|
@@ -557,6 +557,7 @@ var KNOWN_PLATFORMS = new Set([
|
|
|
557
557
|
"camera",
|
|
558
558
|
"alarm_control_panel",
|
|
559
559
|
"number",
|
|
560
|
+
"notify",
|
|
560
561
|
"cover",
|
|
561
562
|
"climate",
|
|
562
563
|
"lock",
|
|
@@ -3453,6 +3454,10 @@ function controlEntitySpecs(slice, quantity) {
|
|
|
3453
3454
|
}, kindExtra];
|
|
3454
3455
|
}
|
|
3455
3456
|
var CAPS_NOT_EXPORTED = [
|
|
3457
|
+
{
|
|
3458
|
+
cap: "pet-fountain",
|
|
3459
|
+
reason: "A fountain is BUILT here from the loose entities Home Assistant already holds (a pump switch, two number boxes, a button), so exporting it back would publish a second copy of entities that already exist there. A dedicated mapping can follow when an ecosystem asks for the built device."
|
|
3460
|
+
},
|
|
3456
3461
|
{
|
|
3457
3462
|
cap: "ir-transmitter",
|
|
3458
3463
|
reason: "A pulse-train emitter with no state: it is driven by the remote that owns a command table, and a raw send has no meaningful single entity."
|
|
@@ -5652,7 +5657,8 @@ function renderCapValue(mapping, raw) {
|
|
|
5652
5657
|
case "alarm_control_panel":
|
|
5653
5658
|
case "image":
|
|
5654
5659
|
case "camera": return typeof raw === "string" ? raw : null;
|
|
5655
|
-
case "button":
|
|
5660
|
+
case "button":
|
|
5661
|
+
case "notify": return null;
|
|
5656
5662
|
case "cover":
|
|
5657
5663
|
case "climate":
|
|
5658
5664
|
case "lock":
|
|
@@ -6609,7 +6615,8 @@ function projectSynthetic(input, nowMs) {
|
|
|
6609
6615
|
}, {
|
|
6610
6616
|
topic: stateTopic(srv, `${slug}_cpu`),
|
|
6611
6617
|
value: String(Math.round(node.cpuPercent))
|
|
6612
|
-
}
|
|
6618
|
+
});
|
|
6619
|
+
if (node.memoryPercent !== null) values.push({
|
|
6613
6620
|
topic: stateTopic(srv, `${slug}_memory`),
|
|
6614
6621
|
value: String(Math.round(node.memoryPercent))
|
|
6615
6622
|
});
|
|
@@ -6639,6 +6646,225 @@ function projectSynthetic(input, nowMs) {
|
|
|
6639
6646
|
return values;
|
|
6640
6647
|
}
|
|
6641
6648
|
//#endregion
|
|
6649
|
+
//#region src/ha-export/exported-entities.ts
|
|
6650
|
+
/**
|
|
6651
|
+
* Actions and notification targets exported to Home Assistant (D968).
|
|
6652
|
+
*
|
|
6653
|
+
* An action is a `button`: pressing it runs the action through `actions.run`.
|
|
6654
|
+
* A notification target is a `notify` entity: a message sent to it goes out
|
|
6655
|
+
* through `notification-output.send` to that target. Both live on ONE
|
|
6656
|
+
* synthetic device — they are list entries, never camstack devices (D954), so
|
|
6657
|
+
* nothing here touches the device registry or the membership store.
|
|
6658
|
+
*
|
|
6659
|
+
* The selection is this addon's own setting. The class holds it, validates an
|
|
6660
|
+
* entry against the live lists, prunes what the operator deleted (a failed
|
|
6661
|
+
* read changes nothing, D49), and answers the commands Home Assistant sends
|
|
6662
|
+
* back. The addon supplies `persist` and `reconcile`, so the lifecycle is
|
|
6663
|
+
* testable without an Home Assistant.
|
|
6664
|
+
*
|
|
6665
|
+
* `notify` is a platform the custom component must BUILD (see
|
|
6666
|
+
* `component-support.ts`): against a component that does not, the targets are
|
|
6667
|
+
* withheld from the plan and the caller logs which ones, by name — an entity
|
|
6668
|
+
* announced to a component that drops it would read as "exported nothing".
|
|
6669
|
+
*/
|
|
6670
|
+
/** The stable id of the synthetic device that carries them. A WIRE FORMAT. */
|
|
6671
|
+
var EXPORTED_ENTITIES_STABLE_ID = "synthetic-exported-entities";
|
|
6672
|
+
var EXPORTER = "homeassistant-export";
|
|
6673
|
+
/** `action_<slug>` — derived from the action ID, so a rename keeps its entity. */
|
|
6674
|
+
function actionEntity(actionId) {
|
|
6675
|
+
return `action_${toSlug(actionId)}`;
|
|
6676
|
+
}
|
|
6677
|
+
/** `notify_<slug>` — derived from the target ID. */
|
|
6678
|
+
function targetEntity(targetId) {
|
|
6679
|
+
return `notify_${toSlug(targetId)}`;
|
|
6680
|
+
}
|
|
6681
|
+
function errMsg$1(err) {
|
|
6682
|
+
return err instanceof Error ? err.message : String(err);
|
|
6683
|
+
}
|
|
6684
|
+
var ExportedEntities = class {
|
|
6685
|
+
ports;
|
|
6686
|
+
state;
|
|
6687
|
+
constructor(ports, initial) {
|
|
6688
|
+
this.ports = ports;
|
|
6689
|
+
this.state = initial;
|
|
6690
|
+
}
|
|
6691
|
+
/** The cap's `listExposedEntities` rows for actions and notifiers. */
|
|
6692
|
+
list() {
|
|
6693
|
+
return [...this.state.actions.map((e) => ({
|
|
6694
|
+
entity: {
|
|
6695
|
+
kind: "action",
|
|
6696
|
+
actionId: e.actionId
|
|
6697
|
+
},
|
|
6698
|
+
exposedAs: e.displayName
|
|
6699
|
+
})), ...this.state.targets.map((e) => ({
|
|
6700
|
+
entity: {
|
|
6701
|
+
kind: "notifier",
|
|
6702
|
+
targetId: e.targetId
|
|
6703
|
+
},
|
|
6704
|
+
exposedAs: e.displayName
|
|
6705
|
+
}))];
|
|
6706
|
+
}
|
|
6707
|
+
async expose(entity) {
|
|
6708
|
+
if (entity.kind === "device") throw new Error("homeassistant-export: a device is not an action or a notification target");
|
|
6709
|
+
if (entity.kind === "action") {
|
|
6710
|
+
const action = (await this.ports.api.actions.list.query({})).find((a) => a.id === entity.actionId);
|
|
6711
|
+
if (action === void 0) throw new Error(`homeassistant-export: action '${entity.actionId}' does not exist`);
|
|
6712
|
+
const previous = this.state.actions.find((e) => e.actionId === entity.actionId);
|
|
6713
|
+
const entry = {
|
|
6714
|
+
actionId: entity.actionId,
|
|
6715
|
+
displayName: action.name,
|
|
6716
|
+
addedAt: previous?.addedAt ?? Date.now()
|
|
6717
|
+
};
|
|
6718
|
+
await this.save({
|
|
6719
|
+
...this.state,
|
|
6720
|
+
actions: [...this.state.actions.filter((e) => e.actionId !== entity.actionId), entry]
|
|
6721
|
+
});
|
|
6722
|
+
this.ports.logger.info("homeassistant-export: exposed action", {
|
|
6723
|
+
tags: { actionId: entity.actionId },
|
|
6724
|
+
meta: { displayName: action.name }
|
|
6725
|
+
});
|
|
6726
|
+
} else {
|
|
6727
|
+
const target = (await this.ports.api.notificationOutput.listTargets.query({})).find((t) => t.id === entity.targetId);
|
|
6728
|
+
if (target === void 0) throw new Error(`homeassistant-export: notification target '${entity.targetId}' does not exist`);
|
|
6729
|
+
const previous = this.state.targets.find((e) => e.targetId === entity.targetId);
|
|
6730
|
+
const entry = {
|
|
6731
|
+
targetId: entity.targetId,
|
|
6732
|
+
displayName: target.name,
|
|
6733
|
+
addedAt: previous?.addedAt ?? Date.now()
|
|
6734
|
+
};
|
|
6735
|
+
await this.save({
|
|
6736
|
+
...this.state,
|
|
6737
|
+
targets: [...this.state.targets.filter((e) => e.targetId !== entity.targetId), entry]
|
|
6738
|
+
});
|
|
6739
|
+
this.ports.logger.info("homeassistant-export: exposed notification target", {
|
|
6740
|
+
tags: { targetId: entity.targetId },
|
|
6741
|
+
meta: { displayName: target.name }
|
|
6742
|
+
});
|
|
6743
|
+
}
|
|
6744
|
+
await this.ports.reconcile();
|
|
6745
|
+
}
|
|
6746
|
+
async unexpose(entity) {
|
|
6747
|
+
if (entity.kind === "device") throw new Error("homeassistant-export: a device is not an action or a notification target");
|
|
6748
|
+
const next = entity.kind === "action" ? {
|
|
6749
|
+
...this.state,
|
|
6750
|
+
actions: this.state.actions.filter((e) => e.actionId !== entity.actionId)
|
|
6751
|
+
} : {
|
|
6752
|
+
...this.state,
|
|
6753
|
+
targets: this.state.targets.filter((e) => e.targetId !== entity.targetId)
|
|
6754
|
+
};
|
|
6755
|
+
await this.save(next);
|
|
6756
|
+
await this.ports.reconcile();
|
|
6757
|
+
}
|
|
6758
|
+
/**
|
|
6759
|
+
* Drop what the operator deleted, logging each drop. A list that could not
|
|
6760
|
+
* be read changes nothing: a failed read must never destroy the selection.
|
|
6761
|
+
*/
|
|
6762
|
+
async pruneDeleted() {
|
|
6763
|
+
if (this.state.actions.length === 0 && this.state.targets.length === 0) return;
|
|
6764
|
+
let actionIds;
|
|
6765
|
+
let targetIds;
|
|
6766
|
+
try {
|
|
6767
|
+
const [actions, targets] = await Promise.all([this.ports.api.actions.list.query({}), this.ports.api.notificationOutput.listTargets.query({})]);
|
|
6768
|
+
actionIds = new Set(actions.map((a) => a.id));
|
|
6769
|
+
targetIds = new Set(targets.map((t) => t.id));
|
|
6770
|
+
} catch (err) {
|
|
6771
|
+
this.ports.logger.warn("homeassistant-export: exported entities not reconciled — a list was unanswered", { meta: { error: errMsg$1(err) } });
|
|
6772
|
+
return;
|
|
6773
|
+
}
|
|
6774
|
+
const { dropped } = pruneDeletedEntities([...this.state.actions.map((e) => ({
|
|
6775
|
+
kind: "action",
|
|
6776
|
+
actionId: e.actionId
|
|
6777
|
+
})), ...this.state.targets.map((e) => ({
|
|
6778
|
+
kind: "notifier",
|
|
6779
|
+
targetId: e.targetId
|
|
6780
|
+
}))], {
|
|
6781
|
+
actionIds,
|
|
6782
|
+
targetIds
|
|
6783
|
+
}, this.ports.logger, EXPORTER);
|
|
6784
|
+
if (dropped.length === 0) return;
|
|
6785
|
+
const goneActions = new Set(dropped.flatMap((r) => r.kind === "action" ? [r.actionId] : []));
|
|
6786
|
+
const goneTargets = new Set(dropped.flatMap((r) => r.kind === "notifier" ? [r.targetId] : []));
|
|
6787
|
+
await this.save({
|
|
6788
|
+
actions: this.state.actions.filter((e) => !goneActions.has(e.actionId)),
|
|
6789
|
+
targets: this.state.targets.filter((e) => !goneTargets.has(e.targetId))
|
|
6790
|
+
});
|
|
6791
|
+
}
|
|
6792
|
+
/** The synthetic device the selection announces. Null when nothing is exported. */
|
|
6793
|
+
plan(support) {
|
|
6794
|
+
const deviceKey = syntheticDeviceKeyFor(EXPORTED_ENTITIES_STABLE_ID);
|
|
6795
|
+
const cmps = {};
|
|
6796
|
+
for (const e of this.state.actions) {
|
|
6797
|
+
const entity = actionEntity(e.actionId);
|
|
6798
|
+
cmps[toComponentKey("button", entity)] = {
|
|
6799
|
+
platform: "button",
|
|
6800
|
+
unique_id: `${EXPORTED_ENTITIES_STABLE_ID}_${entity}`,
|
|
6801
|
+
name: e.displayName,
|
|
6802
|
+
command_topic: commandTopic(deviceKey, entity),
|
|
6803
|
+
payload_press: "PRESS",
|
|
6804
|
+
icon: "mdi:play-circle-outline"
|
|
6805
|
+
};
|
|
6806
|
+
}
|
|
6807
|
+
const withheld = [];
|
|
6808
|
+
for (const e of this.state.targets) {
|
|
6809
|
+
if (!support.has("notify")) {
|
|
6810
|
+
withheld.push(e.targetId);
|
|
6811
|
+
continue;
|
|
6812
|
+
}
|
|
6813
|
+
const entity = targetEntity(e.targetId);
|
|
6814
|
+
cmps[toComponentKey("notify", entity)] = {
|
|
6815
|
+
platform: "notify",
|
|
6816
|
+
unique_id: `${EXPORTED_ENTITIES_STABLE_ID}_${entity}`,
|
|
6817
|
+
name: e.displayName,
|
|
6818
|
+
command_topic: commandTopic(deviceKey, entity),
|
|
6819
|
+
icon: "mdi:message-text-outline"
|
|
6820
|
+
};
|
|
6821
|
+
}
|
|
6822
|
+
if (Object.keys(cmps).length === 0) return {
|
|
6823
|
+
plan: null,
|
|
6824
|
+
withheld
|
|
6825
|
+
};
|
|
6826
|
+
return {
|
|
6827
|
+
plan: {
|
|
6828
|
+
deviceKey,
|
|
6829
|
+
deviceId: null,
|
|
6830
|
+
dev: {
|
|
6831
|
+
ids: [deviceKey],
|
|
6832
|
+
name: "CamStack Actions and Notifications",
|
|
6833
|
+
mf: "CamStack",
|
|
6834
|
+
mdl: "Synthetic"
|
|
6835
|
+
},
|
|
6836
|
+
cmps
|
|
6837
|
+
},
|
|
6838
|
+
withheld
|
|
6839
|
+
};
|
|
6840
|
+
}
|
|
6841
|
+
/** Answer a command Home Assistant sent for this device. */
|
|
6842
|
+
async handleCommand(entity, value) {
|
|
6843
|
+
const action = this.state.actions.find((e) => actionEntity(e.actionId) === entity);
|
|
6844
|
+
if (action !== void 0) return {
|
|
6845
|
+
handled: true,
|
|
6846
|
+
applied: (await runExportedAction(this.ports.api, this.ports.logger, {
|
|
6847
|
+
exporter: EXPORTER,
|
|
6848
|
+
actionId: action.actionId
|
|
6849
|
+
})).status === "started"
|
|
6850
|
+
};
|
|
6851
|
+
const target = this.state.targets.find((e) => targetEntity(e.targetId) === entity);
|
|
6852
|
+
if (target !== void 0) return {
|
|
6853
|
+
handled: true,
|
|
6854
|
+
applied: (await sendToExportedTarget(this.ports.api, this.ports.logger, {
|
|
6855
|
+
exporter: EXPORTER,
|
|
6856
|
+
targetId: target.targetId,
|
|
6857
|
+
notification: { body: value }
|
|
6858
|
+
})).status === "sent"
|
|
6859
|
+
};
|
|
6860
|
+
return { handled: false };
|
|
6861
|
+
}
|
|
6862
|
+
async save(next) {
|
|
6863
|
+
this.state = next;
|
|
6864
|
+
await this.ports.persist(next);
|
|
6865
|
+
}
|
|
6866
|
+
};
|
|
6867
|
+
//#endregion
|
|
6642
6868
|
//#region src/ha-export/supported-device-kinds.ts
|
|
6643
6869
|
/**
|
|
6644
6870
|
* Device types the Home Assistant export picker offers.
|
|
@@ -6743,6 +6969,10 @@ var DEFAULT_CONFIG = {
|
|
|
6743
6969
|
reconcileIntervalSec: 300,
|
|
6744
6970
|
membership: {},
|
|
6745
6971
|
knownBrokers: [],
|
|
6972
|
+
exportedEntities: {
|
|
6973
|
+
actions: [],
|
|
6974
|
+
targets: []
|
|
6975
|
+
},
|
|
6746
6976
|
publicBaseUrl: ""
|
|
6747
6977
|
};
|
|
6748
6978
|
var HaExportAddon = class extends BaseAddon {
|
|
@@ -6814,6 +7044,8 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
6814
7044
|
* whenever the synthetic devices are, never written to.
|
|
6815
7045
|
*/
|
|
6816
7046
|
syntheticRules = [];
|
|
7047
|
+
/** D968: the actions and notification targets exported beside the devices. */
|
|
7048
|
+
exportedEntities = null;
|
|
6817
7049
|
/**
|
|
6818
7050
|
* `pipelineOrchestrator.getCameraStatuses`, keyed by deviceId — ONE call
|
|
6819
7051
|
* per reconcile pass for every exported camera (Task C3), never per
|
|
@@ -6874,6 +7106,13 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
6874
7106
|
for (const link of this.links.values()) await link.client.dispose();
|
|
6875
7107
|
this.links.clear();
|
|
6876
7108
|
});
|
|
7109
|
+
const exportedEntities = new ExportedEntities({
|
|
7110
|
+
api: this.ctx.api,
|
|
7111
|
+
logger: this.ctx.logger,
|
|
7112
|
+
persist: (next) => this.updateGlobalSettings({ exportedEntities: next }),
|
|
7113
|
+
reconcile: () => this.reconcile("exported-entities-changed")
|
|
7114
|
+
}, this.config.exportedEntities ?? DEFAULT_CONFIG.exportedEntities);
|
|
7115
|
+
this.exportedEntities = exportedEntities;
|
|
6877
7116
|
return [
|
|
6878
7117
|
{
|
|
6879
7118
|
capability: deviceExportCapability,
|
|
@@ -6886,6 +7125,23 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
6886
7125
|
})),
|
|
6887
7126
|
exposeDevice: async ({ deviceId }) => this.setMembershipEverywhere(deviceId, true),
|
|
6888
7127
|
unexposeDevice: async ({ deviceId }) => this.setMembershipEverywhere(deviceId, false),
|
|
7128
|
+
listSupportedEntityKinds: async () => [
|
|
7129
|
+
"device",
|
|
7130
|
+
"action",
|
|
7131
|
+
"notifier"
|
|
7132
|
+
],
|
|
7133
|
+
listExposedEntities: async () => {
|
|
7134
|
+
await exportedEntities.pruneDeleted();
|
|
7135
|
+
return [...exposedDeviceIds(this.config.membership, this.enabledBrokerIds()).map((deviceId) => ({
|
|
7136
|
+
entity: {
|
|
7137
|
+
kind: "device",
|
|
7138
|
+
deviceId
|
|
7139
|
+
},
|
|
7140
|
+
exposedAs: brokersExposing(this.config.membership, deviceId).map((brokerId) => this.brokerName(brokerId)).join(", ")
|
|
7141
|
+
})), ...exportedEntities.list()];
|
|
7142
|
+
},
|
|
7143
|
+
exposeEntity: async ({ entity }) => entity.kind === "device" ? this.setMembershipEverywhere(entity.deviceId, true) : exportedEntities.expose(entity),
|
|
7144
|
+
unexposeEntity: async ({ entity }) => entity.kind === "device" ? this.setMembershipEverywhere(entity.deviceId, false) : exportedEntities.unexpose(entity),
|
|
6889
7145
|
getDeviceSettingsContribution: async ({ deviceId }) => this.buildContribution(deviceId),
|
|
6890
7146
|
getDeviceLiveContribution: async () => null,
|
|
6891
7147
|
applyDeviceSettingsPatch: async ({ deviceId, patch }) => this.applyDeviceSettingsPatch(deviceId, patch)
|
|
@@ -7523,6 +7779,32 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
7523
7779
|
* `brokerIds` is every enabled broker, because they belong to the server
|
|
7524
7780
|
* and not to a membership the operator picks per camera.
|
|
7525
7781
|
*/
|
|
7782
|
+
/**
|
|
7783
|
+
* The exported actions and notification targets (D968): one more
|
|
7784
|
+
* synthetic device. What the operator deleted is dropped first (a failed
|
|
7785
|
+
* read changes nothing); a target the component cannot build as `notify`
|
|
7786
|
+
* is named, not silently lost.
|
|
7787
|
+
*/
|
|
7788
|
+
if (this.exportedEntities !== null) {
|
|
7789
|
+
await this.exportedEntities.pruneDeleted();
|
|
7790
|
+
const { plan: entitiesPlan, withheld } = this.exportedEntities.plan(this.componentSupport.supportForAll([...enabled]));
|
|
7791
|
+
if (withheld.length > 0) this.ctx.logger.warn("ha-export: notification targets withheld — the Home Assistant component does not build notify entities yet", { meta: { targetIds: withheld } });
|
|
7792
|
+
if (entitiesPlan !== null) {
|
|
7793
|
+
exported.set(entitiesPlan.deviceKey, {
|
|
7794
|
+
plan: entitiesPlan,
|
|
7795
|
+
deviceId: -1,
|
|
7796
|
+
target: {
|
|
7797
|
+
deviceId: -1,
|
|
7798
|
+
type: "synthetic",
|
|
7799
|
+
boundCaps: []
|
|
7800
|
+
},
|
|
7801
|
+
brokerIds: [...enabled],
|
|
7802
|
+
streams: [],
|
|
7803
|
+
cameraFunctions: null
|
|
7804
|
+
});
|
|
7805
|
+
entityCount += Object.keys(entitiesPlan.cmps).length;
|
|
7806
|
+
}
|
|
7807
|
+
}
|
|
7526
7808
|
const synthetic = await this.buildSynthetic(byId);
|
|
7527
7809
|
if (synthetic !== null) for (const plan of syntheticPlans(synthetic)) {
|
|
7528
7810
|
exported.set(plan.deviceKey, {
|
|
@@ -8545,6 +8827,21 @@ var HaExportAddon = class extends BaseAddon {
|
|
|
8545
8827
|
* `setRuleEnabled`, never a store of its own. A second knob that can
|
|
8546
8828
|
* disagree with the admin UI is worse than no knob (D62).
|
|
8547
8829
|
*/
|
|
8830
|
+
if (this.exportedEntities !== null && parsed.deviceKey === syntheticDeviceKeyFor("synthetic-exported-entities")) {
|
|
8831
|
+
const outcome = await this.exportedEntities.handleCommand(parsed.entity, value);
|
|
8832
|
+
if (!outcome.handled) {
|
|
8833
|
+
this.ctx.logger.warn("ha-export: dropping a command for an entity that is not exported", { meta: {
|
|
8834
|
+
topic,
|
|
8835
|
+
entity: parsed.entity
|
|
8836
|
+
} });
|
|
8837
|
+
reply.status(422);
|
|
8838
|
+
reply.send({ error: "entity not exported" });
|
|
8839
|
+
return;
|
|
8840
|
+
}
|
|
8841
|
+
reply.status(outcome.applied ? 200 : 422);
|
|
8842
|
+
reply.send(outcome.applied ? {} : { error: "command not applied" });
|
|
8843
|
+
return;
|
|
8844
|
+
}
|
|
8548
8845
|
if (isSyntheticDeviceKey(parsed.deviceKey)) {
|
|
8549
8846
|
const ruleId = ruleIdFromEntity(parsed.entity, this.syntheticRules);
|
|
8550
8847
|
if (ruleId === null) {
|
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.638",
|
|
4
4
|
"description": "Home Assistant device provider addon for CamStack",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
@@ -106,7 +106,8 @@
|
|
|
106
106
|
"icon": "assets/icon.svg",
|
|
107
107
|
"color": "#41bdf5",
|
|
108
108
|
"execution": {
|
|
109
|
-
"placement": "hub-only"
|
|
109
|
+
"placement": "hub-only",
|
|
110
|
+
"group": "integrations"
|
|
110
111
|
},
|
|
111
112
|
"capabilities": [
|
|
112
113
|
{
|