@camstack/addon-provider-rtsp 1.2.70 → 1.2.72

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
@@ -5430,6 +5430,12 @@ Object.fromEntries([
5430
5430
  icon: "move",
5431
5431
  order: 40
5432
5432
  },
5433
+ {
5434
+ id: "navigation",
5435
+ label: "Navigation",
5436
+ icon: "compass",
5437
+ order: 41
5438
+ },
5433
5439
  {
5434
5440
  id: "consumables",
5435
5441
  label: "Consumables",
@@ -29115,6 +29121,287 @@ var ptzAutotrackCapability = {
29115
29121
  */
29116
29122
  durability: "session"
29117
29123
  };
29124
+ /**
29125
+ * `navigation` — a device-scoped capability that natively expresses the FULL
29126
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
29127
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
29128
+ *
29129
+ * Why a NEW cap rather than overloading `ptz`:
29130
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
29131
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
29132
+ * The two are different physical models: PTZ is absolute-position + presets,
29133
+ * navigation is momentary drive nudges + discrete robot ACTIONS
29134
+ * (dock / spot-clean / follow-pet / go-to-point / …).
29135
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
29136
+ * the reverse:
29137
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
29138
+ * / `getOptions`), and
29139
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
29140
+ * robot camera shows up in the existing PTZ control path without every
29141
+ * PTZ provider learning about robots. The mapping lives in the adapter,
29142
+ * not here (see the addon design note):
29143
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
29144
+ * ptz.stop() → navigation.stop()
29145
+ * ptz.goHome() → navigation.runAction('goHome')
29146
+ * ptz.getPresets() → navigation.listActions() (id→preset)
29147
+ * ptz.goToPreset(id) → navigation.runAction(id)
29148
+ *
29149
+ * ## Continuous drive
29150
+ *
29151
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
29152
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
29153
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
29154
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
29155
+ * coalesce them. The UI owns the cadence.
29156
+ *
29157
+ * ## The action dictionary
29158
+ *
29159
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
29160
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
29161
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
29162
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
29163
+ * vendor-specific list. `kind: 'action'` entries are triggered with
29164
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
29165
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
29166
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
29167
+ *
29168
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
29169
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
29170
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
29171
+ * every device handle. A future nodedreame publish adds a typed
29172
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
29173
+ * provider can then swap the raw calls for the typed methods with no change to
29174
+ * THIS contract.
29175
+ */
29176
+ /**
29177
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
29178
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
29179
+ * halts it.
29180
+ *
29181
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
29182
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
29183
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
29184
+ * vector by it (drivers without proportional drive ignore it).
29185
+ *
29186
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
29187
+ * axis alone; an all-undefined nudge is a no-op.
29188
+ */
29189
+ var NavigationMoveCommandSchema = object({
29190
+ pan: number().min(-1).max(1).optional(),
29191
+ tilt: number().min(-1).max(1).optional(),
29192
+ speed: number().min(0).max(1).optional()
29193
+ });
29194
+ /**
29195
+ * The enumerated discrete actions a navigation-capable robot can perform via
29196
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
29197
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
29198
+ * `playSound` (see the `sound` dictionary entries).
29199
+ */
29200
+ var NavigationActionIdSchema = _enum([
29201
+ "goHome",
29202
+ "locate",
29203
+ "spotClean",
29204
+ "findPet",
29205
+ "personFollow",
29206
+ "stop",
29207
+ "startClean",
29208
+ "pauseClean",
29209
+ "dockWash",
29210
+ "autoEmpty",
29211
+ "flashOn",
29212
+ "flashOff"
29213
+ ]);
29214
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
29215
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
29216
+ /**
29217
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
29218
+ * native panel and the PTZ mimic render as a button.
29219
+ *
29220
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
29221
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
29222
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
29223
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
29224
+ * - `label` — operator-facing English label.
29225
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
29226
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
29227
+ * PTZ render ONLY enabled entries. Data-driven: the provider
29228
+ * flips it from config, never by editing code.
29229
+ */
29230
+ var NavigationActionEntrySchema = object({
29231
+ id: string(),
29232
+ kind: NavigationEntryKindSchema,
29233
+ label: string(),
29234
+ icon: string(),
29235
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
29236
+ soundId: number().int().optional(),
29237
+ /** Per-device feature flag — render this entry only when true. */
29238
+ enabled: boolean()
29239
+ });
29240
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
29241
+ var NavigationPointSchema = object({
29242
+ x: number(),
29243
+ y: number()
29244
+ });
29245
+ /**
29246
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
29247
+ * The cap reports which are enabled so the UI / PTZ render only the controls
29248
+ * that are turned on for THIS device. Data-driven: the provider derives these
29249
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
29250
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
29251
+ * that are not dictionary entries.
29252
+ *
29253
+ * - `move` / `stop` — the momentary drive joystick.
29254
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
29255
+ * map-coordinate plumbing is wired.
29256
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
29257
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
29258
+ * - `light` — the on/off fill-light toggle (works anytime).
29259
+ * - `lightMode` — the auto/manual selector + manual level slider (a
29260
+ * camera-service control; needs an active stream).
29261
+ */
29262
+ var NavigationFeaturesSchema = object({
29263
+ move: boolean(),
29264
+ stop: boolean(),
29265
+ goToPoint: boolean(),
29266
+ runAction: boolean(),
29267
+ playSound: boolean(),
29268
+ light: boolean(),
29269
+ lightMode: boolean()
29270
+ });
29271
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
29272
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
29273
+ /**
29274
+ * Live navigation state so the UI can reflect what the robot is doing:
29275
+ * - `mode` — coarse activity (idle / cleaning / following / …).
29276
+ * - `following` — person/pet follow is currently armed.
29277
+ * - `flash` — the on-camera fill light is on.
29278
+ * - `lightMode` — auto vs manual fill-light mode.
29279
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
29280
+ * `lightMode === 'manual'`.
29281
+ */
29282
+ var NavigationStatusSchema = object({
29283
+ mode: _enum([
29284
+ "idle",
29285
+ "cleaning",
29286
+ "spot",
29287
+ "following",
29288
+ "goto",
29289
+ "returning",
29290
+ "paused",
29291
+ "unknown"
29292
+ ]),
29293
+ following: boolean(),
29294
+ flash: boolean(),
29295
+ lightMode: NavigationLightModeSchema,
29296
+ lightLevel: number().min(40).max(100),
29297
+ /** Ms epoch when the slice was last updated. */
29298
+ lastChangedAt: number()
29299
+ });
29300
+ /**
29301
+ * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
29302
+ * observable). Adds `lastFetchedAt` on top of the status shape per the
29303
+ * convention.
29304
+ */
29305
+ var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: number() });
29306
+ var navigationCapability = {
29307
+ name: "navigation",
29308
+ scope: "device",
29309
+ deviceNative: true,
29310
+ mode: "singleton",
29311
+ deviceTypes: [DeviceType.Camera],
29312
+ deviceConfig: { ui: {
29313
+ kind: "widget",
29314
+ widgetId: "host/navigation-panel",
29315
+ tab: "navigation",
29316
+ topTab: true,
29317
+ label: "Navigation",
29318
+ order: 0
29319
+ } },
29320
+ methods: {
29321
+ /**
29322
+ * Momentary drive nudge (the robot moves). `protected` — mirrors
29323
+ * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
29324
+ * path) works for any authenticated user, not admin-only. The UI sends
29325
+ * these at ~1 Hz while a control is held; the provider forwards each one to
29326
+ * a single drive write WITHOUT debouncing.
29327
+ */
29328
+ move: method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29329
+ /** Halt all motion immediately (zero drive vector). */
29330
+ stop: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
29331
+ /** Send the robot to a point on its live map. */
29332
+ goToPoint: method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29333
+ /**
29334
+ * Enumerate the discrete controls THIS device supports (data-driven UI +
29335
+ * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
29336
+ */
29337
+ listActions: method(object({ deviceId: number() }), array(NavigationActionEntrySchema)),
29338
+ /**
29339
+ * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
29340
+ * unsupported action ids are rejected by the provider.
29341
+ */
29342
+ runAction: method(object({
29343
+ deviceId: number(),
29344
+ actionId: NavigationActionIdSchema
29345
+ }), _void(), { kind: "mutation" }),
29346
+ /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
29347
+ playSound: method(object({
29348
+ deviceId: number(),
29349
+ soundId: number().int()
29350
+ }), _void(), { kind: "mutation" }),
29351
+ /**
29352
+ * Turn the on-camera fill light on / off (the `OpenFullLight` control —
29353
+ * works anytime, no active stream required).
29354
+ */
29355
+ setLightOn: method(object({
29356
+ deviceId: number(),
29357
+ on: boolean()
29358
+ }), _void(), { kind: "mutation" }),
29359
+ /**
29360
+ * Set the fill-light mode (auto vs manual). `manual` optionally carries the
29361
+ * initial `level`. The auto/manual + level control is a CAMERA-service
29362
+ * action that generally needs an active camera stream/monitor session — the
29363
+ * UI shows the manual level slider ONLY when `mode === 'manual'`.
29364
+ */
29365
+ setLightMode: method(object({
29366
+ deviceId: number(),
29367
+ mode: NavigationLightModeSchema,
29368
+ level: number().min(40).max(100).optional()
29369
+ }), _void(), { kind: "mutation" }),
29370
+ /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
29371
+ setLightLevel: method(object({
29372
+ deviceId: number(),
29373
+ level: number().min(40).max(100)
29374
+ }), _void(), { kind: "mutation" }),
29375
+ /**
29376
+ * Per-device FEATURE-FLAG report for the general primitives — drives which
29377
+ * controls the UI shows (the per-entry flags for the dictionary come back on
29378
+ * `listActions`).
29379
+ */
29380
+ getFeatures: method(object({ deviceId: number() }), NavigationFeaturesSchema)
29381
+ },
29382
+ events: { onStatusChanged: { data: object({
29383
+ deviceId: number(),
29384
+ status: NavigationStatusSchema
29385
+ }) } },
29386
+ status: {
29387
+ schema: NavigationStatusSchema,
29388
+ kind: "push"
29389
+ },
29390
+ /**
29391
+ * Runtime-state slice mirrored by the kernel. The navigation panel watches it
29392
+ * for live mode / follow / flash changes.
29393
+ */
29394
+ runtimeState: NavigationRuntimeStateSchema,
29395
+ /**
29396
+ * Runtime-state durability: **session** — like `vacuum-control`, a restored
29397
+ * `mode: cleaning` / `following: true` is a robot that is not actually doing
29398
+ * that. The live handle re-publishes on connect.
29399
+ *
29400
+ * See `RuntimeStateDurability`. Enforced by
29401
+ * `scripts/check-runtime-state-durability.ts`.
29402
+ */
29403
+ durability: "session"
29404
+ };
29118
29405
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
29119
29406
  kind: "mutation",
