@camstack/addon-provider-reolink 1.2.140 → 1.2.142

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.
Files changed (3) hide show
  1. package/dist/addon.js +206 -19
  2. package/dist/addon.mjs +206 -19
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -5386,7 +5386,7 @@ var ZodIssueCode = {
5386
5386
  var ZodFirstPartyTypeKind;
5387
5387
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5388
5388
  //#endregion
5389
- //#region ../types/dist/sleep-vl6nBE8d.mjs
5389
+ //#region ../types/dist/sleep-Bs3xPtSh.mjs
5390
5390
  /**
5391
5391
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5392
5392
  * window to float samples (D455).
@@ -12454,6 +12454,24 @@ var GetStreamWithCodecInputSchema = object({
12454
12454
  });
12455
12455
  var RtpSourceSchema = object({
12456
12456
  url: string(),
12457
+ /**
12458
+ * The SENTRY counterpart of {@link url} — the dial that asks for the stream
12459
+ * WITHOUT asking for the camera — or `null` when this camera offers none.
12460
+ *
12461
+ * The broker answers it because the broker is the only thing that can:
12462
+ * eligibility is `sentryEligible` (a BATTERY camera that DECLARES the signals
12463
+ * cap), asked of facts only the broker holds. A consumer re-deriving it from
12464
+ * cap bindings would be a second authority, and two authorities on the same
12465
+ * question is how a composite camera ends up dialling a sleeping camera
12466
+ * because its own copy of the rule was a release behind.
12467
+ *
12468
+ * `null` is a real answer and is never a live url in disguise: a consumer
12469
+ * that silently fell back to {@link url} would perform exactly the wake this
12470
+ * field exists to prevent. Only a PASSTHROUGH acquisition can offer one — a
12471
+ * transcode hands back an egress the broker is feeding itself, which has no
12472
+ * sentry counterpart.
12473
+ */
12474
+ sentryUrl: string().nullable().optional(),
12457
12475
  videoCodec: _enum(["H264", "H265"]),
12458
12476
  audioCodec: string(),
12459
12477
  resolution: object({
@@ -29619,6 +29637,114 @@ var motionTriggerCapability = {
29619
29637
  durability: "session"
29620
29638
  };
29621
29639
  /**
29640
+ * camera-grid-layout — the geometry of a COMPOSITE camera, on the camera's own
29641
+ * page.
29642
+ *
29643
+ * ## Why this is a capability and not an addon settings schema
29644
+ *
29645
+ * It was one, and it did not render. The addon declared the editor as a
29646
+ * `type: 'widget'` field inside its own `deviceSettingsSchema()`; the hub
29647
+ * returned that section correctly and `ConfigFormField` renders `type:'widget'`
29648
+ * perfectly well — and nothing ever asked camera-grid for it. The Cluster →
29649
+ * Pipeline → Device Overrides page interrogates a HAND-WRITTEN list of four
29650
+ * addons (`PIPELINE_CLUSTER_DEVICE_ADDONS`), whose own comment says an addon
29651
+ * not on it "falls off silently".
29652
+ *
29653
+ * Adding a fifth name to that list would have been the wrong fix twice over:
29654
+ * that page is per-camera DETECTION tuning, and a grid's geometry belongs
29655
+ * beside PTZ and motion zones on the camera itself — which is exactly where the
29656
+ * framework already puts a widget, when the widget is declared on a CAPABILITY.
29657
+ * `deviceConfig.ui` is the mechanism (`motion-zones`, `ptz`, `ptz-autotrack`
29658
+ * all use it): the framework emits the structural section and the widget
29659
+ * self-persists through the cap's own mutations.
29660
+ *
29661
+ * ## Why one addon may implement it
29662
+ *
29663
+ * It is a device-scoped NATIVE cap, registered by the grid camera device
29664
+ * itself. Nothing else declares a composite camera, so nothing else has a
29665
+ * layout — and the device-scoped route means the widget asks THE camera, not
29666
+ * "the camera-grid addon", which is what let the old custom-action pair be
29667
+ * reached only by a caller that already knew the addon id.
29668
+ *
29669
+ * ## The tab
29670
+ *
29671
+ * `streaming`, not a top-tab of its own. A grid's geometry IS what its stream
29672
+ * is, so the Streaming tab is where it belongs; a `grid` top-tab would need an
29673
+ * entry in `WELL_KNOWN_TAB_MAP` or the device page renders the raw id as the
29674
+ * label (measured on the robot camera, 2026-09-06 — a tab called "navigation"
29675
+ * next to "PTZ").
29676
+ */
29677
+ /** A rectangle in normalized [0,1] coordinates of whatever contains it. */
29678
+ var GridNormalizedRectSchema = object({
29679
+ x: number().min(0).max(1),
29680
+ y: number().min(0).max(1),
29681
+ width: number().gt(0).max(1),
29682
+ height: number().gt(0).max(1)
29683
+ });
29684
+ /**
29685
+ * One source camera, the part of its picture taken, and where that part lands.
29686
+ *
29687
+ * Both rectangles are NORMALIZED (D519): a source camera can change resolution
29688
+ * — a profile switch, a firmware update, a substream that comes back different
29689
+ * — and a stored PIXEL rectangle would quietly start cutting the wrong region,
29690
+ * which is the class of bug nobody files.
29691
+ */
29692
+ var GridLayoutCellSchema = object({
29693
+ deviceId: number().int().positive(),
29694
+ /** The part of the SOURCE taken, normalized against the source. */
29695
+ source: GridNormalizedRectSchema,
29696
+ /** Where it lands, normalized against the CANVAS. */
29697
+ cell: GridNormalizedRectSchema
29698
+ });
29699
+ /**
29700
+ * Which profiles this grid can actually compose, and why not.
29701
+ *
29702
+ * A grid's `high` composes its sources' `high` and its `low` their `low`, so a
29703
+ * profile is on offer only when EVERY source can serve it. The refusal NAMES
29704
+ * the sources, because "this grid has no low" is not a finding — "615 has no
29705
+ * low" is, and it is the one an operator can act on.
29706
+ */
29707
+ var GridProfileOfferSchema = object({
29708
+ profile: _enum([
29709
+ "high",
29710
+ "mid",
29711
+ "low"
29712
+ ]),
29713
+ offered: boolean(),
29714
+ /** Sources that cannot serve it. Empty when it is offered, or when there are no cells. */
29715
+ missingSources: array(number().int().positive())
29716
+ });
29717
+ var GridLayoutViewSchema = object({
29718
+ /** The persisted grid row this camera was declared from. */
29719
+ instanceId: string(),
29720
+ deviceId: number().int().nonnegative(),
29721
+ name: string(),
29722
+ /** The canvas the operator laid out — this grid's `high`. mid/low are scaled from it. */
29723
+ width: number().int(),
29724
+ height: number().int(),
29725
+ fps: number().int(),
29726
+ cells: array(GridLayoutCellSchema),
29727
+ /** What the catalog will publish, and what it refuses to. Read-only. */
29728
+ profiles: array(GridProfileOfferSchema)
29729
+ });
29730
+ var GridLayoutPatchSchema = object({
29731
+ deviceId: number().int().nonnegative(),
29732
+ name: string().min(1).max(160).optional(),
29733
+ width: number().int().min(160).max(7680).optional(),
29734
+ height: number().int().min(120).max(4320).optional(),
29735
+ fps: number().int().min(1).max(60).optional(),
29736
+ /**
29737
+ * The whole cell list at once. A per-cell patch would need an ordering the
29738
+ * editor does not have, and a half-applied layout is a picture nobody asked
29739
+ * for.
29740
+ */
29741
+ cells: array(GridLayoutCellSchema).max(16)
29742
+ });
29743
+ DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), GridLayoutViewSchema.nullable(), { auth: "admin" }), method(GridLayoutPatchSchema, GridLayoutViewSchema, {
29744
+ kind: "mutation",
29745
+ auth: "admin"
29746
+ });
29747
+ /**
29622
29748
  * Motion-zones share the same MaskShape vocabulary as privacy-mask — the
29623
29749
  * on-camera motion-detection mask is a single `grid` region (a row-major
29624
29750
  * boolean cell lattice the camera's onboard VMD evaluates). Composing it as
@@ -31820,6 +31946,26 @@ var PtzHomePresetSchema = object({
31820
31946
  "none"
31821
31947
  ])
31822
31948
  });
31949
+ /**
31950
+ * The camera's own idle-return behaviour: whether it goes home by itself, and
31951
+ * after how long.
31952
+ *
31953
+ * ONE shape, not two scalars, because on the camera they are ONE fact —
31954
+ * Reolink's guard point carries `benable` and `timeout` in the same element,
31955
+ * written by the same command. Reading them apart means two round-trips that
31956
+ * can disagree.
31957
+ *
31958
+ * `enabled` and `seconds` are INDEPENDENT: a camera can hold a 68 s delay with
31959
+ * the behaviour switched off, which is exactly what the vendor app leaves when
31960
+ * you turn the guard point off without clearing it. The first version of this
31961
+ * cap reported only the delay, so that state was invisible — a camera could be
31962
+ * configured to return after 68 seconds with no way to say whether it should
31963
+ * return at all.
31964
+ */
31965
+ var PtzHomeReturnSchema = object({
31966
+ enabled: boolean(),
31967
+ seconds: number().int()
31968
+ });
31823
31969
  var ptzCapability = {
31824
31970
  name: "ptz",
31825
31971
  scope: "device",
@@ -31902,18 +32048,25 @@ var ptzCapability = {
31902
32048
  auth: "admin"
31903
32049
  }),
31904
32050
  /**
31905
- * How long the camera waits, idle, before returning home on its own --
31906
- * `null` when it has no such behaviour.
32051
+ * Whether the camera returns home on its own when left idle, and after how
32052
+ * long. `null` when it has no such behaviour at all.
31907
32053
  *
31908
32054
  * Reolink's guard point carries one (`timeout`, freely settable in
31909
32055
  * seconds). It is a real function of the camera that we did not expose at
31910
32056
  * all, and it belongs next to the home it governs rather than in a vendor
31911
32057
  * settings page nobody associates with Home.
31912
32058
  */
31913
- getHomeReturnSeconds: method(object({ deviceId: number() }), number().int().nullable()),
31914
- /** Set the idle-return delay. Refused by a camera that has no such timer. */
31915
- setHomeReturnSeconds: method(object({
32059
+ getHomeReturn: method(object({ deviceId: number() }), PtzHomeReturnSchema.nullable()),
32060
+ /**
32061
+ * Set both together. Refused by a camera with no idle-return behaviour.
32062
+ *
32063
+ * They travel together for the same reason they are read together: the
32064
+ * underlying write carries both, so setting one alone means reading the
32065
+ * other back first and racing whoever else is writing.
32066
+ */
32067
+ setHomeReturn: method(object({
31916
32068
  deviceId: number(),
32069
+ enabled: boolean(),
31917
32070
  seconds: number().int().min(0).max(3600)
31918
32071
  }), _void(), {
31919
32072
  kind: "mutation",
@@ -37851,6 +38004,18 @@ Object.freeze({
37851
38004
  addonId: null,
37852
38005
  access: "view"
37853
38006
  },
38007
+ "cameraGridLayout.getLayout": {
38008
+ capName: "camera-grid-layout",
38009
+ capScope: "device",
38010
+ addonId: null,
38011
+ access: "view"
38012
+ },
38013
+ "cameraGridLayout.saveLayout": {
38014
+ capName: "camera-grid-layout",
38015
+ capScope: "device",
38016
+ addonId: null,
38017
+ access: "create"
38018
+ },
37854
38019
  "cameraStreams.getBrokerStreams": {
37855
38020
  capName: "camera-streams",
37856
38021
  capScope: "device",
@@ -41601,7 +41766,7 @@ Object.freeze({
41601
41766
  addonId: null,
41602
41767
  access: "view"
41603
41768
  },
41604
- "ptz.getHomeReturnSeconds": {
41769
+ "ptz.getHomeReturn": {
41605
41770
  capName: "ptz",
41606
41771
  capScope: "device",
41607
41772
  addonId: null,
@@ -41661,7 +41826,7 @@ Object.freeze({
41661
41826
  addonId: null,
41662
41827
  access: "create"
41663
41828
  },
41664
- "ptz.setHomeReturnSeconds": {
41829
+ "ptz.setHomeReturn": {
41665
41830
  capName: "ptz",
41666
41831
  capScope: "device",
41667
41832
  addonId: null,
@@ -43550,6 +43715,21 @@ Object.freeze({
43550
43715
  form: "single",
43551
43716
  optional: false
43552
43717
  }],
43718
+ "cameraGridLayout.getLayout": [{
43719
+ name: "deviceId",
43720
+ form: "single",
43721
+ optional: false
43722
+ }],
43723
+ "cameraGridLayout.saveLayout": [{
43724
+ name: "cells",
43725
+ form: "object-array",
43726
+ optional: false,
43727
+ itemField: "deviceId"
43728
+ }, {
43729
+ name: "deviceId",
43730
+ form: "single",
43731
+ optional: false
43732
+ }],
43553
43733
  "cameraStreams.getBrokerStreams": [{
43554
43734
  name: "deviceId",
43555
43735
  form: "single",
@@ -44850,7 +45030,7 @@ Object.freeze({
44850
45030
  form: "single",
44851
45031
  optional: false
44852
45032
  }],
44853
- "ptz.getHomeReturnSeconds": [{
45033
+ "ptz.getHomeReturn": [{
44854
45034
  name: "deviceId",
44855
45035
  form: "single",
44856
45036
  optional: false
@@ -44900,7 +45080,7 @@ Object.freeze({
44900
45080
  form: "single",
44901
45081
  optional: false
44902
45082
  }],
44903
- "ptz.setHomeReturnSeconds": [{
45083
+ "ptz.setHomeReturn": [{
44904
45084
  name: "deviceId",
44905
45085
  form: "single",
44906
45086
  optional: false
@@ -153888,21 +154068,28 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
153888
154068
  }, channel);
153889
154069
  this.ctx.logger.info("reolink: guard point pinned to the current position", { tags: { deviceId: this.id } });
153890
154070
  },
153891
- getHomeReturnSeconds: async ({ deviceId }) => {
154071
+ getHomeReturn: async ({ deviceId }) => {
153892
154072
  if (deviceId !== this.id) return null;
153893
154073
  const guard = await this.readGuardStatus();
153894
- return guard === null ? null : guard.timeoutSeconds;
154074
+ return guard === null ? null : {
154075
+ enabled: guard.enabled,
154076
+ seconds: guard.timeoutSeconds
154077
+ };
153895
154078
  },
153896
- setHomeReturnSeconds: async ({ deviceId, seconds }) => {
154079
+ setHomeReturn: async ({ deviceId, enabled, seconds }) => {
153897
154080
  if (deviceId !== this.id) return;
153898
- const current = await this.readGuardStatus();
153899
- const api = await this.ensureApi();
153900
- const channel = this.getChannel();
153901
- await api.setPtzGuard({
153902
- enabled: current?.enabled ?? true,
154081
+ await (await this.ensureApi()).setPtzGuard({
154082
+ enabled,
153903
154083
  timeoutSeconds: seconds,
153904
154084
  setPosition: false
153905
- }, channel);
154085
+ }, this.getChannel());
154086
+ this.ctx.logger.info("reolink: guard point idle-return updated", {
154087
+ tags: { deviceId: this.id },
154088
+ meta: {
154089
+ enabled,
154090
+ seconds
154091
+ }
154092
+ });
153906
154093
  },
153907
154094
  setHomePreset: async ({ deviceId, presetId }) => {
153908
154095
  if (deviceId !== this.id) return;
package/dist/addon.mjs CHANGED
@@ -5381,7 +5381,7 @@ var ZodIssueCode = {
5381
5381
  var ZodFirstPartyTypeKind;
5382
5382
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5383
5383
  //#endregion
5384
- //#region ../types/dist/sleep-vl6nBE8d.mjs
5384
+ //#region ../types/dist/sleep-Bs3xPtSh.mjs
5385
5385
  /**
5386
5386
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5387
5387
  * window to float samples (D455).
@@ -12449,6 +12449,24 @@ var GetStreamWithCodecInputSchema = object({
12449
12449
  });
12450
12450
  var RtpSourceSchema = object({
12451
12451
  url: string(),
12452
+ /**
12453
+ * The SENTRY counterpart of {@link url} — the dial that asks for the stream
12454
+ * WITHOUT asking for the camera — or `null` when this camera offers none.
12455
+ *
12456
+ * The broker answers it because the broker is the only thing that can:
12457
+ * eligibility is `sentryEligible` (a BATTERY camera that DECLARES the signals
12458
+ * cap), asked of facts only the broker holds. A consumer re-deriving it from
12459
+ * cap bindings would be a second authority, and two authorities on the same
12460
+ * question is how a composite camera ends up dialling a sleeping camera
12461
+ * because its own copy of the rule was a release behind.
12462
+ *
12463
+ * `null` is a real answer and is never a live url in disguise: a consumer
12464
+ * that silently fell back to {@link url} would perform exactly the wake this
12465
+ * field exists to prevent. Only a PASSTHROUGH acquisition can offer one — a
12466
+ * transcode hands back an egress the broker is feeding itself, which has no
12467
+ * sentry counterpart.
12468
+ */
12469
+ sentryUrl: string().nullable().optional(),
12452
12470
  videoCodec: _enum(["H264", "H265"]),
12453
12471
  audioCodec: string(),
12454
12472
  resolution: object({
@@ -29614,6 +29632,114 @@ var motionTriggerCapability = {
29614
29632
  durability: "session"
29615
29633
  };
29616
29634
  /**
29635
+ * camera-grid-layout — the geometry of a COMPOSITE camera, on the camera's own
29636
+ * page.
29637
+ *
29638
+ * ## Why this is a capability and not an addon settings schema
29639
+ *
29640
+ * It was one, and it did not render. The addon declared the editor as a
29641
+ * `type: 'widget'` field inside its own `deviceSettingsSchema()`; the hub
29642
+ * returned that section correctly and `ConfigFormField` renders `type:'widget'`
29643
+ * perfectly well — and nothing ever asked camera-grid for it. The Cluster →
29644
+ * Pipeline → Device Overrides page interrogates a HAND-WRITTEN list of four
29645
+ * addons (`PIPELINE_CLUSTER_DEVICE_ADDONS`), whose own comment says an addon
29646
+ * not on it "falls off silently".
29647
+ *
29648
+ * Adding a fifth name to that list would have been the wrong fix twice over:
29649
+ * that page is per-camera DETECTION tuning, and a grid's geometry belongs
29650
+ * beside PTZ and motion zones on the camera itself — which is exactly where the
29651
+ * framework already puts a widget, when the widget is declared on a CAPABILITY.
29652
+ * `deviceConfig.ui` is the mechanism (`motion-zones`, `ptz`, `ptz-autotrack`
29653
+ * all use it): the framework emits the structural section and the widget
29654
+ * self-persists through the cap's own mutations.
29655
+ *
29656
+ * ## Why one addon may implement it
29657
+ *
29658
+ * It is a device-scoped NATIVE cap, registered by the grid camera device
29659
+ * itself. Nothing else declares a composite camera, so nothing else has a
29660
+ * layout — and the device-scoped route means the widget asks THE camera, not
29661
+ * "the camera-grid addon", which is what let the old custom-action pair be
29662
+ * reached only by a caller that already knew the addon id.
29663
+ *
29664
+ * ## The tab
29665
+ *
29666
+ * `streaming`, not a top-tab of its own. A grid's geometry IS what its stream
29667
+ * is, so the Streaming tab is where it belongs; a `grid` top-tab would need an
29668
+ * entry in `WELL_KNOWN_TAB_MAP` or the device page renders the raw id as the
29669
+ * label (measured on the robot camera, 2026-09-06 — a tab called "navigation"
29670
+ * next to "PTZ").
29671
+ */
29672
+ /** A rectangle in normalized [0,1] coordinates of whatever contains it. */
29673
+ var GridNormalizedRectSchema = object({
29674
+ x: number().min(0).max(1),
29675
+ y: number().min(0).max(1),
29676
+ width: number().gt(0).max(1),
29677
+ height: number().gt(0).max(1)
29678
+ });
29679
+ /**
29680
+ * One source camera, the part of its picture taken, and where that part lands.
29681
+ *
29682
+ * Both rectangles are NORMALIZED (D519): a source camera can change resolution
29683
+ * — a profile switch, a firmware update, a substream that comes back different
29684
+ * — and a stored PIXEL rectangle would quietly start cutting the wrong region,
29685
+ * which is the class of bug nobody files.
29686
+ */
29687
+ var GridLayoutCellSchema = object({
29688
+ deviceId: number().int().positive(),
29689
+ /** The part of the SOURCE taken, normalized against the source. */
29690
+ source: GridNormalizedRectSchema,
29691
+ /** Where it lands, normalized against the CANVAS. */
29692
+ cell: GridNormalizedRectSchema
29693
+ });
29694
+ /**
29695
+ * Which profiles this grid can actually compose, and why not.
29696
+ *
29697
+ * A grid's `high` composes its sources' `high` and its `low` their `low`, so a
29698
+ * profile is on offer only when EVERY source can serve it. The refusal NAMES
29699
+ * the sources, because "this grid has no low" is not a finding — "615 has no
29700
+ * low" is, and it is the one an operator can act on.
29701
+ */
29702
+ var GridProfileOfferSchema = object({
29703
+ profile: _enum([
29704
+ "high",
29705
+ "mid",
29706
+ "low"
29707
+ ]),
29708
+ offered: boolean(),
29709
+ /** Sources that cannot serve it. Empty when it is offered, or when there are no cells. */
29710
+ missingSources: array(number().int().positive())
29711
+ });
29712
+ var GridLayoutViewSchema = object({
29713
+ /** The persisted grid row this camera was declared from. */
29714
+ instanceId: string(),
29715
+ deviceId: number().int().nonnegative(),
29716
+ name: string(),
29717
+ /** The canvas the operator laid out — this grid's `high`. mid/low are scaled from it. */
29718
+ width: number().int(),
29719
+ height: number().int(),
29720
+ fps: number().int(),
29721
+ cells: array(GridLayoutCellSchema),
29722
+ /** What the catalog will publish, and what it refuses to. Read-only. */
29723
+ profiles: array(GridProfileOfferSchema)
29724
+ });
29725
+ var GridLayoutPatchSchema = object({
29726
+ deviceId: number().int().nonnegative(),
29727
+ name: string().min(1).max(160).optional(),
29728
+ width: number().int().min(160).max(7680).optional(),
29729
+ height: number().int().min(120).max(4320).optional(),
29730
+ fps: number().int().min(1).max(60).optional(),
29731
+ /**
29732
+ * The whole cell list at once. A per-cell patch would need an ordering the
29733
+ * editor does not have, and a half-applied layout is a picture nobody asked
29734
+ * for.
29735
+ */
29736
+ cells: array(GridLayoutCellSchema).max(16)
29737
+ });
29738
+ DeviceType.Camera, method(object({ deviceId: number().int().nonnegative() }), GridLayoutViewSchema.nullable(), { auth: "admin" }), method(GridLayoutPatchSchema, GridLayoutViewSchema, {
29739
+ kind: "mutation",
29740
+ auth: "admin"
29741
+ });
29742
+ /**
29617
29743
  * Motion-zones share the same MaskShape vocabulary as privacy-mask — the
29618
29744
  * on-camera motion-detection mask is a single `grid` region (a row-major
29619
29745
  * boolean cell lattice the camera's onboard VMD evaluates). Composing it as
@@ -31815,6 +31941,26 @@ var PtzHomePresetSchema = object({
31815
31941
  "none"
31816
31942
  ])
31817
31943
  });
31944
+ /**
31945
+ * The camera's own idle-return behaviour: whether it goes home by itself, and
31946
+ * after how long.
31947
+ *
31948
+ * ONE shape, not two scalars, because on the camera they are ONE fact —
31949
+ * Reolink's guard point carries `benable` and `timeout` in the same element,
31950
+ * written by the same command. Reading them apart means two round-trips that
31951
+ * can disagree.
31952
+ *
31953
+ * `enabled` and `seconds` are INDEPENDENT: a camera can hold a 68 s delay with
31954
+ * the behaviour switched off, which is exactly what the vendor app leaves when
31955
+ * you turn the guard point off without clearing it. The first version of this
31956
+ * cap reported only the delay, so that state was invisible — a camera could be
31957
+ * configured to return after 68 seconds with no way to say whether it should
31958
+ * return at all.
31959
+ */
31960
+ var PtzHomeReturnSchema = object({
31961
+ enabled: boolean(),
31962
+ seconds: number().int()
31963
+ });
31818
31964
  var ptzCapability = {
31819
31965
  name: "ptz",
31820
31966
  scope: "device",
@@ -31897,18 +32043,25 @@ var ptzCapability = {
31897
32043
  auth: "admin"
31898
32044
  }),
31899
32045
  /**
31900
- * How long the camera waits, idle, before returning home on its own --
31901
- * `null` when it has no such behaviour.
32046
+ * Whether the camera returns home on its own when left idle, and after how
32047
+ * long. `null` when it has no such behaviour at all.
31902
32048
  *
31903
32049
  * Reolink's guard point carries one (`timeout`, freely settable in
31904
32050
  * seconds). It is a real function of the camera that we did not expose at
31905
32051
  * all, and it belongs next to the home it governs rather than in a vendor
31906
32052
  * settings page nobody associates with Home.
31907
32053
  */
31908
- getHomeReturnSeconds: method(object({ deviceId: number() }), number().int().nullable()),
31909
- /** Set the idle-return delay. Refused by a camera that has no such timer. */
31910
- setHomeReturnSeconds: method(object({
32054
+ getHomeReturn: method(object({ deviceId: number() }), PtzHomeReturnSchema.nullable()),
32055
+ /**
32056
+ * Set both together. Refused by a camera with no idle-return behaviour.
32057
+ *
32058
+ * They travel together for the same reason they are read together: the
32059
+ * underlying write carries both, so setting one alone means reading the
32060
+ * other back first and racing whoever else is writing.
32061
+ */
32062
+ setHomeReturn: method(object({
31911
32063
  deviceId: number(),
32064
+ enabled: boolean(),
31912
32065
  seconds: number().int().min(0).max(3600)
31913
32066
  }), _void(), {
31914
32067
  kind: "mutation",
@@ -37846,6 +37999,18 @@ Object.freeze({
37846
37999
  addonId: null,
37847
38000
  access: "view"
37848
38001
  },
38002
+ "cameraGridLayout.getLayout": {
38003
+ capName: "camera-grid-layout",
38004
+ capScope: "device",
38005
+ addonId: null,
38006
+ access: "view"
38007
+ },
38008
+ "cameraGridLayout.saveLayout": {
38009
+ capName: "camera-grid-layout",
38010
+ capScope: "device",
38011
+ addonId: null,
38012
+ access: "create"
38013
+ },
37849
38014
  "cameraStreams.getBrokerStreams": {
37850
38015
  capName: "camera-streams",
37851
38016
  capScope: "device",
@@ -41596,7 +41761,7 @@ Object.freeze({
41596
41761
  addonId: null,
41597
41762
  access: "view"
41598
41763
  },
41599
- "ptz.getHomeReturnSeconds": {
41764
+ "ptz.getHomeReturn": {
41600
41765
  capName: "ptz",
41601
41766
  capScope: "device",
41602
41767
  addonId: null,
@@ -41656,7 +41821,7 @@ Object.freeze({
41656
41821
  addonId: null,
41657
41822
  access: "create"
41658
41823
  },
41659
- "ptz.setHomeReturnSeconds": {
41824
+ "ptz.setHomeReturn": {
41660
41825
  capName: "ptz",
41661
41826
  capScope: "device",
41662
41827
  addonId: null,
@@ -43545,6 +43710,21 @@ Object.freeze({
43545
43710
  form: "single",
43546
43711
  optional: false
43547
43712
  }],
43713
+ "cameraGridLayout.getLayout": [{
43714
+ name: "deviceId",
43715
+ form: "single",
43716
+ optional: false
43717
+ }],
43718
+ "cameraGridLayout.saveLayout": [{
43719
+ name: "cells",
43720
+ form: "object-array",
43721
+ optional: false,
43722
+ itemField: "deviceId"
43723
+ }, {
43724
+ name: "deviceId",
43725
+ form: "single",
43726
+ optional: false
43727
+ }],
43548
43728
  "cameraStreams.getBrokerStreams": [{
43549
43729
  name: "deviceId",
43550
43730
  form: "single",
@@ -44845,7 +45025,7 @@ Object.freeze({
44845
45025
  form: "single",
44846
45026
  optional: false
44847
45027
  }],
44848
- "ptz.getHomeReturnSeconds": [{
45028
+ "ptz.getHomeReturn": [{
44849
45029
  name: "deviceId",
44850
45030
  form: "single",
44851
45031
  optional: false
@@ -44895,7 +45075,7 @@ Object.freeze({
44895
45075
  form: "single",
44896
45076
  optional: false
44897
45077
  }],
44898
- "ptz.setHomeReturnSeconds": [{
45078
+ "ptz.setHomeReturn": [{
44899
45079
  name: "deviceId",
44900
45080
  form: "single",
44901
45081
  optional: false
@@ -153883,21 +154063,28 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
153883
154063
  }, channel);
153884
154064
  this.ctx.logger.info("reolink: guard point pinned to the current position", { tags: { deviceId: this.id } });
153885
154065
  },
153886
- getHomeReturnSeconds: async ({ deviceId }) => {
154066
+ getHomeReturn: async ({ deviceId }) => {
153887
154067
  if (deviceId !== this.id) return null;
153888
154068
  const guard = await this.readGuardStatus();
153889
- return guard === null ? null : guard.timeoutSeconds;
154069
+ return guard === null ? null : {
154070
+ enabled: guard.enabled,
154071
+ seconds: guard.timeoutSeconds
154072
+ };
153890
154073
  },
153891
- setHomeReturnSeconds: async ({ deviceId, seconds }) => {
154074
+ setHomeReturn: async ({ deviceId, enabled, seconds }) => {
153892
154075
  if (deviceId !== this.id) return;
153893
- const current = await this.readGuardStatus();
153894
- const api = await this.ensureApi();
153895
- const channel = this.getChannel();
153896
- await api.setPtzGuard({
153897
- enabled: current?.enabled ?? true,
154076
+ await (await this.ensureApi()).setPtzGuard({
154077
+ enabled,
153898
154078
  timeoutSeconds: seconds,
153899
154079
  setPosition: false
153900
- }, channel);
154080
+ }, this.getChannel());
154081
+ this.ctx.logger.info("reolink: guard point idle-return updated", {
154082
+ tags: { deviceId: this.id },
154083
+ meta: {
154084
+ enabled,
154085
+ seconds
154086
+ }
154087
+ });
153901
154088
  },
153902
154089
  setHomePreset: async ({ deviceId, presetId }) => {
153903
154090
  if (deviceId !== this.id) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-reolink",
3
- "version": "1.2.140",
3
+ "version": "1.2.142",
4
4
  "description": "Reolink camera device provider addon for CamStack — native Baichuan protocol",
5
5
  "keywords": [
6
6
  "camstack",