@camstack/addon-provider-amcrest 0.2.72 → 0.2.74

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",
@@ -29154,6 +29160,287 @@ var ptzAutotrackCapability = {
29154
29160
  */
29155
29161
  durability: "session"
29156
29162
  };
29163
+ /**
29164
+ * `navigation` — a device-scoped capability that natively expresses the FULL
29165
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
29166
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
29167
+ *
29168
+ * Why a NEW cap rather than overloading `ptz`:
29169
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
29170
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
29171
+ * The two are different physical models: PTZ is absolute-position + presets,
29172
+ * navigation is momentary drive nudges + discrete robot ACTIONS
29173
+ * (dock / spot-clean / follow-pet / go-to-point / …).
29174
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
29175
+ * the reverse:
29176
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
29177
+ * / `getOptions`), and
29178
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
29179
+ * robot camera shows up in the existing PTZ control path without every
29180
+ * PTZ provider learning about robots. The mapping lives in the adapter,
29181
+ * not here (see the addon design note):
29182
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
29183
+ * ptz.stop() → navigation.stop()
29184
+ * ptz.goHome() → navigation.runAction('goHome')
29185
+ * ptz.getPresets() → navigation.listActions() (id→preset)
29186
+ * ptz.goToPreset(id) → navigation.runAction(id)
29187
+ *
29188
+ * ## Continuous drive
29189
+ *
29190
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
29191
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
29192
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
29193
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
29194
+ * coalesce them. The UI owns the cadence.
29195
+ *
29196
+ * ## The action dictionary
29197
+ *
29198
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
29199
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
29200
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
29201
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
29202
+ * vendor-specific list. `kind: 'action'` entries are triggered with
29203
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
29204
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
29205
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
29206
+ *
29207
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
29208
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
29209
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
29210
+ * every device handle. A future nodedreame publish adds a typed
29211
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
29212
+ * provider can then swap the raw calls for the typed methods with no change to
29213
+ * THIS contract.
29214
+ */
29215
+ /**
29216
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
29217
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
29218
+ * halts it.
29219
+ *
29220
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
29221
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
29222
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
29223
+ * vector by it (drivers without proportional drive ignore it).
29224
+ *
29225
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
29226
+ * axis alone; an all-undefined nudge is a no-op.
29227
+ */
29228
+ var NavigationMoveCommandSchema = object({
29229
+ pan: number().min(-1).max(1).optional(),
29230
+ tilt: number().min(-1).max(1).optional(),
29231
+ speed: number().min(0).max(1).optional()
29232
+ });
29233
+ /**
29234
+ * The enumerated discrete actions a navigation-capable robot can perform via
29235
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
29236
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
29237
+ * `playSound` (see the `sound` dictionary entries).
29238
+ */
29239
+ var NavigationActionIdSchema = _enum([
29240
+ "goHome",
29241
+ "locate",
29242
+ "spotClean",
29243
+ "findPet",
29244
+ "personFollow",
29245
+ "stop",
29246
+ "startClean",
29247
+ "pauseClean",
29248
+ "dockWash",
29249
+ "autoEmpty",
29250
+ "flashOn",
29251
+ "flashOff"
29252
+ ]);
29253
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
29254
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
29255
+ /**
29256
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
29257
+ * native panel and the PTZ mimic render as a button.
29258
+ *
29259
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
29260
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
29261
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
29262
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
29263
+ * - `label` — operator-facing English label.
29264
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
29265
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
29266
+ * PTZ render ONLY enabled entries. Data-driven: the provider
29267
+ * flips it from config, never by editing code.
29268
+ */
29269
+ var NavigationActionEntrySchema = object({
29270
+ id: string(),
29271
+ kind: NavigationEntryKindSchema,
29272
+ label: string(),
29273
+ icon: string(),
29274
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
29275
+ soundId: number().int().optional(),
29276
+ /** Per-device feature flag — render this entry only when true. */
29277
+ enabled: boolean()
29278
+ });
29279
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
29280
+ var NavigationPointSchema = object({
29281
+ x: number(),
29282
+ y: number()
29283
+ });
29284
+ /**
29285
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
29286
+ * The cap reports which are enabled so the UI / PTZ render only the controls
29287
+ * that are turned on for THIS device. Data-driven: the provider derives these
29288
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
29289
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
29290
+ * that are not dictionary entries.
29291
+ *
29292
+ * - `move` / `stop` — the momentary drive joystick.
29293
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
29294
+ * map-coordinate plumbing is wired.
29295
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
29296
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
29297
+ * - `light` — the on/off fill-light toggle (works anytime).
29298
+ * - `lightMode` — the auto/manual selector + manual level slider (a
29299
+ * camera-service control; needs an active stream).
29300
+ */
29301
+ var NavigationFeaturesSchema = object({
29302
+ move: boolean(),
29303
+ stop: boolean(),
29304
+ goToPoint: boolean(),
29305
+ runAction: boolean(),
29306
+ playSound: boolean(),
29307
+ light: boolean(),
29308
+ lightMode: boolean()
29309
+ });
29310
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
29311
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
29312
+ /**
29313
+ * Live navigation state so the UI can reflect what the robot is doing:
29314
+ * - `mode` — coarse activity (idle / cleaning / following / …).
29315
+ * - `following` — person/pet follow is currently armed.
29316
+ * - `flash` — the on-camera fill light is on.
29317
+ * - `lightMode` — auto vs manual fill-light mode.
29318
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
29319
+ * `lightMode === 'manual'`.
29320
+ */
29321
+ var NavigationStatusSchema = object({
29322
+ mode: _enum([
29323
+ "idle",
29324
+ "cleaning",
29325
+ "spot",
29326
+ "following",
29327
+ "goto",
29328
+ "returning",
29329
+ "paused",
29330
+ "unknown"
29331
+ ]),
29332
+ following: boolean(),
29333
+ flash: boolean(),
29334
+ lightMode: NavigationLightModeSchema,
29335
+ lightLevel: number().min(40).max(100),
29336
+ /** Ms epoch when the slice was last updated. */
29337
+ lastChangedAt: number()
29338
+ });
29339
+ /**
29340
+ * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
29341
+ * observable). Adds `lastFetchedAt` on top of the status shape per the
29342
+ * convention.
29343
+ */
29344
+ var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: number() });
29345
+ var navigationCapability = {
29346
+ name: "navigation",
29347
+ scope: "device",
29348
+ deviceNative: true,
29349
+ mode: "singleton",
29350
+ deviceTypes: [DeviceType.Camera],
29351
+ deviceConfig: { ui: {
29352
+ kind: "widget",
29353
+ widgetId: "host/navigation-panel",
29354
+ tab: "navigation",
29355
+ topTab: true,
29356
+ label: "Navigation",
29357
+ order: 0
29358
+ } },
29359
+ methods: {
29360
+ /**
29361
+ * Momentary drive nudge (the robot moves). `protected` — mirrors
29362
+ * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
29363
+ * path) works for any authenticated user, not admin-only. The UI sends
29364
+ * these at ~1 Hz while a control is held; the provider forwards each one to
29365
+ * a single drive write WITHOUT debouncing.
29366
+ */
29367
+ move: method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29368
+ /** Halt all motion immediately (zero drive vector). */
29369
+ stop: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
29370
+ /** Send the robot to a point on its live map. */
29371
+ goToPoint: method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29372
+ /**
29373
+ * Enumerate the discrete controls THIS device supports (data-driven UI +
29374
+ * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
29375
+ */
29376
+ listActions: method(object({ deviceId: number() }), array(NavigationActionEntrySchema)),
29377
+ /**
29378
+ * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
29379
+ * unsupported action ids are rejected by the provider.
29380
+ */
29381
+ runAction: method(object({
29382
+ deviceId: number(),
29383
+ actionId: NavigationActionIdSchema
29384
+ }), _void(), { kind: "mutation" }),
29385
+ /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
29386
+ playSound: method(object({
29387
+ deviceId: number(),
29388
+ soundId: number().int()
29389
+ }), _void(), { kind: "mutation" }),
29390
+ /**
29391
+ * Turn the on-camera fill light on / off (the `OpenFullLight` control —
29392
+ * works anytime, no active stream required).
29393
+ */
29394
+ setLightOn: method(object({
29395
+ deviceId: number(),
29396
+ on: boolean()
29397
+ }), _void(), { kind: "mutation" }),
29398
+ /**
29399
+ * Set the fill-light mode (auto vs manual). `manual` optionally carries the
29400
+ * initial `level`. The auto/manual + level control is a CAMERA-service
29401
+ * action that generally needs an active camera stream/monitor session — the
29402
+ * UI shows the manual level slider ONLY when `mode === 'manual'`.
29403
+ */
29404
+ setLightMode: method(object({
29405
+ deviceId: number(),
29406
+ mode: NavigationLightModeSchema,
29407
+ level: number().min(40).max(100).optional()
29408
+ }), _void(), { kind: "mutation" }),
29409
+ /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
29410
+ setLightLevel: method(object({
29411
+ deviceId: number(),
29412
+ level: number().min(40).max(100)
29413
+ }), _void(), { kind: "mutation" }),
29414
+ /**
29415
+ * Per-device FEATURE-FLAG report for the general primitives — drives which
29416
+ * controls the UI shows (the per-entry flags for the dictionary come back on
29417
+ * `listActions`).
29418
+ */
29419
+ getFeatures: method(object({ deviceId: number() }), NavigationFeaturesSchema)
29420
+ },
29421
+ events: { onStatusChanged: { data: object({
29422
+ deviceId: number(),
29423
+ status: NavigationStatusSchema
29424
+ }) } },
29425
+ status: {
29426
+ schema: NavigationStatusSchema,
29427
+ kind: "push"
29428
+ },
29429
+ /**
29430
+ * Runtime-state slice mirrored by the kernel. The navigation panel watches it
29431
+ * for live mode / follow / flash changes.
29432
+ */
29433
+ runtimeState: NavigationRuntimeStateSchema,
29434
+ /**
29435
+ * Runtime-state durability: **session** — like `vacuum-control`, a restored
29436
+ * `mode: cleaning` / `following: true` is a robot that is not actually doing
29437
+ * that. The live handle re-publishes on connect.
29438
+ *
29439
+ * See `RuntimeStateDurability`. Enforced by
29440
+ * `scripts/check-runtime-state-durability.ts`.
29441
+ */
29442
+ durability: "session"
29443
+ };
29157
29444
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
29158
29445
  kind: "mutation",