29120
29407
  auth: "admin"
@@ -32390,6 +32677,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
32390
32677
  motionTrigger: motionTriggerCapability,
32391
32678
  motionZones: motionZonesCapability,
32392
32679
  nativeObjectDetection: nativeObjectDetectionCapability,
32680
+ navigation: navigationCapability,
32393
32681
  networkLink: networkLinkCapability,
32394
32682
  notifier: notifierCapability,
32395
32683
  numericSensor: numericSensorCapability,
@@ -36310,6 +36598,66 @@ Object.freeze({
36310
36598
  addonId: null,
36311
36599
  access: "create"
36312
36600
  },
36601
+ "navigation.getFeatures": {
36602
+ capName: "navigation",
36603
+ capScope: "device",
36604
+ addonId: null,
36605
+ access: "view"
36606
+ },
36607
+ "navigation.goToPoint": {
36608
+ capName: "navigation",
36609
+ capScope: "device",
36610
+ addonId: null,
36611
+ access: "create"
36612
+ },
36613
+ "navigation.listActions": {
36614
+ capName: "navigation",
36615
+ capScope: "device",
36616
+ addonId: null,
36617
+ access: "view"
36618
+ },
36619
+ "navigation.move": {
36620
+ capName: "navigation",
36621
+ capScope: "device",
36622
+ addonId: null,
36623
+ access: "create"
36624
+ },
36625
+ "navigation.playSound": {
36626
+ capName: "navigation",
36627
+ capScope: "device",
36628
+ addonId: null,
36629
+ access: "create"
36630
+ },
36631
+ "navigation.runAction": {
36632
+ capName: "navigation",
36633
+ capScope: "device",
36634
+ addonId: null,
36635
+ access: "create"
36636
+ },
36637
+ "navigation.setLightLevel": {
36638
+ capName: "navigation",
36639
+ capScope: "device",
36640
+ addonId: null,
36641
+ access: "create"
36642
+ },
36643
+ "navigation.setLightMode": {
36644
+ capName: "navigation",
36645
+ capScope: "device",
36646
+ addonId: null,
36647
+ access: "create"
36648
+ },
36649
+ "navigation.setLightOn": {
36650
+ capName: "navigation",
36651
+ capScope: "device",
36652
+ addonId: null,
36653
+ access: "create"
36654
+ },
36655
+ "navigation.stop": {
36656
+ capName: "navigation",
36657
+ capScope: "device",
36658
+ addonId: null,
36659
+ access: "create"
36660
+ },
36313
36661
  "networkAccess.getEndpoint": {
36314
36662
  capName: "network-access",
36315
36663
  capScope: "system",
@@ -40405,6 +40753,56 @@ Object.freeze({
40405
40753
  form: "single",
40406
40754
  optional: false
40407
40755
  }],
40756
+ "navigation.getFeatures": [{
40757
+ name: "deviceId",
40758
+ form: "single",
40759
+ optional: false
40760
+ }],
40761
+ "navigation.goToPoint": [{
40762
+ name: "deviceId",
40763
+ form: "single",
40764
+ optional: false
40765
+ }],
40766
+ "navigation.listActions": [{
40767
+ name: "deviceId",
40768
+ form: "single",
40769
+ optional: false
40770
+ }],
40771
+ "navigation.move": [{
40772
+ name: "deviceId",
40773
+ form: "single",
40774
+ optional: false
40775
+ }],
40776
+ "navigation.playSound": [{
40777
+ name: "deviceId",
40778
+ form: "single",
40779
+ optional: false
40780
+ }],
40781
+ "navigation.runAction": [{
40782
+ name: "deviceId",
40783
+ form: "single",
40784
+ optional: false
40785
+ }],
40786
+ "navigation.setLightLevel": [{
40787
+ name: "deviceId",
40788
+ form: "single",
40789
+ optional: false
40790
+ }],
40791
+ "navigation.setLightMode": [{
40792
+ name: "deviceId",
40793
+ form: "single",
40794
+ optional: false
40795
+ }],
40796
+ "navigation.setLightOn": [{
40797
+ name: "deviceId",
40798
+ form: "single",
40799
+ optional: false
40800
+ }],
40801
+ "navigation.stop": [{
40802
+ name: "deviceId",
40803
+ form: "single",
40804
+ optional: false
40805
+ }],
40408
40806
  "networkQuality.getDeviceStats": [{
40409
40807
  name: "deviceId",
40410
40808
  form: "single",
package/dist/addon.mjs CHANGED
@@ -5406,6 +5406,12 @@ Object.fromEntries([
5406
5406
  icon: "move",
5407
5407
  order: 40
5408
5408
  },
5409
+ {
5410
+ id: "navigation",
5411
+ label: "Navigation",
5412
+ icon: "compass",
5413
+ order: 41
5414
+ },
5409
5415
  {
5410
5416
  id: "consumables",
5411
5417
  label: "Consumables",
@@ -29091,6 +29097,287 @@ var ptzAutotrackCapability = {
29091
29097
  */
29092
29098
  durability: "session"
29093
29099
  };
29100
+ /**
29101
+ * `navigation` — a device-scoped capability that natively expresses the FULL
29102
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
29103
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
29104
+ *
29105
+ * Why a NEW cap rather than overloading `ptz`:
29106
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
29107
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
29108
+ * The two are different physical models: PTZ is absolute-position + presets,
29109
+ * navigation is momentary drive nudges + discrete robot ACTIONS
29110
+ * (dock / spot-clean / follow-pet / go-to-point / …).
29111
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
29112
+ * the reverse:
29113
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
29114
+ * / `getOptions`), and
29115
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
29116
+ * robot camera shows up in the existing PTZ control path without every
29117
+ * PTZ provider learning about robots. The mapping lives in the adapter,
29118
+ * not here (see the addon design note):
29119
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
29120
+ * ptz.stop() → navigation.stop()
29121
+ * ptz.goHome() → navigation.runAction('goHome')
29122
+ * ptz.getPresets() → navigation.listActions() (id→preset)
29123
+ * ptz.goToPreset(id) → navigation.runAction(id)
29124
+ *
29125
+ * ## Continuous drive
29126
+ *
29127
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
29128
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
29129
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
29130
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
29131
+ * coalesce them. The UI owns the cadence.
29132
+ *
29133
+ * ## The action dictionary
29134
+ *
29135
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
29136
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
29137
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
29138
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
29139
+ * vendor-specific list. `kind: 'action'` entries are triggered with
29140
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
29141
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
29142
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
29143
+ *
29144
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
29145
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
29146
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
29147
+ * every device handle. A future nodedreame publish adds a typed
29148
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
29149
+ * provider can then swap the raw calls for the typed methods with no change to
29150
+ * THIS contract.
29151
+ */
29152
+ /**
29153
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
29154
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
29155
+ * halts it.
29156
+ *
29157
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
29158
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
29159
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
29160
+ * vector by it (drivers without proportional drive ignore it).
29161
+ *
29162
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
29163
+ * axis alone; an all-undefined nudge is a no-op.
29164
+ */
29165
+ var NavigationMoveCommandSchema = object({
29166
+ pan: number().min(-1).max(1).optional(),
29167
+ tilt: number().min(-1).max(1).optional(),
29168
+ speed: number().min(0).max(1).optional()
29169
+ });
29170
+ /**
29171
+ * The enumerated discrete actions a navigation-capable robot can perform via
29172
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
29173
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
29174
+ * `playSound` (see the `sound` dictionary entries).
29175
+ */
29176
+ var NavigationActionIdSchema = _enum([
29177
+ "goHome",
29178
+ "locate",
29179
+ "spotClean",
29180
+ "findPet",
29181
+ "personFollow",
29182
+ "stop",
29183
+ "startClean",
29184
+ "pauseClean",
29185
+ "dockWash",
29186
+ "autoEmpty",
29187
+ "flashOn",
29188
+ "flashOff"
29189
+ ]);
29190
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
29191
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
29192
+ /**
29193
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
29194
+ * native panel and the PTZ mimic render as a button.
29195
+ *
29196
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
29197
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
29198
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
29199
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
29200
+ * - `label` — operator-facing English label.
29201
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
29202
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
29203
+ * PTZ render ONLY enabled entries. Data-driven: the provider
29204
+ * flips it from config, never by editing code.
29205
+ */
29206
+ var NavigationActionEntrySchema = object({
29207
+ id: string(),
29208
+ kind: NavigationEntryKindSchema,
29209
+ label: string(),
29210
+ icon: string(),
29211
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
29212
+ soundId: number().int().optional(),
29213
+ /** Per-device feature flag — render this entry only when true. */
29214
+ enabled: boolean()
29215
+ });
29216
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
29217
+ var NavigationPointSchema = object({
29218
+ x: number(),
29219
+ y: number()
29220
+ });
29221
+ /**
29222
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
29223
+ * The cap reports which are enabled so the UI / PTZ render only the controls
29224
+ * that are turned on for THIS device. Data-driven: the provider derives these
29225
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
29226
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
29227
+ * that are not dictionary entries.
29228
+ *
29229
+ * - `move` / `stop` — the momentary drive joystick.
29230
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
29231
+ * map-coordinate plumbing is wired.
29232
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
29233
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
29234
+ * - `light` — the on/off fill-light toggle (works anytime).
29235
+ * - `lightMode` — the auto/manual selector + manual level slider (a
29236
+ * camera-service control; needs an active stream).
29237
+ */
29238
+ var NavigationFeaturesSchema = object({
29239
+ move: boolean(),
29240
+ stop: boolean(),
29241
+ goToPoint: boolean(),
29242
+ runAction: boolean(),
29243
+ playSound: boolean(),
29244
+ light: boolean(),
29245
+ lightMode: boolean()
29246
+ });
29247
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
29248
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
29249
+ /**
29250
+ * Live navigation state so the UI can reflect what the robot is doing:
29251
+ * - `mode` — coarse activity (idle / cleaning / following / …).
29252
+ * - `following` — person/pet follow is currently armed.
29253
+ * - `flash` — the on-camera fill light is on.
29254
+ * - `lightMode` — auto vs manual fill-light mode.
29255
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
29256
+ * `lightMode === 'manual'`.
29257
+ */
29258
+ var NavigationStatusSchema = object({
29259
+ mode: _enum([
29260
+ "idle",
29261
+ "cleaning",
29262
+ "spot",
29263
+ "following",
29264
+ "goto",
29265
+ "returning",
29266
+ "paused",
29267
+ "unknown"
29268
+ ]),
29269
+ following: boolean(),
29270
+ flash: boolean(),
29271
+ lightMode: NavigationLightModeSchema,
29272
+ lightLevel: number().min(40).max(100),
29273
+ /** Ms epoch when the slice was last updated. */
29274
+ lastChangedAt: number()
29275
+ });
29276
+ /**
29277
+ * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
29278
+ * observable). Adds `lastFetchedAt` on top of the status shape per the
29279
+ * convention.
29280
+ */
29281
+ var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: number() });
29282
+ var navigationCapability = {
29283
+ name: "navigation",
29284
+ scope: "device",
29285
+ deviceNative: true,
29286
+ mode: "singleton",
29287
+ deviceTypes: [DeviceType.Camera],
29288
+ deviceConfig: { ui: {
29289
+ kind: "widget",
29290
+ widgetId: "host/navigation-panel",
29291
+ tab: "navigation",
29292
+ topTab: true,
29293
+ label: "Navigation",
29294
+ order: 0
29295
+ } },
29296
+ methods: {
29297
+ /**
29298
+ * Momentary drive nudge (the robot moves). `protected` — mirrors
29299
+ * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
29300
+ * path) works for any authenticated user, not admin-only. The UI sends
29301
+ * these at ~1 Hz while a control is held; the provider forwards each one to
29302
+ * a single drive write WITHOUT debouncing.
29303
+ */
29304
+ move: method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29305
+ /** Halt all motion immediately (zero drive vector). */
29306
+ stop: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
29307
+ /** Send the robot to a point on its live map. */
29308
+ goToPoint: method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29309
+ /**
29310
+ * Enumerate the discrete controls THIS device supports (data-driven UI +
29311
+ * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
29312
+ */
29313
+ listActions: method(object({ deviceId: number() }), array(NavigationActionEntrySchema)),
29314
+ /**
29315
+ * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
29316
+ * unsupported action ids are rejected by the provider.
29317
+ */
29318
+ runAction: method(object({
29319
+ deviceId: number(),
29320
+ actionId: NavigationActionIdSchema
29321
+ }), _void(), { kind: "mutation" }),
29322
+ /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
29323
+ playSound: method(object({
29324
+ deviceId: number(),
29325
+ soundId: number().int()
29326
+ }), _void(), { kind: "mutation" }),
29327
+ /**
29328
+ * Turn the on-camera fill light on / off (the `OpenFullLight` control —
29329
+ * works anytime, no active stream required).
29330
+ */
29331
+ setLightOn: method(object({
29332
+ deviceId: number(),
29333
+ on: boolean()
29334
+ }), _void(), { kind: "mutation" }),
29335
+ /**
29336
+ * Set the fill-light mode (auto vs manual). `manual` optionally carries the
29337
+ * initial `level`. The auto/manual + level control is a CAMERA-service
29338
+ * action that generally needs an active camera stream/monitor session — the
29339
+ * UI shows the manual level slider ONLY when `mode === 'manual'`.
29340
+ */
29341
+ setLightMode: method(object({
29342
+ deviceId: number(),
29343
+ mode: NavigationLightModeSchema,
29344
+ level: number().min(40).max(100).optional()
29345
+ }), _void(), { kind: "mutation" }),
29346
+ /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
29347
+ setLightLevel: method(object({
29348
+ deviceId: number(),
29349
+ level: number().min(40).max(100)
29350
+ }), _void(), { kind: "mutation" }),
29351
+ /**
29352
+ * Per-device FEATURE-FLAG report for the general primitives — drives which
29353
+ * controls the UI shows (the per-entry flags for the dictionary come back on
29354
+ * `listActions`).
29355
+ */
29356
+ getFeatures: method(object({ deviceId: number() }), NavigationFeaturesSchema)
29357
+ },
29358
+ events: { onStatusChanged: { data: object({
29359
+ deviceId: number(),
29360
+ status: NavigationStatusSchema
29361
+ }) } },
29362
+ status: {
29363
+ schema: NavigationStatusSchema,
29364
+ kind: "push"
29365
+ },
29366
+ /**
29367
+ * Runtime-state slice mirrored by the kernel. The navigation panel watches it
29368
+ * for live mode / follow / flash changes.
29369
+ */
29370
+ runtimeState: NavigationRuntimeStateSchema,
29371
+ /**
29372
+ * Runtime-state durability: **session** — like `vacuum-control`, a restored
29373
+ * `mode: cleaning` / `following: true` is a robot that is not actually doing
29374
+ * that. The live handle re-publishes on connect.
29375
+ *
29376
+ * See `RuntimeStateDurability`. Enforced by
29377
+ * `scripts/check-runtime-state-durability.ts`.
29378
+ */
29379
+ durability: "session"
29380
+ };
29094
29381
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
29095
29382
  kind: "mutation",
29096
29383
  auth: "admin"
@@ -32366,6 +32653,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
32366
32653
  motionTrigger: motionTriggerCapability,
32367
32654
  motionZones: motionZonesCapability,
32368
32655
  nativeObjectDetection: nativeObjectDetectionCapability,
32656
+ navigation: navigationCapability,
32369
32657
  networkLink: networkLinkCapability,
32370
32658
  notifier: notifierCapability,
32371
32659
  numericSensor: numericSensorCapability,
@@ -36286,6 +36574,66 @@ Object.freeze({
36286
36574
  addonId: null,
36287
36575
  access: "create"
36288
36576
  },
36577
+ "navigation.getFeatures": {
36578
+ capName: "navigation",
36579
+ capScope: "device",
36580
+ addonId: null,
36581
+ access: "view"
36582
+ },
36583
+ "navigation.goToPoint": {
36584
+ capName: "navigation",
36585
+ capScope: "device",
36586
+ addonId: null,
36587
+ access: "create"
36588
+ },
36589
+ "navigation.listActions": {
36590
+ capName: "navigation",
36591
+ capScope: "device",
36592
+ addonId: null,
36593
+ access: "view"
36594
+ },
36595
+ "navigation.move": {
36596
+ capName: "navigation",
36597
+ capScope: "device",
36598
+ addonId: null,
36599
+ access: "create"
36600
+ },
36601
+ "navigation.playSound": {
36602
+ capName: "navigation",
36603
+ capScope: "device",
36604
+ addonId: null,
36605
+ access: "create"
36606
+ },
36607
+ "navigation.runAction": {
36608
+ capName: "navigation",
36609
+ capScope: "device",
36610
+ addonId: null,
36611
+ access: "create"
36612
+ },
36613
+ "navigation.setLightLevel": {
36614
+ capName: "navigation",
36615
+ capScope: "device",
36616
+ addonId: null,
36617
+ access: "create"
36618
+ },
36619
+ "navigation.setLightMode": {
36620
+ capName: "navigation",
36621
+ capScope: "device",
36622
+ addonId: null,
36623
+ access: "create"
36624
+ },
36625
+ "navigation.setLightOn": {
36626
+ capName: "navigation",
36627
+ capScope: "device",
36628
+ addonId: null,
36629
+ access: "create"
36630
+ },
36631
+ "navigation.stop": {
36632
+ capName: "navigation",
36633
+ capScope: "device",
36634
+ addonId: null,
36635
+ access: "create"
36636
+ },
36289
36637
  "networkAccess.getEndpoint": {
36290
36638
  capName: "network-access",
36291
36639
  capScope: "system",
@@ -40381,6 +40729,56 @@ Object.freeze({
40381
40729
  form: "single",
40382
40730
  optional: false
40383
40731
  }],
40732
+ "navigation.getFeatures": [{
40733
+ name: "deviceId",
40734
+ form: "single",
40735
+ optional: false
40736
+ }],
40737
+ "navigation.goToPoint": [{
40738
+ name: "deviceId",
40739
+ form: "single",
40740
+ optional: false
40741
+ }],
40742
+ "navigation.listActions": [{
40743
+ name: "deviceId",
40744
+ form: "single",
40745
+ optional: false
40746
+ }],
40747
+ "navigation.move": [{
40748
+ name: "deviceId",
40749
+ form: "single",
40750
+ optional: false
40751
+ }],
40752
+ "navigation.playSound": [{
40753
+ name: "deviceId",
40754
+ form: "single",
40755
+ optional: false
40756
+ }],
40757
+ "navigation.runAction": [{
40758
+ name: "deviceId",
40759
+ form: "single",
40760
+ optional: false
40761
+ }],
40762
+ "navigation.setLightLevel": [{
40763
+ name: "deviceId",
40764
+ form: "single",
40765
+ optional: false
40766
+ }],
40767
+ "navigation.setLightMode": [{
40768
+ name: "deviceId",
40769
+ form: "single",
40770
+ optional: false
40771
+ }],
40772
+ "navigation.setLightOn": [{
40773
+ name: "deviceId",
40774
+ form: "single",
40775
+ optional: false
40776
+ }],
40777
+ "navigation.stop": [{
40778
+ name: "deviceId",
40779
+ form: "single",
40780
+ optional: false
40781
+ }],
40384
40782
  "networkQuality.getDeviceStats": [{
40385
40783
  name: "deviceId",
40386
40784
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-rtsp",
3
- "version": "1.2.70",
3
+ "version": "1.2.72",
4
4
  "description": "Generic RTSP camera device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",