@camstack/addon-provider-homeassistant 1.2.142 → 1.2.144

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-1gj-XZ-R.js");
6
+ const require_dist = require("../dist-BwQbmKRC.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  //#region src/ha-export/topics.ts
9
9
  /**
@@ -2872,6 +2872,10 @@ var CAPS_NOT_EXPORTED = [
2872
2872
  cap: "videoclips",
2873
2873
  reason: "Media retrieval by handle, not a state. Clips reach the operator through notifications and the viewer."
2874
2874
  },
2875
+ {
2876
+ cap: "recording",
2877
+ reason: "Since D625 this is the per-camera COVERAGE and PLAYBACK collection: which sources a camera has recordings in, what each covers over a window, and how to play an instant of it. Every method takes a window and a chosen source, so nothing here is a state a descriptor could publish — an entity would have to invent both. What an operator actually wants in Home Assistant is \"is this camera recording\", and that is `RecordingConfig.enabled` on `recording-archive`, which the camera-function switches already export as the ONE authority (D62). A second entity over this cap would be the second knob that decision forbids."
2878
+ },
2875
2879
  {
2876
2880
  cap: "webrtc-session",
2877
2881
  reason: "A session opener — the definition of transport."
@@ -4038,8 +4042,8 @@ async function applyCameraFunctionWrite(api, deviceId, write, sourceNodeId) {
4038
4042
  });
4039
4043
  return;
4040
4044
  case "recording-enabled": {
4041
- const config = await api.recording.getDeviceConfig.query({ deviceId });
4042
- await api.recording.setDeviceConfig.mutate({
4045
+ const config = await api.recordingArchive.getDeviceConfig.query({ deviceId });
4046
+ await api.recordingArchive.setDeviceConfig.mutate({
4043
4047
  deviceId,
4044
4048
  config: {
4045
4049
  ...config,
@@ -4934,9 +4938,11 @@ function projectMotion(deviceKey, input) {
4934
4938
  /**
4935
4939
  * A clearing edge carries no timestamp. `last_detection` answers "when
4936
4940
  * did something last happen", and restamping it when nothing happened
4937
- * makes an idle camera look permanently busy.
4941
+ * makes an idle camera look permanently busy. Same for a detection whose
4942
+ * timestamp is unknown or implausible — omitted rather than published as
4943
+ * a lie the automation author would trust.
4938
4944
  */
4939
- if (input.detected) values.push({
4945
+ if (input.detected && input.timestamp !== null) values.push({
4940
4946
  topic: stateTopic(deviceKey, "motion_last_detection"),
4941
4947
  value: iso(input.timestamp)
4942
4948
  });
@@ -5926,6 +5932,13 @@ var PUSH_PATH = "/api/camstack/push";
5926
5932
  var MEDIA_ROUTE_PREFIX = `/addon/${ADDON_ID}/ha-media`;
5927
5933
  /** How long `doorbell_pressed` stays on before its OFF edge is published. */
5928
5934
  var DOORBELL_PULSE_MS = 5e3;
5935
+ /**
5936
+ * The earliest timestamp a real change could plausibly carry. Below this,
5937
+ * a slice field is a placeholder or a seeded "known value, unknown change
5938
+ * time" stamp (e.g. Homematic's `HM_SEEDED_CHANGED_AT = 1`) — never a real
5939
+ * instant. Year 2000 predates every device this addon exports.
5940
+ */
5941
+ var MIN_PLAUSIBLE_TIMESTAMP_MS = Date.UTC(2e3, 0, 1);
5929
5942
  /** The hub's API port — where the data plane is reverse-proxied. */
5930
5943
  var HUB_API_PORT = 4443;
5931
5944
  /** One PTZ button press, as a relative move. */
@@ -6228,9 +6241,10 @@ var HaExportAddon = class extends require_dist.BaseAddon {
6228
6241
  const detected = slice["detected"];
6229
6242
  const lastDetectedAt = readNumber(slice, "lastDetectedAt");
6230
6243
  if (typeof detected !== "boolean") return;
6244
+ const timestamp = lastDetectedAt === null ? Date.now() : lastDetectedAt >= MIN_PLAUSIBLE_TIMESTAMP_MS ? lastDetectedAt : null;
6231
6245
  this.push(deviceId, projectMotion(deviceKey, {
6232
6246
  detected,
6233
- timestamp: lastDetectedAt ?? Date.now()
6247
+ timestamp
6234
6248
  }));
6235
6249
  }
6236
6250
  }
@@ -6858,7 +6872,7 @@ var HaExportAddon = class extends require_dist.BaseAddon {
6858
6872
  * that is not read stays `unknown` — unread, never failed.
6859
6873
  */
6860
6874
  async buildSyntheticNodes(topology) {
6861
- const usage = await this.ctx.api.recording.getStorageUsage.query({}).catch((err) => {
6875
+ const usage = await this.ctx.api.recordingArchive.getStorageUsage.query({}).catch((err) => {
6862
6876
  this.ctx.logger.warn("ha-export: could not read storage usage, the footage entities go unknown", { meta: { error: errMsg(err) } });
6863
6877
  return null;
6864
6878
  });
@@ -7202,7 +7216,7 @@ var HaExportAddon = class extends require_dist.BaseAddon {
7202
7216
  });
7203
7217
  return null;
7204
7218
  });
7205
- const recordingEnabled = await this.ctx.api.recording.getDeviceConfig.query({ deviceId }).then((config) => config.enabled).catch((err) => {
7219
+ const recordingEnabled = await this.ctx.api.recordingArchive.getDeviceConfig.query({ deviceId }).then((config) => config.enabled).catch((err) => {
7206
7220
  this.ctx.logger.warn("ha-export: could not read recording config, the recording function degrades", {
7207
7221
  tags: { deviceId },
7208
7222
  meta: { error: errMsg(err) }
@@ -1,4 +1,4 @@
1
- import { C as buildAddonRouteProvider, F as deviceExportCapability, Nt as EventCategory, Q as oauthIntegrationCapability, a as COCO_TO_MACRO, c as FanDirectionSchema, gt as BaseAddon, i as CAMERA_SWITCH_ORDER, kt as string, l as HvacModeSchema, o as CameraSwitchIdSchema, p as addonRoutesCapability, t as AlarmArmModeSchema, u as MediaPlayerRepeatSchema, xt as nodePin } from "../dist-DbcraPOl.mjs";
1
+ import { C as buildAddonRouteProvider, F as deviceExportCapability, Nt as EventCategory, Q as oauthIntegrationCapability, a as COCO_TO_MACRO, c as FanDirectionSchema, gt as BaseAddon, i as CAMERA_SWITCH_ORDER, kt as string, l as HvacModeSchema, o as CameraSwitchIdSchema, p as addonRoutesCapability, t as AlarmArmModeSchema, u as MediaPlayerRepeatSchema, xt as nodePin } from "../dist-CzKKAmb9.mjs";
2
2
  import { createHmac, timingSafeEqual } from "node:crypto";
3
3
  //#region src/ha-export/topics.ts
4
4
  /**
@@ -2867,6 +2867,10 @@ var CAPS_NOT_EXPORTED = [
2867
2867
  cap: "videoclips",
2868
2868
  reason: "Media retrieval by handle, not a state. Clips reach the operator through notifications and the viewer."
2869
2869
  },
2870
+ {
2871
+ cap: "recording",
2872
+ reason: "Since D625 this is the per-camera COVERAGE and PLAYBACK collection: which sources a camera has recordings in, what each covers over a window, and how to play an instant of it. Every method takes a window and a chosen source, so nothing here is a state a descriptor could publish — an entity would have to invent both. What an operator actually wants in Home Assistant is \"is this camera recording\", and that is `RecordingConfig.enabled` on `recording-archive`, which the camera-function switches already export as the ONE authority (D62). A second entity over this cap would be the second knob that decision forbids."
2873
+ },
2870
2874
  {
2871
2875
  cap: "webrtc-session",
2872
2876
  reason: "A session opener — the definition of transport."
@@ -4033,8 +4037,8 @@ async function applyCameraFunctionWrite(api, deviceId, write, sourceNodeId) {
4033
4037
  });
4034
4038
  return;
4035
4039
  case "recording-enabled": {
4036
- const config = await api.recording.getDeviceConfig.query({ deviceId });
4037
- await api.recording.setDeviceConfig.mutate({
4040
+ const config = await api.recordingArchive.getDeviceConfig.query({ deviceId });
4041
+ await api.recordingArchive.setDeviceConfig.mutate({
4038
4042
  deviceId,
4039
4043
  config: {
4040
4044
  ...config,
@@ -4929,9 +4933,11 @@ function projectMotion(deviceKey, input) {
4929
4933
  /**
4930
4934
  * A clearing edge carries no timestamp. `last_detection` answers "when
4931
4935
  * did something last happen", and restamping it when nothing happened
4932
- * makes an idle camera look permanently busy.
4936
+ * makes an idle camera look permanently busy. Same for a detection whose
4937
+ * timestamp is unknown or implausible — omitted rather than published as
4938
+ * a lie the automation author would trust.
4933
4939
  */
4934
- if (input.detected) values.push({
4940
+ if (input.detected && input.timestamp !== null) values.push({
4935
4941
  topic: stateTopic(deviceKey, "motion_last_detection"),
4936
4942
  value: iso(input.timestamp)
4937
4943
  });
@@ -5921,6 +5927,13 @@ var PUSH_PATH = "/api/camstack/push";
5921
5927
  var MEDIA_ROUTE_PREFIX = `/addon/${ADDON_ID}/ha-media`;
5922
5928
  /** How long `doorbell_pressed` stays on before its OFF edge is published. */
5923
5929
  var DOORBELL_PULSE_MS = 5e3;
5930
+ /**
5931
+ * The earliest timestamp a real change could plausibly carry. Below this,
5932
+ * a slice field is a placeholder or a seeded "known value, unknown change
5933
+ * time" stamp (e.g. Homematic's `HM_SEEDED_CHANGED_AT = 1`) — never a real
5934
+ * instant. Year 2000 predates every device this addon exports.
5935
+ */
5936
+ var MIN_PLAUSIBLE_TIMESTAMP_MS = Date.UTC(2e3, 0, 1);
5924
5937
  /** The hub's API port — where the data plane is reverse-proxied. */
5925
5938
  var HUB_API_PORT = 4443;
5926
5939
  /** One PTZ button press, as a relative move. */
@@ -6223,9 +6236,10 @@ var HaExportAddon = class extends BaseAddon {
6223
6236
  const detected = slice["detected"];
6224
6237
  const lastDetectedAt = readNumber(slice, "lastDetectedAt");
6225
6238
  if (typeof detected !== "boolean") return;
6239
+ const timestamp = lastDetectedAt === null ? Date.now() : lastDetectedAt >= MIN_PLAUSIBLE_TIMESTAMP_MS ? lastDetectedAt : null;
6226
6240
  this.push(deviceId, projectMotion(deviceKey, {
6227
6241
  detected,
6228
- timestamp: lastDetectedAt ?? Date.now()
6242
+ timestamp
6229
6243
  }));
6230
6244
  }
6231
6245
  }
@@ -6853,7 +6867,7 @@ var HaExportAddon = class extends BaseAddon {
6853
6867
  * that is not read stays `unknown` — unread, never failed.
6854
6868
  */
6855
6869
  async buildSyntheticNodes(topology) {
6856
- const usage = await this.ctx.api.recording.getStorageUsage.query({}).catch((err) => {
6870
+ const usage = await this.ctx.api.recordingArchive.getStorageUsage.query({}).catch((err) => {
6857
6871
  this.ctx.logger.warn("ha-export: could not read storage usage, the footage entities go unknown", { meta: { error: errMsg(err) } });
6858
6872
  return null;
6859
6873
  });
@@ -7197,7 +7211,7 @@ var HaExportAddon = class extends BaseAddon {
7197
7211
  });
7198
7212
  return null;
7199
7213
  });
7200
- const recordingEnabled = await this.ctx.api.recording.getDeviceConfig.query({ deviceId }).then((config) => config.enabled).catch((err) => {
7214
+ const recordingEnabled = await this.ctx.api.recordingArchive.getDeviceConfig.query({ deviceId }).then((config) => config.enabled).catch((err) => {
7201
7215
  this.ctx.logger.warn("ha-export: could not read recording config, the recording function degrades", {
7202
7216
  tags: { deviceId },
7203
7217
  meta: { error: errMsg(err) }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-homeassistant",
3
- "version": "1.2.142",
3
+ "version": "1.2.144",
4
4
  "description": "Home Assistant device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",