29159
29446
  auth: "admin"
@@ -32627,6 +32914,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
32627
32914
  motionTrigger: motionTriggerCapability,
32628
32915
  motionZones: motionZonesCapability,
32629
32916
  nativeObjectDetection: nativeObjectDetectionCapability,
32917
+ navigation: navigationCapability,
32630
32918
  networkLink: networkLinkCapability,
32631
32919
  notifier: notifierCapability,
32632
32920
  numericSensor: numericSensorCapability,
@@ -36687,6 +36975,66 @@ Object.freeze({
36687
36975
  addonId: null,
36688
36976
  access: "create"
36689
36977
  },
36978
+ "navigation.getFeatures": {
36979
+ capName: "navigation",
36980
+ capScope: "device",
36981
+ addonId: null,
36982
+ access: "view"
36983
+ },
36984
+ "navigation.goToPoint": {
36985
+ capName: "navigation",
36986
+ capScope: "device",
36987
+ addonId: null,
36988
+ access: "create"
36989
+ },
36990
+ "navigation.listActions": {
36991
+ capName: "navigation",
36992
+ capScope: "device",
36993
+ addonId: null,
36994
+ access: "view"
36995
+ },
36996
+ "navigation.move": {
36997
+ capName: "navigation",
36998
+ capScope: "device",
36999
+ addonId: null,
37000
+ access: "create"
37001
+ },
37002
+ "navigation.playSound": {
37003
+ capName: "navigation",
37004
+ capScope: "device",
37005
+ addonId: null,
37006
+ access: "create"
37007
+ },
37008
+ "navigation.runAction": {
37009
+ capName: "navigation",
37010
+ capScope: "device",
37011
+ addonId: null,
37012
+ access: "create"
37013
+ },
37014
+ "navigation.setLightLevel": {
37015
+ capName: "navigation",
37016
+ capScope: "device",
37017
+ addonId: null,
37018
+ access: "create"
37019
+ },
37020
+ "navigation.setLightMode": {
37021
+ capName: "navigation",
37022
+ capScope: "device",
37023
+ addonId: null,
37024
+ access: "create"
37025
+ },
37026
+ "navigation.setLightOn": {
37027
+ capName: "navigation",
37028
+ capScope: "device",
37029
+ addonId: null,
37030
+ access: "create"
37031
+ },
37032
+ "navigation.stop": {
37033
+ capName: "navigation",
37034
+ capScope: "device",
37035
+ addonId: null,
37036
+ access: "create"
37037
+ },
36690
37038
  "networkAccess.getEndpoint": {
36691
37039
  capName: "network-access",
36692
37040
  capScope: "system",
@@ -40782,6 +41130,56 @@ Object.freeze({
40782
41130
  form: "single",
40783
41131
  optional: false
40784
41132
  }],
41133
+ "navigation.getFeatures": [{
41134
+ name: "deviceId",
41135
+ form: "single",
41136
+ optional: false
41137
+ }],
41138
+ "navigation.goToPoint": [{
41139
+ name: "deviceId",
41140
+ form: "single",
41141
+ optional: false
41142
+ }],
41143
+ "navigation.listActions": [{
41144
+ name: "deviceId",
41145
+ form: "single",
41146
+ optional: false
41147
+ }],
41148
+ "navigation.move": [{
41149
+ name: "deviceId",
41150
+ form: "single",
41151
+ optional: false
41152
+ }],
41153
+ "navigation.playSound": [{
41154
+ name: "deviceId",
41155
+ form: "single",
41156
+ optional: false
41157
+ }],
41158
+ "navigation.runAction": [{
41159
+ name: "deviceId",
41160
+ form: "single",
41161
+ optional: false
41162
+ }],
41163
+ "navigation.setLightLevel": [{
41164
+ name: "deviceId",
41165
+ form: "single",
41166
+ optional: false
41167
+ }],
41168
+ "navigation.setLightMode": [{
41169
+ name: "deviceId",
41170
+ form: "single",
41171
+ optional: false
41172
+ }],
41173
+ "navigation.setLightOn": [{
41174
+ name: "deviceId",
41175
+ form: "single",
41176
+ optional: false
41177
+ }],
41178
+ "navigation.stop": [{
41179
+ name: "deviceId",
41180
+ form: "single",
41181
+ optional: false
41182
+ }],
40785
41183
  "networkQuality.getDeviceStats": [{
40786
41184
  name: "deviceId",
40787
41185
  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",
@@ -29155,6 +29161,287 @@ var ptzAutotrackCapability = {
29155
29161
  */
29156
29162
  durability: "session"
29157
29163
  };
29164
+ /**
29165
+ * `navigation` — a device-scoped capability that natively expresses the FULL
29166
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
29167
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
29168
+ *
29169
+ * Why a NEW cap rather than overloading `ptz`:
29170
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
29171
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
29172
+ * The two are different physical models: PTZ is absolute-position + presets,
29173
+ * navigation is momentary drive nudges + discrete robot ACTIONS
29174
+ * (dock / spot-clean / follow-pet / go-to-point / …).
29175
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
29176
+ * the reverse:
29177
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
29178
+ * / `getOptions`), and
29179
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
29180
+ * robot camera shows up in the existing PTZ control path without every
29181
+ * PTZ provider learning about robots. The mapping lives in the adapter,
29182
+ * not here (see the addon design note):
29183
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
29184
+ * ptz.stop() → navigation.stop()
29185
+ * ptz.goHome() → navigation.runAction('goHome')
29186
+ * ptz.getPresets() → navigation.listActions() (id→preset)
29187
+ * ptz.goToPreset(id) → navigation.runAction(id)
29188
+ *
29189
+ * ## Continuous drive
29190
+ *
29191
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
29192
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
29193
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
29194
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
29195
+ * coalesce them. The UI owns the cadence.
29196
+ *
29197
+ * ## The action dictionary
29198
+ *
29199
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
29200
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
29201
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
29202
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
29203
+ * vendor-specific list. `kind: 'action'` entries are triggered with
29204
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
29205
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
29206
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
29207
+ *
29208
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
29209
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
29210
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
29211
+ * every device handle. A future nodedreame publish adds a typed
29212
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
29213
+ * provider can then swap the raw calls for the typed methods with no change to
29214
+ * THIS contract.
29215
+ */
29216
+ /**
29217
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
29218
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
29219
+ * halts it.
29220
+ *
29221
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
29222
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
29223
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
29224
+ * vector by it (drivers without proportional drive ignore it).
29225
+ *
29226
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
29227
+ * axis alone; an all-undefined nudge is a no-op.
29228
+ */
29229
+ var NavigationMoveCommandSchema = object({
29230
+ pan: number().min(-1).max(1).optional(),
29231
+ tilt: number().min(-1).max(1).optional(),
29232
+ speed: number().min(0).max(1).optional()
29233
+ });
29234
+ /**
29235
+ * The enumerated discrete actions a navigation-capable robot can perform via
29236
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
29237
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
29238
+ * `playSound` (see the `sound` dictionary entries).
29239
+ */
29240
+ var NavigationActionIdSchema = _enum([
29241
+ "goHome",
29242
+ "locate",
29243
+ "spotClean",
29244
+ "findPet",
29245
+ "personFollow",
29246
+ "stop",
29247
+ "startClean",
29248
+ "pauseClean",
29249
+ "dockWash",
29250
+ "autoEmpty",
29251
+ "flashOn",
29252
+ "flashOff"
29253
+ ]);
29254
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
29255
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
29256
+ /**
29257
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
29258
+ * native panel and the PTZ mimic render as a button.
29259
+ *
29260
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
29261
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
29262
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
29263
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
29264
+ * - `label` — operator-facing English label.
29265
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
29266
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
29267
+ * PTZ render ONLY enabled entries. Data-driven: the provider
29268
+ * flips it from config, never by editing code.
29269
+ */
29270
+ var NavigationActionEntrySchema = object({
29271
+ id: string(),
29272
+ kind: NavigationEntryKindSchema,
29273
+ label: string(),
29274
+ icon: string(),
29275
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
29276
+ soundId: number().int().optional(),
29277
+ /** Per-device feature flag — render this entry only when true. */
29278
+ enabled: boolean()
29279
+ });
29280
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
29281
+ var NavigationPointSchema = object({
29282
+ x: number(),
29283
+ y: number()
29284
+ });
29285
+ /**
29286
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
29287
+ * The cap reports which are enabled so the UI / PTZ render only the controls
29288
+ * that are turned on for THIS device. Data-driven: the provider derives these
29289
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
29290
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
29291
+ * that are not dictionary entries.
29292
+ *
29293
+ * - `move` / `stop` — the momentary drive joystick.
29294
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
29295
+ * map-coordinate plumbing is wired.
29296
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
29297
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
29298
+ * - `light` — the on/off fill-light toggle (works anytime).
29299
+ * - `lightMode` — the auto/manual selector + manual level slider (a
29300
+ * camera-service control; needs an active stream).
29301
+ */
29302
+ var NavigationFeaturesSchema = object({
29303
+ move: boolean(),
29304
+ stop: boolean(),
29305
+ goToPoint: boolean(),
29306
+ runAction: boolean(),
29307
+ playSound: boolean(),
29308
+ light: boolean(),
29309
+ lightMode: boolean()
29310
+ });
29311
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
29312
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
29313
+ /**
29314
+ * Live navigation state so the UI can reflect what the robot is doing:
29315
+ * - `mode` — coarse activity (idle / cleaning / following / …).
29316
+ * - `following` — person/pet follow is currently armed.
29317
+ * - `flash` — the on-camera fill light is on.
29318
+ * - `lightMode` — auto vs manual fill-light mode.
29319
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
29320
+ * `lightMode === 'manual'`.
29321
+ */
29322
+ var NavigationStatusSchema = object({
29323
+ mode: _enum([
29324
+ "idle",
29325
+ "cleaning",
29326
+ "spot",
29327
+ "following",
29328
+ "goto",
29329
+ "returning",
29330
+ "paused",
29331
+ "unknown"
29332
+ ]),
29333
+ following: boolean(),
29334
+ flash: boolean(),
29335
+ lightMode: NavigationLightModeSchema,
29336
+ lightLevel: number().min(40).max(100),
29337
+ /** Ms epoch when the slice was last updated. */
29338
+ lastChangedAt: number()
29339
+ });
29340
+ /**
29341
+ * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
29342
+ * observable). Adds `lastFetchedAt` on top of the status shape per the
29343
+ * convention.
29344
+ */
29345
+ var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: number() });
29346
+ var navigationCapability = {
29347
+ name: "navigation",
29348
+ scope: "device",
29349
+ deviceNative: true,
29350
+ mode: "singleton",
29351
+ deviceTypes: [DeviceType.Camera],
29352
+ deviceConfig: { ui: {
29353
+ kind: "widget",
29354
+ widgetId: "host/navigation-panel",
29355
+ tab: "navigation",
29356
+ topTab: true,
29357
+ label: "Navigation",
29358
+ order: 0
29359
+ } },
29360
+ methods: {
29361
+ /**
29362
+ * Momentary drive nudge (the robot moves). `protected` — mirrors
29363
+ * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
29364
+ * path) works for any authenticated user, not admin-only. The UI sends
29365
+ * these at ~1 Hz while a control is held; the provider forwards each one to
29366
+ * a single drive write WITHOUT debouncing.
29367
+ */
29368
+ move: method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29369
+ /** Halt all motion immediately (zero drive vector). */
29370
+ stop: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
29371
+ /** Send the robot to a point on its live map. */
29372
+ goToPoint: method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29373
+ /**
29374
+ * Enumerate the discrete controls THIS device supports (data-driven UI +
29375
+ * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
29376
+ */
29377
+ listActions: method(object({ deviceId: number() }), array(NavigationActionEntrySchema)),
29378
+ /**
29379
+ * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
29380
+ * unsupported action ids are rejected by the provider.
29381
+ */
29382
+ runAction: method(object({
29383
+ deviceId: number(),
29384
+ actionId: NavigationActionIdSchema
29385
+ }), _void(), { kind: "mutation" }),
29386
+ /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
29387
+ playSound: method(object({
29388
+ deviceId: number(),
29389
+ soundId: number().int()
29390
+ }), _void(), { kind: "mutation" }),
29391
+ /**
29392
+ * Turn the on-camera fill light on / off (the `OpenFullLight` control —
29393
+ * works anytime, no active stream required).
29394
+ */
29395
+ setLightOn: method(object({
29396
+ deviceId: number(),
29397
+ on: boolean()
29398
+ }), _void(), { kind: "mutation" }),
29399
+ /**
29400
+ * Set the fill-light mode (auto vs manual). `manual` optionally carries the
29401
+ * initial `level`. The auto/manual + level control is a CAMERA-service
29402
+ * action that generally needs an active camera stream/monitor session — the
29403
+ * UI shows the manual level slider ONLY when `mode === 'manual'`.
29404
+ */
29405
+ setLightMode: method(object({
29406
+ deviceId: number(),
29407
+ mode: NavigationLightModeSchema,
29408
+ level: number().min(40).max(100).optional()
29409
+ }), _void(), { kind: "mutation" }),
29410
+ /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
29411
+ setLightLevel: method(object({
29412
+ deviceId: number(),
29413
+ level: number().min(40).max(100)
29414
+ }), _void(), { kind: "mutation" }),
29415
+ /**
29416
+ * Per-device FEATURE-FLAG report for the general primitives — drives which
29417
+ * controls the UI shows (the per-entry flags for the dictionary come back on
29418
+ * `listActions`).
29419
+ */
29420
+ getFeatures: method(object({ deviceId: number() }), NavigationFeaturesSchema)
29421
+ },
29422
+ events: { onStatusChanged: { data: object({
29423
+ deviceId: number(),
29424
+ status: NavigationStatusSchema
29425
+ }) } },
29426
+ status: {
29427
+ schema: NavigationStatusSchema,
29428
+ kind: "push"
29429
+ },
29430
+ /**
29431
+ * Runtime-state slice mirrored by the kernel. The navigation panel watches it
29432
+ * for live mode / follow / flash changes.
29433
+ */
29434
+ runtimeState: NavigationRuntimeStateSchema,
29435
+ /**
29436
+ * Runtime-state durability: **session** — like `vacuum-control`, a restored
29437
+ * `mode: cleaning` / `following: true` is a robot that is not actually doing
29438
+ * that. The live handle re-publishes on connect.
29439
+ *
29440
+ * See `RuntimeStateDurability`. Enforced by
29441
+ * `scripts/check-runtime-state-durability.ts`.
29442
+ */
29443
+ durability: "session"
29444
+ };
29158
29445
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
29159
29446
  kind: "mutation",
29160
29447
  auth: "admin"
@@ -32628,6 +32915,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
32628
32915
  motionTrigger: motionTriggerCapability,
32629
32916
  motionZones: motionZonesCapability,
32630
32917
  nativeObjectDetection: nativeObjectDetectionCapability,
32918
+ navigation: navigationCapability,
32631
32919
  networkLink: networkLinkCapability,
32632
32920
  notifier: notifierCapability,
32633
32921
  numericSensor: numericSensorCapability,
@@ -36688,6 +36976,66 @@ Object.freeze({
36688
36976
  addonId: null,
36689
36977
  access: "create"
36690
36978
  },
36979
+ "navigation.getFeatures": {
36980
+ capName: "navigation",
36981
+ capScope: "device",
36982
+ addonId: null,
36983
+ access: "view"
36984
+ },
36985
+ "navigation.goToPoint": {
36986
+ capName: "navigation",
36987
+ capScope: "device",
36988
+ addonId: null,
36989
+ access: "create"
36990
+ },
36991
+ "navigation.listActions": {
36992
+ capName: "navigation",
36993
+ capScope: "device",
36994
+ addonId: null,
36995
+ access: "view"
36996
+ },
36997
+ "navigation.move": {
36998
+ capName: "navigation",
36999
+ capScope: "device",
37000
+ addonId: null,
37001
+ access: "create"
37002
+ },
37003
+ "navigation.playSound": {
37004
+ capName: "navigation",
37005
+ capScope: "device",
37006
+ addonId: null,
37007
+ access: "create"
37008
+ },
37009
+ "navigation.runAction": {
37010
+ capName: "navigation",
37011
+ capScope: "device",
37012
+ addonId: null,
37013
+ access: "create"
37014
+ },
37015
+ "navigation.setLightLevel": {
37016
+ capName: "navigation",
37017
+ capScope: "device",
37018
+ addonId: null,
37019
+ access: "create"
37020
+ },
37021
+ "navigation.setLightMode": {
37022
+ capName: "navigation",
37023
+ capScope: "device",
37024
+ addonId: null,
37025
+ access: "create"
37026
+ },
37027
+ "navigation.setLightOn": {
37028
+ capName: "navigation",
37029
+ capScope: "device",
37030
+ addonId: null,
37031
+ access: "create"
37032
+ },
37033
+ "navigation.stop": {
37034
+ capName: "navigation",
37035
+ capScope: "device",
37036
+ addonId: null,
37037
+ access: "create"
37038
+ },
36691
37039
  "networkAccess.getEndpoint": {
36692
37040
  capName: "network-access",
36693
37041
  capScope: "system",
@@ -40783,6 +41131,56 @@ Object.freeze({
40783
41131
  form: "single",
40784
41132
  optional: false
40785
41133
  }],
41134
+ "navigation.getFeatures": [{
41135
+ name: "deviceId",
41136
+ form: "single",
41137
+ optional: false
41138
+ }],
41139
+ "navigation.goToPoint": [{
41140
+ name: "deviceId",
41141
+ form: "single",
41142
+ optional: false
41143
+ }],
41144
+ "navigation.listActions": [{
41145
+ name: "deviceId",
41146
+ form: "single",
41147
+ optional: false
41148
+ }],
41149
+ "navigation.move": [{
41150
+ name: "deviceId",
41151
+ form: "single",
41152
+ optional: false
41153
+ }],
41154
+ "navigation.playSound": [{
41155
+ name: "deviceId",
41156
+ form: "single",
41157
+ optional: false
41158
+ }],
41159
+ "navigation.runAction": [{
41160
+ name: "deviceId",
41161
+ form: "single",
41162
+ optional: false
41163
+ }],
41164
+ "navigation.setLightLevel": [{
41165
+ name: "deviceId",
41166
+ form: "single",
41167
+ optional: false
41168
+ }],
41169
+ "navigation.setLightMode": [{
41170
+ name: "deviceId",
41171
+ form: "single",
41172
+ optional: false
41173
+ }],
41174
+ "navigation.setLightOn": [{
41175
+ name: "deviceId",
41176
+ form: "single",
41177
+ optional: false
41178
+ }],
41179
+ "navigation.stop": [{
41180
+ name: "deviceId",
41181
+ form: "single",
41182
+ optional: false
41183
+ }],
40786
41184
  "networkQuality.getDeviceStats": [{
40787
41185
  name: "deviceId",
40788
41186
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-amcrest",
3
- "version": "0.2.72",
3
+ "version": "0.2.74",
4
4
  "description": "Amcrest/Dahua camera device provider addon for CamStack — Dahua CGI over HTTP(S) with digest auth (snapshot, RTSP catalog, PTZ, image/day-night config)",
5
5
  "keywords": [
6
6
  "camstack",