@camstack/addon-provider-hikvision 1.2.78 → 1.2.80

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/dist/addon.js CHANGED
@@ -5413,6 +5413,12 @@ Object.fromEntries([
5413
5413
  icon: "move",
5414
5414
  order: 40
5415
5415
  },
5416
+ {
5417
+ id: "navigation",
5418
+ label: "Navigation",
5419
+ icon: "compass",
5420
+ order: 41
5421
+ },
5416
5422
  {
5417
5423
  id: "consumables",
5418
5424
  label: "Consumables",
@@ -29467,6 +29473,287 @@ var ptzAutotrackCapability = {
29467
29473
  durability: "session"
29468
29474
  };
29469
29475
  /**
29476
+ * `navigation` — a device-scoped capability that natively expresses the FULL
29477
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
29478
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
29479
+ *
29480
+ * Why a NEW cap rather than overloading `ptz`:
29481
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
29482
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
29483
+ * The two are different physical models: PTZ is absolute-position + presets,
29484
+ * navigation is momentary drive nudges + discrete robot ACTIONS
29485
+ * (dock / spot-clean / follow-pet / go-to-point / …).
29486
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
29487
+ * the reverse:
29488
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
29489
+ * / `getOptions`), and
29490
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
29491
+ * robot camera shows up in the existing PTZ control path without every
29492
+ * PTZ provider learning about robots. The mapping lives in the adapter,
29493
+ * not here (see the addon design note):
29494
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
29495
+ * ptz.stop() → navigation.stop()
29496
+ * ptz.goHome() → navigation.runAction('goHome')
29497
+ * ptz.getPresets() → navigation.listActions() (id→preset)
29498
+ * ptz.goToPreset(id) → navigation.runAction(id)
29499
+ *
29500
+ * ## Continuous drive
29501
+ *
29502
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
29503
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
29504
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
29505
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
29506
+ * coalesce them. The UI owns the cadence.
29507
+ *
29508
+ * ## The action dictionary
29509
+ *
29510
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
29511
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
29512
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
29513
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
29514
+ * vendor-specific list. `kind: 'action'` entries are triggered with
29515
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
29516
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
29517
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
29518
+ *
29519
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
29520
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
29521
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
29522
+ * every device handle. A future nodedreame publish adds a typed
29523
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
29524
+ * provider can then swap the raw calls for the typed methods with no change to
29525
+ * THIS contract.
29526
+ */
29527
+ /**
29528
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
29529
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
29530
+ * halts it.
29531
+ *
29532
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
29533
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
29534
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
29535
+ * vector by it (drivers without proportional drive ignore it).
29536
+ *
29537
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
29538
+ * axis alone; an all-undefined nudge is a no-op.
29539
+ */
29540
+ var NavigationMoveCommandSchema = object({
29541
+ pan: number().min(-1).max(1).optional(),
29542
+ tilt: number().min(-1).max(1).optional(),
29543
+ speed: number().min(0).max(1).optional()
29544
+ });
29545
+ /**
29546
+ * The enumerated discrete actions a navigation-capable robot can perform via
29547
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
29548
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
29549
+ * `playSound` (see the `sound` dictionary entries).
29550
+ */
29551
+ var NavigationActionIdSchema = _enum([
29552
+ "goHome",
29553
+ "locate",
29554
+ "spotClean",
29555
+ "findPet",
29556
+ "personFollow",
29557
+ "stop",
29558
+ "startClean",
29559
+ "pauseClean",
29560
+ "dockWash",
29561
+ "autoEmpty",
29562
+ "flashOn",
29563
+ "flashOff"
29564
+ ]);
29565
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
29566
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
29567
+ /**
29568
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
29569
+ * native panel and the PTZ mimic render as a button.
29570
+ *
29571
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
29572
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
29573
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
29574
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
29575
+ * - `label` — operator-facing English label.
29576
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
29577
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
29578
+ * PTZ render ONLY enabled entries. Data-driven: the provider
29579
+ * flips it from config, never by editing code.
29580
+ */
29581
+ var NavigationActionEntrySchema = object({
29582
+ id: string(),
29583
+ kind: NavigationEntryKindSchema,
29584
+ label: string(),
29585
+ icon: string(),
29586
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
29587
+ soundId: number().int().optional(),
29588
+ /** Per-device feature flag — render this entry only when true. */
29589
+ enabled: boolean()
29590
+ });
29591
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
29592
+ var NavigationPointSchema = object({
29593
+ x: number(),
29594
+ y: number()
29595
+ });
29596
+ /**
29597
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
29598
+ * The cap reports which are enabled so the UI / PTZ render only the controls
29599
+ * that are turned on for THIS device. Data-driven: the provider derives these
29600
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
29601
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
29602
+ * that are not dictionary entries.
29603
+ *
29604
+ * - `move` / `stop` — the momentary drive joystick.
29605
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
29606
+ * map-coordinate plumbing is wired.
29607
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
29608
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
29609
+ * - `light` — the on/off fill-light toggle (works anytime).
29610
+ * - `lightMode` — the auto/manual selector + manual level slider (a
29611
+ * camera-service control; needs an active stream).
29612
+ */
29613
+ var NavigationFeaturesSchema = object({
29614
+ move: boolean(),
29615
+ stop: boolean(),
29616
+ goToPoint: boolean(),
29617
+ runAction: boolean(),
29618
+ playSound: boolean(),
29619
+ light: boolean(),
29620
+ lightMode: boolean()
29621
+ });
29622
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
29623
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
29624
+ /**
29625
+ * Live navigation state so the UI can reflect what the robot is doing:
29626
+ * - `mode` — coarse activity (idle / cleaning / following / …).
29627
+ * - `following` — person/pet follow is currently armed.
29628
+ * - `flash` — the on-camera fill light is on.
29629
+ * - `lightMode` — auto vs manual fill-light mode.
29630
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
29631
+ * `lightMode === 'manual'`.
29632
+ */
29633
+ var NavigationStatusSchema = object({
29634
+ mode: _enum([
29635
+ "idle",
29636
+ "cleaning",
29637
+ "spot",
29638
+ "following",
29639
+ "goto",
29640
+ "returning",
29641
+ "paused",
29642
+ "unknown"
29643
+ ]),
29644
+ following: boolean(),
29645
+ flash: boolean(),
29646
+ lightMode: NavigationLightModeSchema,
29647
+ lightLevel: number().min(40).max(100),
29648
+ /** Ms epoch when the slice was last updated. */
29649
+ lastChangedAt: number()
29650
+ });
29651
+ /**
29652
+ * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
29653
+ * observable). Adds `lastFetchedAt` on top of the status shape per the
29654
+ * convention.
29655
+ */
29656
+ var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: number() });
29657
+ var navigationCapability = {
29658
+ name: "navigation",
29659
+ scope: "device",
29660
+ deviceNative: true,
29661
+ mode: "singleton",
29662
+ deviceTypes: [DeviceType.Camera],
29663
+ deviceConfig: { ui: {
29664
+ kind: "widget",
29665
+ widgetId: "host/navigation-panel",
29666
+ tab: "navigation",
29667
+ topTab: true,
29668
+ label: "Navigation",
29669
+ order: 0
29670
+ } },
29671
+ methods: {
29672
+ /**
29673
+ * Momentary drive nudge (the robot moves). `protected` — mirrors
29674
+ * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
29675
+ * path) works for any authenticated user, not admin-only. The UI sends
29676
+ * these at ~1 Hz while a control is held; the provider forwards each one to
29677
+ * a single drive write WITHOUT debouncing.
29678
+ */
29679
+ move: method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29680
+ /** Halt all motion immediately (zero drive vector). */
29681
+ stop: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
29682
+ /** Send the robot to a point on its live map. */
29683
+ goToPoint: method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29684
+ /**
29685
+ * Enumerate the discrete controls THIS device supports (data-driven UI +
29686
+ * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
29687
+ */
29688
+ listActions: method(object({ deviceId: number() }), array(NavigationActionEntrySchema)),
29689
+ /**
29690
+ * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
29691
+ * unsupported action ids are rejected by the provider.
29692
+ */
29693
+ runAction: method(object({
29694
+ deviceId: number(),
29695
+ actionId: NavigationActionIdSchema
29696
+ }), _void(), { kind: "mutation" }),
29697
+ /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
29698
+ playSound: method(object({
29699
+ deviceId: number(),
29700
+ soundId: number().int()
29701
+ }), _void(), { kind: "mutation" }),
29702
+ /**
29703
+ * Turn the on-camera fill light on / off (the `OpenFullLight` control —
29704
+ * works anytime, no active stream required).
29705
+ */
29706
+ setLightOn: method(object({
29707
+ deviceId: number(),
29708
+ on: boolean()
29709
+ }), _void(), { kind: "mutation" }),
29710
+ /**
29711
+ * Set the fill-light mode (auto vs manual). `manual` optionally carries the
29712
+ * initial `level`. The auto/manual + level control is a CAMERA-service
29713
+ * action that generally needs an active camera stream/monitor session — the
29714
+ * UI shows the manual level slider ONLY when `mode === 'manual'`.
29715
+ */
29716
+ setLightMode: method(object({
29717
+ deviceId: number(),
29718
+ mode: NavigationLightModeSchema,
29719
+ level: number().min(40).max(100).optional()
29720
+ }), _void(), { kind: "mutation" }),
29721
+ /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
29722
+ setLightLevel: method(object({
29723
+ deviceId: number(),
29724
+ level: number().min(40).max(100)
29725
+ }), _void(), { kind: "mutation" }),
29726
+ /**
29727
+ * Per-device FEATURE-FLAG report for the general primitives — drives which
29728
+ * controls the UI shows (the per-entry flags for the dictionary come back on
29729
+ * `listActions`).
29730
+ */
29731
+ getFeatures: method(object({ deviceId: number() }), NavigationFeaturesSchema)
29732
+ },
29733
+ events: { onStatusChanged: { data: object({
29734
+ deviceId: number(),
29735
+ status: NavigationStatusSchema
29736
+ }) } },
29737
+ status: {
29738
+ schema: NavigationStatusSchema,
29739
+ kind: "push"
29740
+ },
29741
+ /**
29742
+ * Runtime-state slice mirrored by the kernel. The navigation panel watches it
29743
+ * for live mode / follow / flash changes.
29744
+ */
29745
+ runtimeState: NavigationRuntimeStateSchema,
29746
+ /**
29747
+ * Runtime-state durability: **session** — like `vacuum-control`, a restored
29748
+ * `mode: cleaning` / `following: true` is a robot that is not actually doing
29749
+ * that. The live handle re-publishes on connect.
29750
+ *
29751
+ * See `RuntimeStateDurability`. Enforced by
29752
+ * `scripts/check-runtime-state-durability.ts`.
29753
+ */
29754
+ durability: "session"
29755
+ };
29756
+ /**
29470
29757
  * reboot — device-scoped capability for "soft" device reboots (firmware
29471
29758
  * reboot via vendor protocol; cameras, NVRs, doorbells). Surfaces a
29472
29759
  * single mutation so the UI can offer a confirm-and-reboot button for
@@ -32979,6 +33266,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
32979
33266
  motionTrigger: motionTriggerCapability,
32980
33267
  motionZones: motionZonesCapability,
32981
33268
  nativeObjectDetection: nativeObjectDetectionCapability,
33269
+ navigation: navigationCapability,
32982
33270
  networkLink: networkLinkCapability,
32983
33271
  notifier: notifierCapability,
32984
33272
  numericSensor: numericSensorCapability,
@@ -37051,6 +37339,66 @@ Object.freeze({
37051
37339
  addonId: null,
37052
37340
  access: "create"
37053
37341
  },
37342
+ "navigation.getFeatures": {
37343
+ capName: "navigation",
37344
+ capScope: "device",
37345
+ addonId: null,
37346
+ access: "view"
37347
+ },
37348
+ "navigation.goToPoint": {
37349
+ capName: "navigation",
37350
+ capScope: "device",
37351
+ addonId: null,
37352
+ access: "create"
37353
+ },
37354
+ "navigation.listActions": {
37355
+ capName: "navigation",
37356
+ capScope: "device",
37357
+ addonId: null,
37358
+ access: "view"
37359
+ },
37360
+ "navigation.move": {
37361
+ capName: "navigation",
37362
+ capScope: "device",
37363
+ addonId: null,
37364
+ access: "create"
37365
+ },
37366
+ "navigation.playSound": {
37367
+ capName: "navigation",
37368
+ capScope: "device",
37369
+ addonId: null,
37370
+ access: "create"
37371
+ },
37372
+ "navigation.runAction": {
37373
+ capName: "navigation",
37374
+ capScope: "device",
37375
+ addonId: null,
37376
+ access: "create"
37377
+ },
37378
+ "navigation.setLightLevel": {
37379
+ capName: "navigation",
37380
+ capScope: "device",
37381
+ addonId: null,
37382
+ access: "create"
37383
+ },
37384
+ "navigation.setLightMode": {
37385
+ capName: "navigation",
37386
+ capScope: "device",
37387
+ addonId: null,
37388
+ access: "create"
37389
+ },
37390
+ "navigation.setLightOn": {
37391
+ capName: "navigation",
37392
+ capScope: "device",
37393
+ addonId: null,
37394
+ access: "create"
37395
+ },
37396
+ "navigation.stop": {
37397
+ capName: "navigation",
37398
+ capScope: "device",
37399
+ addonId: null,
37400
+ access: "create"
37401
+ },
37054
37402
  "networkAccess.getEndpoint": {
37055
37403
  capName: "network-access",
37056
37404
  capScope: "system",
@@ -41146,6 +41494,56 @@ Object.freeze({
41146
41494
  form: "single",
41147
41495
  optional: false
41148
41496
  }],
41497
+ "navigation.getFeatures": [{
41498
+ name: "deviceId",
41499
+ form: "single",
41500
+ optional: false
41501
+ }],
41502
+ "navigation.goToPoint": [{
41503
+ name: "deviceId",
41504
+ form: "single",
41505
+ optional: false
41506
+ }],
41507
+ "navigation.listActions": [{
41508
+ name: "deviceId",
41509
+ form: "single",
41510
+ optional: false
41511
+ }],
41512
+ "navigation.move": [{
41513
+ name: "deviceId",
41514
+ form: "single",
41515
+ optional: false
41516
+ }],
41517
+ "navigation.playSound": [{
41518
+ name: "deviceId",
41519
+ form: "single",
41520
+ optional: false
41521
+ }],
41522
+ "navigation.runAction": [{
41523
+ name: "deviceId",
41524
+ form: "single",
41525
+ optional: false
41526
+ }],
41527
+ "navigation.setLightLevel": [{
41528
+ name: "deviceId",
41529
+ form: "single",
41530
+ optional: false
41531
+ }],
41532
+ "navigation.setLightMode": [{
41533
+ name: "deviceId",
41534
+ form: "single",
41535
+ optional: false
41536
+ }],
41537
+ "navigation.setLightOn": [{
41538
+ name: "deviceId",
41539
+ form: "single",
41540
+ optional: false
41541
+ }],
41542
+ "navigation.stop": [{
41543
+ name: "deviceId",
41544
+ form: "single",
41545
+ optional: false
41546
+ }],
41149
41547
  "networkQuality.getDeviceStats": [{
41150
41548
  name: "deviceId",
41151
41549
  form: "single",
package/dist/addon.mjs CHANGED
@@ -5414,6 +5414,12 @@ Object.fromEntries([
5414
5414
  icon: "move",
5415
5415
  order: 40
5416
5416
  },
5417
+ {
5418
+ id: "navigation",
5419
+ label: "Navigation",
5420
+ icon: "compass",
5421
+ order: 41
5422
+ },
5417
5423
  {
5418
5424
  id: "consumables",
5419
5425
  label: "Consumables",
@@ -29468,6 +29474,287 @@ var ptzAutotrackCapability = {
29468
29474
  durability: "session"
29469
29475
  };
29470
29476
  /**
29477
+ * `navigation` — a device-scoped capability that natively expresses the FULL
29478
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
29479
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
29480
+ *
29481
+ * Why a NEW cap rather than overloading `ptz`:
29482
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
29483
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
29484
+ * The two are different physical models: PTZ is absolute-position + presets,
29485
+ * navigation is momentary drive nudges + discrete robot ACTIONS
29486
+ * (dock / spot-clean / follow-pet / go-to-point / …).
29487
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
29488
+ * the reverse:
29489
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
29490
+ * / `getOptions`), and
29491
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
29492
+ * robot camera shows up in the existing PTZ control path without every
29493
+ * PTZ provider learning about robots. The mapping lives in the adapter,
29494
+ * not here (see the addon design note):
29495
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
29496
+ * ptz.stop() → navigation.stop()
29497
+ * ptz.goHome() → navigation.runAction('goHome')
29498
+ * ptz.getPresets() → navigation.listActions() (id→preset)
29499
+ * ptz.goToPreset(id) → navigation.runAction(id)
29500
+ *
29501
+ * ## Continuous drive
29502
+ *
29503
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
29504
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
29505
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
29506
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
29507
+ * coalesce them. The UI owns the cadence.
29508
+ *
29509
+ * ## The action dictionary
29510
+ *
29511
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
29512
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
29513
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
29514
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
29515
+ * vendor-specific list. `kind: 'action'` entries are triggered with
29516
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
29517
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
29518
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
29519
+ *
29520
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
29521
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
29522
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
29523
+ * every device handle. A future nodedreame publish adds a typed
29524
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
29525
+ * provider can then swap the raw calls for the typed methods with no change to
29526
+ * THIS contract.
29527
+ */
29528
+ /**
29529
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
29530
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
29531
+ * halts it.
29532
+ *
29533
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
29534
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
29535
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
29536
+ * vector by it (drivers without proportional drive ignore it).
29537
+ *
29538
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
29539
+ * axis alone; an all-undefined nudge is a no-op.
29540
+ */
29541
+ var NavigationMoveCommandSchema = object({
29542
+ pan: number().min(-1).max(1).optional(),
29543
+ tilt: number().min(-1).max(1).optional(),
29544
+ speed: number().min(0).max(1).optional()
29545
+ });
29546
+ /**
29547
+ * The enumerated discrete actions a navigation-capable robot can perform via
29548
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
29549
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
29550
+ * `playSound` (see the `sound` dictionary entries).
29551
+ */
29552
+ var NavigationActionIdSchema = _enum([
29553
+ "goHome",
29554
+ "locate",
29555
+ "spotClean",
29556
+ "findPet",
29557
+ "personFollow",
29558
+ "stop",
29559
+ "startClean",
29560
+ "pauseClean",
29561
+ "dockWash",
29562
+ "autoEmpty",
29563
+ "flashOn",
29564
+ "flashOff"
29565
+ ]);
29566
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
29567
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
29568
+ /**
29569
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
29570
+ * native panel and the PTZ mimic render as a button.
29571
+ *
29572
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
29573
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
29574
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
29575
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
29576
+ * - `label` — operator-facing English label.
29577
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
29578
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
29579
+ * PTZ render ONLY enabled entries. Data-driven: the provider
29580
+ * flips it from config, never by editing code.
29581
+ */
29582
+ var NavigationActionEntrySchema = object({
29583
+ id: string(),
29584
+ kind: NavigationEntryKindSchema,
29585
+ label: string(),
29586
+ icon: string(),
29587
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
29588
+ soundId: number().int().optional(),
29589
+ /** Per-device feature flag — render this entry only when true. */
29590
+ enabled: boolean()
29591
+ });
29592
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
29593
+ var NavigationPointSchema = object({
29594
+ x: number(),
29595
+ y: number()
29596
+ });
29597
+ /**
29598
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
29599
+ * The cap reports which are enabled so the UI / PTZ render only the controls
29600
+ * that are turned on for THIS device. Data-driven: the provider derives these
29601
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
29602
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
29603
+ * that are not dictionary entries.
29604
+ *
29605
+ * - `move` / `stop` — the momentary drive joystick.
29606
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
29607
+ * map-coordinate plumbing is wired.
29608
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
29609
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
29610
+ * - `light` — the on/off fill-light toggle (works anytime).
29611
+ * - `lightMode` — the auto/manual selector + manual level slider (a
29612
+ * camera-service control; needs an active stream).
29613
+ */
29614
+ var NavigationFeaturesSchema = object({
29615
+ move: boolean(),
29616
+ stop: boolean(),
29617
+ goToPoint: boolean(),
29618
+ runAction: boolean(),
29619
+ playSound: boolean(),
29620
+ light: boolean(),
29621
+ lightMode: boolean()
29622
+ });
29623
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
29624
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
29625
+ /**
29626
+ * Live navigation state so the UI can reflect what the robot is doing:
29627
+ * - `mode` — coarse activity (idle / cleaning / following / …).
29628
+ * - `following` — person/pet follow is currently armed.
29629
+ * - `flash` — the on-camera fill light is on.
29630
+ * - `lightMode` — auto vs manual fill-light mode.
29631
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
29632
+ * `lightMode === 'manual'`.
29633
+ */
29634
+ var NavigationStatusSchema = object({
29635
+ mode: _enum([
29636
+ "idle",
29637
+ "cleaning",
29638
+ "spot",
29639
+ "following",
29640
+ "goto",
29641
+ "returning",
29642
+ "paused",
29643
+ "unknown"
29644
+ ]),
29645
+ following: boolean(),
29646
+ flash: boolean(),
29647
+ lightMode: NavigationLightModeSchema,
29648
+ lightLevel: number().min(40).max(100),
29649
+ /** Ms epoch when the slice was last updated. */
29650
+ lastChangedAt: number()
29651
+ });
29652
+ /**
29653
+ * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
29654
+ * observable). Adds `lastFetchedAt` on top of the status shape per the
29655
+ * convention.
29656
+ */
29657
+ var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: number() });
29658
+ var navigationCapability = {
29659
+ name: "navigation",
29660
+ scope: "device",
29661
+ deviceNative: true,
29662
+ mode: "singleton",
29663
+ deviceTypes: [DeviceType.Camera],
29664
+ deviceConfig: { ui: {
29665
+ kind: "widget",
29666
+ widgetId: "host/navigation-panel",
29667
+ tab: "navigation",
29668
+ topTab: true,
29669
+ label: "Navigation",
29670
+ order: 0
29671
+ } },
29672
+ methods: {
29673
+ /**
29674
+ * Momentary drive nudge (the robot moves). `protected` — mirrors
29675
+ * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
29676
+ * path) works for any authenticated user, not admin-only. The UI sends
29677
+ * these at ~1 Hz while a control is held; the provider forwards each one to
29678
+ * a single drive write WITHOUT debouncing.
29679
+ */
29680
+ move: method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29681
+ /** Halt all motion immediately (zero drive vector). */
29682
+ stop: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
29683
+ /** Send the robot to a point on its live map. */
29684
+ goToPoint: method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29685
+ /**
29686
+ * Enumerate the discrete controls THIS device supports (data-driven UI +
29687
+ * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
29688
+ */
29689
+ listActions: method(object({ deviceId: number() }), array(NavigationActionEntrySchema)),
29690
+ /**
29691
+ * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
29692
+ * unsupported action ids are rejected by the provider.
29693
+ */
29694
+ runAction: method(object({
29695
+ deviceId: number(),
29696
+ actionId: NavigationActionIdSchema
29697
+ }), _void(), { kind: "mutation" }),
29698
+ /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
29699
+ playSound: method(object({
29700
+ deviceId: number(),
29701
+ soundId: number().int()
29702
+ }), _void(), { kind: "mutation" }),
29703
+ /**
29704
+ * Turn the on-camera fill light on / off (the `OpenFullLight` control —
29705
+ * works anytime, no active stream required).
29706
+ */
29707
+ setLightOn: method(object({
29708
+ deviceId: number(),
29709
+ on: boolean()
29710
+ }), _void(), { kind: "mutation" }),
29711
+ /**
29712
+ * Set the fill-light mode (auto vs manual). `manual` optionally carries the
29713
+ * initial `level`. The auto/manual + level control is a CAMERA-service
29714
+ * action that generally needs an active camera stream/monitor session — the
29715
+ * UI shows the manual level slider ONLY when `mode === 'manual'`.
29716
+ */
29717
+ setLightMode: method(object({
29718
+ deviceId: number(),
29719
+ mode: NavigationLightModeSchema,
29720
+ level: number().min(40).max(100).optional()
29721
+ }), _void(), { kind: "mutation" }),
29722
+ /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
29723
+ setLightLevel: method(object({
29724
+ deviceId: number(),
29725
+ level: number().min(40).max(100)
29726
+ }), _void(), { kind: "mutation" }),
29727
+ /**
29728
+ * Per-device FEATURE-FLAG report for the general primitives — drives which
29729
+ * controls the UI shows (the per-entry flags for the dictionary come back on
29730
+ * `listActions`).
29731
+ */
29732
+ getFeatures: method(object({ deviceId: number() }), NavigationFeaturesSchema)
29733
+ },
29734
+ events: { onStatusChanged: { data: object({
29735
+ deviceId: number(),
29736
+ status: NavigationStatusSchema
29737
+ }) } },
29738
+ status: {
29739
+ schema: NavigationStatusSchema,
29740
+ kind: "push"
29741
+ },
29742
+ /**
29743
+ * Runtime-state slice mirrored by the kernel. The navigation panel watches it
29744
+ * for live mode / follow / flash changes.
29745
+ */
29746
+ runtimeState: NavigationRuntimeStateSchema,
29747
+ /**
29748
+ * Runtime-state durability: **session** — like `vacuum-control`, a restored
29749
+ * `mode: cleaning` / `following: true` is a robot that is not actually doing
29750
+ * that. The live handle re-publishes on connect.
29751
+ *
29752
+ * See `RuntimeStateDurability`. Enforced by
29753
+ * `scripts/check-runtime-state-durability.ts`.
29754
+ */
29755
+ durability: "session"
29756
+ };
29757
+ /**
29471
29758
  * reboot — device-scoped capability for "soft" device reboots (firmware
29472
29759
  * reboot via vendor protocol; cameras, NVRs, doorbells). Surfaces a
29473
29760
  * single mutation so the UI can offer a confirm-and-reboot button for
@@ -32980,6 +33267,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
32980
33267
  motionTrigger: motionTriggerCapability,
32981
33268
  motionZones: motionZonesCapability,
32982
33269
  nativeObjectDetection: nativeObjectDetectionCapability,
33270
+ navigation: navigationCapability,
32983
33271
  networkLink: networkLinkCapability,
32984
33272
  notifier: notifierCapability,
32985
33273
  numericSensor: numericSensorCapability,
@@ -37052,6 +37340,66 @@ Object.freeze({
37052
37340
  addonId: null,
37053
37341
  access: "create"
37054
37342
  },
37343
+ "navigation.getFeatures": {
37344
+ capName: "navigation",
37345
+ capScope: "device",
37346
+ addonId: null,
37347
+ access: "view"
37348
+ },
37349
+ "navigation.goToPoint": {
37350
+ capName: "navigation",
37351
+ capScope: "device",
37352
+ addonId: null,
37353
+ access: "create"
37354
+ },
37355
+ "navigation.listActions": {
37356
+ capName: "navigation",
37357
+ capScope: "device",
37358
+ addonId: null,
37359
+ access: "view"
37360
+ },
37361
+ "navigation.move": {
37362
+ capName: "navigation",
37363
+ capScope: "device",
37364
+ addonId: null,
37365
+ access: "create"
37366
+ },
37367
+ "navigation.playSound": {
37368
+ capName: "navigation",
37369
+ capScope: "device",
37370
+ addonId: null,
37371
+ access: "create"
37372
+ },
37373
+ "navigation.runAction": {
37374
+ capName: "navigation",
37375
+ capScope: "device",
37376
+ addonId: null,
37377
+ access: "create"
37378
+ },
37379
+ "navigation.setLightLevel": {
37380
+ capName: "navigation",
37381
+ capScope: "device",
37382
+ addonId: null,
37383
+ access: "create"
37384
+ },
37385
+ "navigation.setLightMode": {
37386
+ capName: "navigation",
37387
+ capScope: "device",
37388
+ addonId: null,
37389
+ access: "create"
37390
+ },
37391
+ "navigation.setLightOn": {
37392
+ capName: "navigation",
37393
+ capScope: "device",
37394
+ addonId: null,
37395
+ access: "create"
37396
+ },
37397
+ "navigation.stop": {
37398
+ capName: "navigation",
37399
+ capScope: "device",
37400
+ addonId: null,
37401
+ access: "create"
37402
+ },
37055
37403
  "networkAccess.getEndpoint": {
37056
37404
  capName: "network-access",
37057
37405
  capScope: "system",
@@ -41147,6 +41495,56 @@ Object.freeze({
41147
41495
  form: "single",
41148
41496
  optional: false
41149
41497
  }],
41498
+ "navigation.getFeatures": [{
41499
+ name: "deviceId",
41500
+ form: "single",
41501
+ optional: false
41502
+ }],
41503
+ "navigation.goToPoint": [{
41504
+ name: "deviceId",
41505
+ form: "single",
41506
+ optional: false
41507
+ }],
41508
+ "navigation.listActions": [{
41509
+ name: "deviceId",
41510
+ form: "single",
41511
+ optional: false
41512
+ }],
41513
+ "navigation.move": [{
41514
+ name: "deviceId",
41515
+ form: "single",
41516
+ optional: false
41517
+ }],
41518
+ "navigation.playSound": [{
41519
+ name: "deviceId",
41520
+ form: "single",
41521
+ optional: false
41522
+ }],
41523
+ "navigation.runAction": [{
41524
+ name: "deviceId",
41525
+ form: "single",
41526
+ optional: false
41527
+ }],
41528
+ "navigation.setLightLevel": [{
41529
+ name: "deviceId",
41530
+ form: "single",
41531
+ optional: false
41532
+ }],
41533
+ "navigation.setLightMode": [{
41534
+ name: "deviceId",
41535
+ form: "single",
41536
+ optional: false
41537
+ }],
41538
+ "navigation.setLightOn": [{
41539
+ name: "deviceId",
41540
+ form: "single",
41541
+ optional: false
41542
+ }],
41543
+ "navigation.stop": [{
41544
+ name: "deviceId",
41545
+ form: "single",
41546
+ optional: false
41547
+ }],
41150
41548
  "networkQuality.getDeviceStats": [{
41151
41549
  name: "deviceId",
41152
41550
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-hikvision",
3
- "version": "1.2.78",
3
+ "version": "1.2.80",
4
4
  "description": "Hikvision camera device provider addon for CamStack — ISAPI over HTTP(S) with digest auth (snapshot, alarm stream, RTSP discovery)",
5
5
  "keywords": [
6
6
  "camstack",