@camstack/addon-provider-homeassistant 1.2.155 → 1.2.157

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.
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  //#endregion
6
- const require_dist = require("../dist-CERTsNmo.js");
6
+ const require_dist = require("../dist-M6hP_tw3.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  //#region src/ha-export/topics.ts
9
9
  /**
@@ -2582,13 +2582,18 @@ mireds: {
2582
2582
  },
2583
2583
  /**
2584
2584
  * The generic user-settable input. `value` is `number | string` and
2585
- * `kind` says which of the four shapes it is — a `number`, a `select`
2586
- * or a text box, all three of which would need `control.setValue` in
2587
- * the route table before they could be anything but a reading.
2585
+ * `kind` on the slice says which of the four shapes it is. The row
2586
+ * here is the PROJECTOR + route authority (`writable` gates
2587
+ * `control.setValue`); the announced platform (`select` / `number` /
2588
+ * read-only `sensor`) is minted at reconcile from the slice by
2589
+ * {@link controlEntitySpecs} — a static `select` here cannot carry
2590
+ * the device's live `options`, and a static `number` cannot carry
2591
+ * its live min/max/step.
2588
2592
  */
2589
2593
  control: {
2590
2594
  platform: "sensor",
2591
2595
  field: "value",
2596
+ writable: true,
2592
2597
  extras: { kind: {
2593
2598
  platform: "sensor",
2594
2599
  field: "kind",
@@ -2755,6 +2760,71 @@ function capEntitySpecs(capName) {
2755
2760
  });
2756
2761
  return specs;
2757
2762
  }
2763
+ function readControlOptions(slice) {
2764
+ const raw = slice?.["options"];
2765
+ if (!Array.isArray(raw)) return [];
2766
+ return raw.filter((entry) => typeof entry === "string" && entry.length > 0);
2767
+ }
2768
+ function readFiniteNumber(slice, field) {
2769
+ const raw = slice?.[field];
2770
+ return typeof raw === "number" && Number.isFinite(raw) ? raw : void 0;
2771
+ }
2772
+ /**
2773
+ * The announced shape of a `control` device, from its live slice.
2774
+ *
2775
+ * `CAP_ENTITY_MAP.control` cannot name a static `select`/`number`: the
2776
+ * options and the bounds live on the slice and change (VPN station list,
2777
+ * HA `input_number` limits). A select with no options is an entity HA
2778
+ * builds with nothing to choose from — so an empty list falls back to a
2779
+ * read-only sensor rather than a dead control.
2780
+ */
2781
+ function controlEntitySpecs(slice) {
2782
+ const kind = typeof slice?.["kind"] === "string" ? slice["kind"] : null;
2783
+ const primary = derivedEntityId("control");
2784
+ const kindExtra = {
2785
+ entity: derivedExtraEntityId("control", "kind"),
2786
+ platform: "sensor",
2787
+ label: "Input kind",
2788
+ entityCategory: "diagnostic",
2789
+ enabledByDefault: false
2790
+ };
2791
+ if (kind === "select") {
2792
+ const options = readControlOptions(slice);
2793
+ if (options.length > 0) return [{
2794
+ entity: primary,
2795
+ platform: "select",
2796
+ label: humanise("control"),
2797
+ writable: true,
2798
+ options,
2799
+ enabledByDefault: true
2800
+ }, kindExtra];
2801
+ }
2802
+ if (kind === "numeric") {
2803
+ const min = readFiniteNumber(slice, "min") ?? PERCENT_RANGE.min;
2804
+ const max = readFiniteNumber(slice, "max") ?? PERCENT_RANGE.max;
2805
+ const step = readFiniteNumber(slice, "step") ?? PERCENT_RANGE.step;
2806
+ const unit = typeof slice?.["unit"] === "string" ? slice["unit"] : void 0;
2807
+ if (min < max && step > 0) return [{
2808
+ entity: primary,
2809
+ platform: "number",
2810
+ label: humanise("control"),
2811
+ writable: true,
2812
+ range: {
2813
+ min,
2814
+ max,
2815
+ step
2816
+ },
2817
+ ...unit !== void 0 ? { unit } : {},
2818
+ enabledByDefault: true
2819
+ }, kindExtra];
2820
+ }
2821
+ return [{
2822
+ entity: primary,
2823
+ platform: "sensor",
2824
+ label: humanise("control"),
2825
+ enabledByDefault: true
2826
+ }, kindExtra];
2827
+ }
2758
2828
  var CAPS_NOT_EXPORTED = [
2759
2829
  {
2760
2830
  cap: "recording-onboard",
@@ -2984,6 +3054,15 @@ function buildDerivedPlan(device, options = UNNEGOTIATED_PLAN_OPTIONS) {
2984
3054
  * two halves of this file would build the same entity id twice.
2985
3055
  */
2986
3056
  if (CAMERA_DERIVED_CAPS.includes(cap)) continue;
3057
+ /**
3058
+ * `control` is slice-shaped: select options and number bounds are not
3059
+ * on the static row. Mint the announced platform here; the CAP_ENTITY_MAP
3060
+ * row stays the projector + writable-route authority.
3061
+ */
3062
+ if (cap === "control") {
3063
+ specs.push(...controlEntitySpecs(device.capSlices?.["control"]).map((spec) => degradeUnsupported(spec, options)));
3064
+ continue;
3065
+ }
2987
3066
  const mapping = CAP_ENTITY_MAP[cap];
2988
3067
  const platform = nativePlatformFor(cap);
2989
3068
  const plan = mapping === void 0 || platform === null || !options.platforms.has(platform) ? null : buildNativeComponent({
@@ -3207,6 +3286,11 @@ var CAP_COMMAND_ROUTES = {
3207
3286
  deviceId,
3208
3287
  capName: "button"
3209
3288
  }) },
3289
+ control: { primary: (deviceId, value) => value.trim().length === 0 ? null : {
3290
+ kind: "cap-control",
3291
+ deviceId,
3292
+ value
3293
+ } },
3210
3294
  cover: {
3211
3295
  extras: {
3212
3296
  position: (deviceId, value) => {
@@ -3895,6 +3979,28 @@ async function applyCapCommand(device, command) {
3895
3979
  };
3896
3980
  await device.button.press({});
3897
3981
  return { ok: true };
3982
+ case "cap-control": return call(device.control, "control", async (control) => {
3983
+ const status = await control.getStatus({});
3984
+ if (status === null || typeof status !== "object") throw new Error("the device reports no control status, so setValue has no kind");
3985
+ const kind = Reflect.get(status, "kind");
3986
+ if (kind === "numeric") {
3987
+ const numeric = Number(command.value);
3988
+ if (!Number.isFinite(numeric)) throw new Error(`control value is not a number: ${command.value}`);
3989
+ await control.setValue({ control: {
3990
+ kind: "numeric",
3991
+ value: numeric
3992
+ } });
3993
+ return;
3994
+ }
3995
+ if (kind === "select" || kind === "text" || kind === "datetime") {
3996
+ await control.setValue({ control: {
3997
+ kind,
3998
+ value: command.value
3999
+ } });
4000
+ return;
4001
+ }
4002
+ throw new Error(`unsupported control kind: ${String(kind)}`);
4003
+ });
3898
4004
  case "cover-position": return call(device.cover, "cover", (cover) => cover.setPosition({ position: command.position }));
3899
4005
  case "cover-tilt": return call(device.cover, "cover", (cover) => cover.setTiltPosition({ tiltPosition: command.tiltPosition }));
3900
4006
  case "cover-verb": return call(device.cover, "cover", (cover) => {
@@ -1,4 +1,4 @@
1
- import { $ as oauthIntegrationCapability, At as string, C as buildAddonRouteProvider, I as deviceExportCapability, Pt as EventCategory, St as nodePin, a as COCO_TO_MACRO, bt as BaseAddon, c as FanDirectionSchema, i as CAMERA_SWITCH_ORDER, l as HvacModeSchema, o as CameraSwitchIdSchema, p as addonRoutesCapability, t as AlarmArmModeSchema, u as MediaPlayerRepeatSchema } from "../dist-DqKiElqb.mjs";
1
+ import { $ as oauthIntegrationCapability, C as buildAddonRouteProvider, Ft as EventCategory, I as deviceExportCapability, St as nodePin, a as COCO_TO_MACRO, bt as BaseAddon, c as FanDirectionSchema, i as CAMERA_SWITCH_ORDER, jt as string, l as HvacModeSchema, o as CameraSwitchIdSchema, p as addonRoutesCapability, t as AlarmArmModeSchema, u as MediaPlayerRepeatSchema } from "../dist-BgTykMb6.mjs";
2
2
  import { createHmac, timingSafeEqual } from "node:crypto";
3
3
  //#region src/ha-export/topics.ts
4
4
  /**
@@ -2577,13 +2577,18 @@ mireds: {
2577
2577
  },
2578
2578
  /**
2579
2579
  * The generic user-settable input. `value` is `number | string` and
2580
- * `kind` says which of the four shapes it is — a `number`, a `select`
2581
- * or a text box, all three of which would need `control.setValue` in
2582
- * the route table before they could be anything but a reading.
2580
+ * `kind` on the slice says which of the four shapes it is. The row
2581
+ * here is the PROJECTOR + route authority (`writable` gates
2582
+ * `control.setValue`); the announced platform (`select` / `number` /
2583
+ * read-only `sensor`) is minted at reconcile from the slice by
2584
+ * {@link controlEntitySpecs} — a static `select` here cannot carry
2585
+ * the device's live `options`, and a static `number` cannot carry
2586
+ * its live min/max/step.
2583
2587
  */
2584
2588
  control: {
2585
2589
  platform: "sensor",
2586
2590
  field: "value",
2591
+ writable: true,
2587
2592
  extras: { kind: {
2588
2593
  platform: "sensor",
2589
2594
  field: "kind",
@@ -2750,6 +2755,71 @@ function capEntitySpecs(capName) {
2750
2755
  });
2751
2756
  return specs;
2752
2757
  }
2758
+ function readControlOptions(slice) {
2759
+ const raw = slice?.["options"];
2760
+ if (!Array.isArray(raw)) return [];
2761
+ return raw.filter((entry) => typeof entry === "string" && entry.length > 0);
2762
+ }
2763
+ function readFiniteNumber(slice, field) {
2764
+ const raw = slice?.[field];
2765
+ return typeof raw === "number" && Number.isFinite(raw) ? raw : void 0;
2766
+ }
2767
+ /**
2768
+ * The announced shape of a `control` device, from its live slice.
2769
+ *
2770
+ * `CAP_ENTITY_MAP.control` cannot name a static `select`/`number`: the
2771
+ * options and the bounds live on the slice and change (VPN station list,
2772
+ * HA `input_number` limits). A select with no options is an entity HA
2773
+ * builds with nothing to choose from — so an empty list falls back to a
2774
+ * read-only sensor rather than a dead control.
2775
+ */
2776
+ function controlEntitySpecs(slice) {
2777
+ const kind = typeof slice?.["kind"] === "string" ? slice["kind"] : null;
2778
+ const primary = derivedEntityId("control");
2779
+ const kindExtra = {
2780
+ entity: derivedExtraEntityId("control", "kind"),
2781
+ platform: "sensor",
2782
+ label: "Input kind",
2783
+ entityCategory: "diagnostic",
2784
+ enabledByDefault: false
2785
+ };
2786
+ if (kind === "select") {
2787
+ const options = readControlOptions(slice);
2788
+ if (options.length > 0) return [{
2789
+ entity: primary,
2790
+ platform: "select",
2791
+ label: humanise("control"),
2792
+ writable: true,
2793
+ options,
2794
+ enabledByDefault: true
2795
+ }, kindExtra];
2796
+ }
2797
+ if (kind === "numeric") {
2798
+ const min = readFiniteNumber(slice, "min") ?? PERCENT_RANGE.min;
2799
+ const max = readFiniteNumber(slice, "max") ?? PERCENT_RANGE.max;
2800
+ const step = readFiniteNumber(slice, "step") ?? PERCENT_RANGE.step;
2801
+ const unit = typeof slice?.["unit"] === "string" ? slice["unit"] : void 0;
2802
+ if (min < max && step > 0) return [{
2803
+ entity: primary,
2804
+ platform: "number",
2805
+ label: humanise("control"),
2806
+ writable: true,
2807
+ range: {
2808
+ min,
2809
+ max,
2810
+ step
2811
+ },
2812
+ ...unit !== void 0 ? { unit } : {},
2813
+ enabledByDefault: true
2814
+ }, kindExtra];
2815
+ }
2816
+ return [{
2817
+ entity: primary,
2818
+ platform: "sensor",
2819
+ label: humanise("control"),
2820
+ enabledByDefault: true
2821
+ }, kindExtra];
2822
+ }
2753
2823
  var CAPS_NOT_EXPORTED = [
2754
2824
  {
2755
2825
  cap: "recording-onboard",
@@ -2979,6 +3049,15 @@ function buildDerivedPlan(device, options = UNNEGOTIATED_PLAN_OPTIONS) {
2979
3049
  * two halves of this file would build the same entity id twice.
2980
3050
  */
2981
3051
  if (CAMERA_DERIVED_CAPS.includes(cap)) continue;
3052
+ /**
3053
+ * `control` is slice-shaped: select options and number bounds are not
3054
+ * on the static row. Mint the announced platform here; the CAP_ENTITY_MAP
3055
+ * row stays the projector + writable-route authority.
3056
+ */
3057
+ if (cap === "control") {
3058
+ specs.push(...controlEntitySpecs(device.capSlices?.["control"]).map((spec) => degradeUnsupported(spec, options)));
3059
+ continue;
3060
+ }
2982
3061
  const mapping = CAP_ENTITY_MAP[cap];
2983
3062
  const platform = nativePlatformFor(cap);
2984
3063
  const plan = mapping === void 0 || platform === null || !options.platforms.has(platform) ? null : buildNativeComponent({
@@ -3202,6 +3281,11 @@ var CAP_COMMAND_ROUTES = {
3202
3281
  deviceId,
3203
3282
  capName: "button"
3204
3283
  }) },
3284
+ control: { primary: (deviceId, value) => value.trim().length === 0 ? null : {
3285
+ kind: "cap-control",
3286
+ deviceId,
3287
+ value
3288
+ } },
3205
3289
  cover: {
3206
3290
  extras: {
3207
3291
  position: (deviceId, value) => {
@@ -3890,6 +3974,28 @@ async function applyCapCommand(device, command) {
3890
3974
  };
3891
3975
  await device.button.press({});
3892
3976
  return { ok: true };
3977
+ case "cap-control": return call(device.control, "control", async (control) => {
3978
+ const status = await control.getStatus({});
3979
+ if (status === null || typeof status !== "object") throw new Error("the device reports no control status, so setValue has no kind");
3980
+ const kind = Reflect.get(status, "kind");
3981
+ if (kind === "numeric") {
3982
+ const numeric = Number(command.value);
3983
+ if (!Number.isFinite(numeric)) throw new Error(`control value is not a number: ${command.value}`);
3984
+ await control.setValue({ control: {
3985
+ kind: "numeric",
3986
+ value: numeric
3987
+ } });
3988
+ return;
3989
+ }
3990
+ if (kind === "select" || kind === "text" || kind === "datetime") {
3991
+ await control.setValue({ control: {
3992
+ kind,
3993
+ value: command.value
3994
+ } });
3995
+ return;
3996
+ }
3997
+ throw new Error(`unsupported control kind: ${String(kind)}`);
3998
+ });
3893
3999
  case "cover-position": return call(device.cover, "cover", (cover) => cover.setPosition({ position: command.position }));
3894
4000
  case "cover-tilt": return call(device.cover, "cover", (cover) => cover.setTiltPosition({ tiltPosition: command.tiltPosition }));
3895
4001
  case "cover-verb": return call(device.cover, "cover", (cover) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-homeassistant",
3
- "version": "1.2.155",
3
+ "version": "1.2.157",
4
4
  "description": "Home Assistant device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",