@camstack/addon-provider-onvif 1.2.68 → 1.2.70

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
@@ -2,7 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  //#region \0rolldown/runtime.js
3
3
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
4
4
  //#endregion
5
- //#region ../types/dist/event-category-BZL-fdNj.mjs
5
+ //#region ../types/dist/event-category-zAv7pMUz.mjs
6
6
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
7
7
  EventCategory["SystemBoot"] = "system.boot";
8
8
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -374,6 +374,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
374
374
  */
375
375
  EventCategory["BatteryOnStatusChanged"] = "battery.onStatusChanged";
376
376
  /**
377
+ * Cap event fired by every device that registers the `network-link`
378
+ * capability. Mirrors the cap definition's `onStatusChanged`. Carries
379
+ * `{ deviceId, status: NetworkLinkStatus }` — a link switch or a signal
380
+ * reading that moved.
381
+ */
382
+ EventCategory["NetworkLinkOnStatusChanged"] = "network-link.onStatusChanged";
383
+ /**
377
384
  * Emitted by the battery cap provider WHEN `wakeForStream` enters the
378
385
  * "wake in progress" window — between the Baichuan wake-up issue and
379
386
  * the camera's first dialed-back RTP packet. The stream-broker
@@ -23365,6 +23372,39 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, meth
23365
23372
  deviceId: number(),
23366
23373
  status: BatteryStatusSchema
23367
23374
  });
23375
+ /**
23376
+ * Network-link snapshot. Same shape for every provider (a Reolink wifi
23377
+ * camera, a Home Assistant device with a signal-strength sensor, a Tapo
23378
+ * plug): one slice under `device.runtimeState['network-link']`, one badge,
23379
+ * one Home Assistant projection.
23380
+ */
23381
+ var NetworkLinkStatusSchema = object({
23382
+ /** The link the device is on. `'unknown'` = not read yet, not "no link". */
23383
+ type: _enum([
23384
+ "wifi",
23385
+ "ethernet",
23386
+ "cellular",
23387
+ "unknown"
23388
+ ]),
23389
+ /**
23390
+ * Link quality, 0..100 inclusive, normalised by the provider from whatever
23391
+ * the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
23392
+ * KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
23393
+ * one whose reading has not landed must not be drawn at 0 %. Consumers
23394
+ * SKIP a null rather than coerce it.
23395
+ */
23396
+ signalPercent: number().min(0).max(100).nullable(),
23397
+ /** Raw received signal strength in dBm, when the firmware reports one. */
23398
+ rssiDbm: number().optional(),
23399
+ /** Network name of a wireless link, when the firmware reports it. */
23400
+ ssid: string().optional(),
23401
+ /** Ms epoch of the last observation. Lets consumers reason about freshness. */
23402
+ lastUpdated: number()
23403
+ });
23404
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
23405
+ deviceId: number(),
23406
+ status: NetworkLinkStatusSchema
23407
+ });
23368
23408
  object({
23369
23409
  on: boolean(),
23370
23410
  /** Ms epoch of the last transition. 0 if never observed. */
@@ -27001,6 +27041,203 @@ DeviceType.Camera, method(object({ deviceId: number() }), PtzAutotrackStatusSche
27001
27041
  deviceId: number(),
27002
27042
  status: PtzAutotrackStatusSchema
27003
27043
  });
27044
+ /**
27045
+ * `navigation` — a device-scoped capability that natively expresses the FULL
27046
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
27047
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
27048
+ *
27049
+ * Why a NEW cap rather than overloading `ptz`:
27050
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
27051
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
27052
+ * The two are different physical models: PTZ is absolute-position + presets,
27053
+ * navigation is momentary drive nudges + discrete robot ACTIONS
27054
+ * (dock / spot-clean / follow-pet / go-to-point / …).
27055
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
27056
+ * the reverse:
27057
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
27058
+ * / `getOptions`), and
27059
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
27060
+ * robot camera shows up in the existing PTZ control path without every
27061
+ * PTZ provider learning about robots. The mapping lives in the adapter,
27062
+ * not here (see the addon design note):
27063
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
27064
+ * ptz.stop() → navigation.stop()
27065
+ * ptz.goHome() → navigation.runAction('goHome')
27066
+ * ptz.getPresets() → navigation.listActions() (id→preset)
27067
+ * ptz.goToPreset(id) → navigation.runAction(id)
27068
+ *
27069
+ * ## Continuous drive
27070
+ *
27071
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
27072
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
27073
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
27074
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
27075
+ * coalesce them. The UI owns the cadence.
27076
+ *
27077
+ * ## The action dictionary
27078
+ *
27079
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
27080
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
27081
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
27082
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
27083
+ * vendor-specific list. `kind: 'action'` entries are triggered with
27084
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
27085
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
27086
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
27087
+ *
27088
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
27089
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
27090
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
27091
+ * every device handle. A future nodedreame publish adds a typed
27092
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
27093
+ * provider can then swap the raw calls for the typed methods with no change to
27094
+ * THIS contract.
27095
+ */
27096
+ /**
27097
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
27098
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
27099
+ * halts it.
27100
+ *
27101
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
27102
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
27103
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
27104
+ * vector by it (drivers without proportional drive ignore it).
27105
+ *
27106
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
27107
+ * axis alone; an all-undefined nudge is a no-op.
27108
+ */
27109
+ var NavigationMoveCommandSchema = object({
27110
+ pan: number().min(-1).max(1).optional(),
27111
+ tilt: number().min(-1).max(1).optional(),
27112
+ speed: number().min(0).max(1).optional()
27113
+ });
27114
+ /**
27115
+ * The enumerated discrete actions a navigation-capable robot can perform via
27116
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
27117
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
27118
+ * `playSound` (see the `sound` dictionary entries).
27119
+ */
27120
+ var NavigationActionIdSchema = _enum([
27121
+ "goHome",
27122
+ "locate",
27123
+ "spotClean",
27124
+ "findPet",
27125
+ "personFollow",
27126
+ "stop",
27127
+ "startClean",
27128
+ "pauseClean",
27129
+ "dockWash",
27130
+ "autoEmpty",
27131
+ "flashOn",
27132
+ "flashOff"
27133
+ ]);
27134
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
27135
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
27136
+ /**
27137
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
27138
+ * native panel and the PTZ mimic render as a button.
27139
+ *
27140
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
27141
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
27142
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
27143
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
27144
+ * - `label` — operator-facing English label.
27145
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
27146
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
27147
+ * PTZ render ONLY enabled entries. Data-driven: the provider
27148
+ * flips it from config, never by editing code.
27149
+ */
27150
+ var NavigationActionEntrySchema = object({
27151
+ id: string(),
27152
+ kind: NavigationEntryKindSchema,
27153
+ label: string(),
27154
+ icon: string(),
27155
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
27156
+ soundId: number().int().optional(),
27157
+ /** Per-device feature flag — render this entry only when true. */
27158
+ enabled: boolean()
27159
+ });
27160
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
27161
+ var NavigationPointSchema = object({
27162
+ x: number(),
27163
+ y: number()
27164
+ });
27165
+ /**
27166
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
27167
+ * The cap reports which are enabled so the UI / PTZ render only the controls
27168
+ * that are turned on for THIS device. Data-driven: the provider derives these
27169
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
27170
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
27171
+ * that are not dictionary entries.
27172
+ *
27173
+ * - `move` / `stop` — the momentary drive joystick.
27174
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
27175
+ * map-coordinate plumbing is wired.
27176
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
27177
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
27178
+ * - `light` — the on/off fill-light toggle (works anytime).
27179
+ * - `lightMode` — the auto/manual selector + manual level slider (a
27180
+ * camera-service control; needs an active stream).
27181
+ */
27182
+ var NavigationFeaturesSchema = object({
27183
+ move: boolean(),
27184
+ stop: boolean(),
27185
+ goToPoint: boolean(),
27186
+ runAction: boolean(),
27187
+ playSound: boolean(),
27188
+ light: boolean(),
27189
+ lightMode: boolean()
27190
+ });
27191
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
27192
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
27193
+ /**
27194
+ * Live navigation state so the UI can reflect what the robot is doing:
27195
+ * - `mode` — coarse activity (idle / cleaning / following / …).
27196
+ * - `following` — person/pet follow is currently armed.
27197
+ * - `flash` — the on-camera fill light is on.
27198
+ * - `lightMode` — auto vs manual fill-light mode.
27199
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
27200
+ * `lightMode === 'manual'`.
27201
+ */
27202
+ var NavigationStatusSchema = object({
27203
+ mode: _enum([
27204
+ "idle",
27205
+ "cleaning",
27206
+ "spot",
27207
+ "following",
27208
+ "goto",
27209
+ "returning",
27210
+ "paused",
27211
+ "unknown"
27212
+ ]),
27213
+ following: boolean(),
27214
+ flash: boolean(),
27215
+ lightMode: NavigationLightModeSchema,
27216
+ lightLevel: number().min(40).max(100),
27217
+ /** Ms epoch when the slice was last updated. */
27218
+ lastChangedAt: number()
27219
+ });
27220
+ NavigationStatusSchema.extend({ lastFetchedAt: number() });
27221
+ DeviceType.Camera, method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), _void(), { kind: "mutation" }), method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), array(NavigationActionEntrySchema)), method(object({
27222
+ deviceId: number(),
27223
+ actionId: NavigationActionIdSchema
27224
+ }), _void(), { kind: "mutation" }), method(object({
27225
+ deviceId: number(),
27226
+ soundId: number().int()
27227
+ }), _void(), { kind: "mutation" }), method(object({
27228
+ deviceId: number(),
27229
+ on: boolean()
27230
+ }), _void(), { kind: "mutation" }), method(object({
27231
+ deviceId: number(),
27232
+ mode: NavigationLightModeSchema,
27233
+ level: number().min(40).max(100).optional()
27234
+ }), _void(), { kind: "mutation" }), method(object({
27235
+ deviceId: number(),
27236
+ level: number().min(40).max(100)
27237
+ }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), NavigationFeaturesSchema), object({
27238
+ deviceId: number(),
27239
+ status: NavigationStatusSchema
27240
+ });
27004
27241
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
27005
27242
  kind: "mutation",
27006
27243
  auth: "admin"
@@ -32840,6 +33077,66 @@ Object.freeze({
32840
33077
  addonId: null,
32841
33078
  access: "create"
32842
33079
  },
33080
+ "navigation.getFeatures": {
33081
+ capName: "navigation",
33082
+ capScope: "device",
33083
+ addonId: null,
33084
+ access: "view"
33085
+ },
33086
+ "navigation.goToPoint": {
33087
+ capName: "navigation",
33088
+ capScope: "device",
33089
+ addonId: null,
33090
+ access: "create"
33091
+ },
33092
+ "navigation.listActions": {
33093
+ capName: "navigation",
33094
+ capScope: "device",
33095
+ addonId: null,
33096
+ access: "view"
33097
+ },
33098
+ "navigation.move": {
33099
+ capName: "navigation",
33100
+ capScope: "device",
33101
+ addonId: null,
33102
+ access: "create"
33103
+ },
33104
+ "navigation.playSound": {
33105
+ capName: "navigation",
33106
+ capScope: "device",
33107
+ addonId: null,
33108
+ access: "create"
33109
+ },
33110
+ "navigation.runAction": {
33111
+ capName: "navigation",
33112
+ capScope: "device",
33113
+ addonId: null,
33114
+ access: "create"
33115
+ },
33116
+ "navigation.setLightLevel": {
33117
+ capName: "navigation",
33118
+ capScope: "device",
33119
+ addonId: null,
33120
+ access: "create"
33121
+ },
33122
+ "navigation.setLightMode": {
33123
+ capName: "navigation",
33124
+ capScope: "device",
33125
+ addonId: null,
33126
+ access: "create"
33127
+ },
33128
+ "navigation.setLightOn": {
33129
+ capName: "navigation",
33130
+ capScope: "device",
33131
+ addonId: null,
33132
+ access: "create"
33133
+ },
33134
+ "navigation.stop": {
33135
+ capName: "navigation",
33136
+ capScope: "device",
33137
+ addonId: null,
33138
+ access: "create"
33139
+ },
32843
33140
  "networkAccess.getEndpoint": {
32844
33141
  capName: "network-access",
32845
33142
  capScope: "system",
@@ -36935,6 +37232,56 @@ Object.freeze({
36935
37232
  form: "single",
36936
37233
  optional: false
36937
37234
  }],
37235
+ "navigation.getFeatures": [{
37236
+ name: "deviceId",
37237
+ form: "single",
37238
+ optional: false
37239
+ }],
37240
+ "navigation.goToPoint": [{
37241
+ name: "deviceId",
37242
+ form: "single",
37243
+ optional: false
37244
+ }],
37245
+ "navigation.listActions": [{
37246
+ name: "deviceId",
37247
+ form: "single",
37248
+ optional: false
37249
+ }],
37250
+ "navigation.move": [{
37251
+ name: "deviceId",
37252
+ form: "single",
37253
+ optional: false
37254
+ }],
37255
+ "navigation.playSound": [{
37256
+ name: "deviceId",
37257
+ form: "single",
37258
+ optional: false
37259
+ }],
37260
+ "navigation.runAction": [{
37261
+ name: "deviceId",
37262
+ form: "single",
37263
+ optional: false
37264
+ }],
37265
+ "navigation.setLightLevel": [{
37266
+ name: "deviceId",
37267
+ form: "single",
37268
+ optional: false
37269
+ }],
37270
+ "navigation.setLightMode": [{
37271
+ name: "deviceId",
37272
+ form: "single",
37273
+ optional: false
37274
+ }],
37275
+ "navigation.setLightOn": [{
37276
+ name: "deviceId",
37277
+ form: "single",
37278
+ optional: false
37279
+ }],
37280
+ "navigation.stop": [{
37281
+ name: "deviceId",
37282
+ form: "single",
37283
+ optional: false
37284
+ }],
36938
37285
  "networkQuality.getDeviceStats": [{
36939
37286
  name: "deviceId",
36940
37287
  form: "single",
package/dist/addon.mjs CHANGED
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
3
3
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
4
4
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
5
5
  //#endregion
6
- //#region ../types/dist/event-category-BZL-fdNj.mjs
6
+ //#region ../types/dist/event-category-zAv7pMUz.mjs
7
7
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
8
8
  EventCategory["SystemBoot"] = "system.boot";
9
9
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -375,6 +375,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
375
375
  */
376
376
  EventCategory["BatteryOnStatusChanged"] = "battery.onStatusChanged";
377
377
  /**
378
+ * Cap event fired by every device that registers the `network-link`
379
+ * capability. Mirrors the cap definition's `onStatusChanged`. Carries
380
+ * `{ deviceId, status: NetworkLinkStatus }` — a link switch or a signal
381
+ * reading that moved.
382
+ */
383
+ EventCategory["NetworkLinkOnStatusChanged"] = "network-link.onStatusChanged";
384
+ /**
378
385
  * Emitted by the battery cap provider WHEN `wakeForStream` enters the
379
386
  * "wake in progress" window — between the Baichuan wake-up issue and
380
387
  * the camera's first dialed-back RTP packet. The stream-broker
@@ -23366,6 +23373,39 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, meth
23366
23373
  deviceId: number(),
23367
23374
  status: BatteryStatusSchema
23368
23375
  });
23376
+ /**
23377
+ * Network-link snapshot. Same shape for every provider (a Reolink wifi
23378
+ * camera, a Home Assistant device with a signal-strength sensor, a Tapo
23379
+ * plug): one slice under `device.runtimeState['network-link']`, one badge,
23380
+ * one Home Assistant projection.
23381
+ */
23382
+ var NetworkLinkStatusSchema = object({
23383
+ /** The link the device is on. `'unknown'` = not read yet, not "no link". */
23384
+ type: _enum([
23385
+ "wifi",
23386
+ "ethernet",
23387
+ "cellular",
23388
+ "unknown"
23389
+ ]),
23390
+ /**
23391
+ * Link quality, 0..100 inclusive, normalised by the provider from whatever
23392
+ * the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
23393
+ * KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
23394
+ * one whose reading has not landed must not be drawn at 0 %. Consumers
23395
+ * SKIP a null rather than coerce it.
23396
+ */
23397
+ signalPercent: number().min(0).max(100).nullable(),
23398
+ /** Raw received signal strength in dBm, when the firmware reports one. */
23399
+ rssiDbm: number().optional(),
23400
+ /** Network name of a wireless link, when the firmware reports it. */
23401
+ ssid: string().optional(),
23402
+ /** Ms epoch of the last observation. Lets consumers reason about freshness. */
23403
+ lastUpdated: number()
23404
+ });
23405
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
23406
+ deviceId: number(),
23407
+ status: NetworkLinkStatusSchema
23408
+ });
23369
23409
  object({
23370
23410
  on: boolean(),
23371
23411
  /** Ms epoch of the last transition. 0 if never observed. */
@@ -27002,6 +27042,203 @@ DeviceType.Camera, method(object({ deviceId: number() }), PtzAutotrackStatusSche
27002
27042
  deviceId: number(),
27003
27043
  status: PtzAutotrackStatusSchema
27004
27044
  });
27045
+ /**
27046
+ * `navigation` — a device-scoped capability that natively expresses the FULL
27047
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
27048
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
27049
+ *
27050
+ * Why a NEW cap rather than overloading `ptz`:
27051
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
27052
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
27053
+ * The two are different physical models: PTZ is absolute-position + presets,
27054
+ * navigation is momentary drive nudges + discrete robot ACTIONS
27055
+ * (dock / spot-clean / follow-pet / go-to-point / …).
27056
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
27057
+ * the reverse:
27058
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
27059
+ * / `getOptions`), and
27060
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
27061
+ * robot camera shows up in the existing PTZ control path without every
27062
+ * PTZ provider learning about robots. The mapping lives in the adapter,
27063
+ * not here (see the addon design note):
27064
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
27065
+ * ptz.stop() → navigation.stop()
27066
+ * ptz.goHome() → navigation.runAction('goHome')
27067
+ * ptz.getPresets() → navigation.listActions() (id→preset)
27068
+ * ptz.goToPreset(id) → navigation.runAction(id)
27069
+ *
27070
+ * ## Continuous drive
27071
+ *
27072
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
27073
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
27074
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
27075
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
27076
+ * coalesce them. The UI owns the cadence.
27077
+ *
27078
+ * ## The action dictionary
27079
+ *
27080
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
27081
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
27082
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
27083
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
27084
+ * vendor-specific list. `kind: 'action'` entries are triggered with
27085
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
27086
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
27087
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
27088
+ *
27089
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
27090
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
27091
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
27092
+ * every device handle. A future nodedreame publish adds a typed
27093
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
27094
+ * provider can then swap the raw calls for the typed methods with no change to
27095
+ * THIS contract.
27096
+ */
27097
+ /**
27098
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
27099
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
27100
+ * halts it.
27101
+ *
27102
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
27103
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
27104
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
27105
+ * vector by it (drivers without proportional drive ignore it).
27106
+ *
27107
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
27108
+ * axis alone; an all-undefined nudge is a no-op.
27109
+ */
27110
+ var NavigationMoveCommandSchema = object({
27111
+ pan: number().min(-1).max(1).optional(),
27112
+ tilt: number().min(-1).max(1).optional(),
27113
+ speed: number().min(0).max(1).optional()
27114
+ });
27115
+ /**
27116
+ * The enumerated discrete actions a navigation-capable robot can perform via
27117
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
27118
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
27119
+ * `playSound` (see the `sound` dictionary entries).
27120
+ */
27121
+ var NavigationActionIdSchema = _enum([
27122
+ "goHome",
27123
+ "locate",
27124
+ "spotClean",
27125
+ "findPet",
27126
+ "personFollow",
27127
+ "stop",
27128
+ "startClean",
27129
+ "pauseClean",
27130
+ "dockWash",
27131
+ "autoEmpty",
27132
+ "flashOn",
27133
+ "flashOff"
27134
+ ]);
27135
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
27136
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
27137
+ /**
27138
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
27139
+ * native panel and the PTZ mimic render as a button.
27140
+ *
27141
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
27142
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
27143
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
27144
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
27145
+ * - `label` — operator-facing English label.
27146
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
27147
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
27148
+ * PTZ render ONLY enabled entries. Data-driven: the provider
27149
+ * flips it from config, never by editing code.
27150
+ */
27151
+ var NavigationActionEntrySchema = object({
27152
+ id: string(),
27153
+ kind: NavigationEntryKindSchema,
27154
+ label: string(),
27155
+ icon: string(),
27156
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
27157
+ soundId: number().int().optional(),
27158
+ /** Per-device feature flag — render this entry only when true. */
27159
+ enabled: boolean()
27160
+ });
27161
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
27162
+ var NavigationPointSchema = object({
27163
+ x: number(),
27164
+ y: number()
27165
+ });
27166
+ /**
27167
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
27168
+ * The cap reports which are enabled so the UI / PTZ render only the controls
27169
+ * that are turned on for THIS device. Data-driven: the provider derives these
27170
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
27171
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
27172
+ * that are not dictionary entries.
27173
+ *
27174
+ * - `move` / `stop` — the momentary drive joystick.
27175
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
27176
+ * map-coordinate plumbing is wired.
27177
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
27178
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
27179
+ * - `light` — the on/off fill-light toggle (works anytime).
27180
+ * - `lightMode` — the auto/manual selector + manual level slider (a
27181
+ * camera-service control; needs an active stream).
27182
+ */
27183
+ var NavigationFeaturesSchema = object({
27184
+ move: boolean(),
27185
+ stop: boolean(),
27186
+ goToPoint: boolean(),
27187
+ runAction: boolean(),
27188
+ playSound: boolean(),
27189
+ light: boolean(),
27190
+ lightMode: boolean()
27191
+ });
27192
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
27193
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
27194
+ /**
27195
+ * Live navigation state so the UI can reflect what the robot is doing:
27196
+ * - `mode` — coarse activity (idle / cleaning / following / …).
27197
+ * - `following` — person/pet follow is currently armed.
27198
+ * - `flash` — the on-camera fill light is on.
27199
+ * - `lightMode` — auto vs manual fill-light mode.
27200
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
27201
+ * `lightMode === 'manual'`.
27202
+ */
27203
+ var NavigationStatusSchema = object({
27204
+ mode: _enum([
27205
+ "idle",
27206
+ "cleaning",
27207
+ "spot",
27208
+ "following",
27209
+ "goto",
27210
+ "returning",
27211
+ "paused",
27212
+ "unknown"
27213
+ ]),
27214
+ following: boolean(),
27215
+ flash: boolean(),
27216
+ lightMode: NavigationLightModeSchema,
27217
+ lightLevel: number().min(40).max(100),
27218
+ /** Ms epoch when the slice was last updated. */
27219
+ lastChangedAt: number()
27220
+ });
27221
+ NavigationStatusSchema.extend({ lastFetchedAt: number() });
27222
+ DeviceType.Camera, method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), _void(), { kind: "mutation" }), method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), array(NavigationActionEntrySchema)), method(object({
27223
+ deviceId: number(),
27224
+ actionId: NavigationActionIdSchema
27225
+ }), _void(), { kind: "mutation" }), method(object({
27226
+ deviceId: number(),
27227
+ soundId: number().int()
27228
+ }), _void(), { kind: "mutation" }), method(object({
27229
+ deviceId: number(),
27230
+ on: boolean()
27231
+ }), _void(), { kind: "mutation" }), method(object({
27232
+ deviceId: number(),
27233
+ mode: NavigationLightModeSchema,
27234
+ level: number().min(40).max(100).optional()
27235
+ }), _void(), { kind: "mutation" }), method(object({
27236
+ deviceId: number(),
27237
+ level: number().min(40).max(100)
27238
+ }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), NavigationFeaturesSchema), object({
27239
+ deviceId: number(),
27240
+ status: NavigationStatusSchema
27241
+ });
27005
27242
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
27006
27243
  kind: "mutation",
27007
27244
  auth: "admin"
@@ -32841,6 +33078,66 @@ Object.freeze({
32841
33078
  addonId: null,
32842
33079
  access: "create"
32843
33080
  },
33081
+ "navigation.getFeatures": {
33082
+ capName: "navigation",
33083
+ capScope: "device",
33084
+ addonId: null,
33085
+ access: "view"
33086
+ },
33087
+ "navigation.goToPoint": {
33088
+ capName: "navigation",
33089
+ capScope: "device",
33090
+ addonId: null,
33091
+ access: "create"
33092
+ },
33093
+ "navigation.listActions": {
33094
+ capName: "navigation",
33095
+ capScope: "device",
33096
+ addonId: null,
33097
+ access: "view"
33098
+ },
33099
+ "navigation.move": {
33100
+ capName: "navigation",
33101
+ capScope: "device",
33102
+ addonId: null,
33103
+ access: "create"
33104
+ },
33105
+ "navigation.playSound": {
33106
+ capName: "navigation",
33107
+ capScope: "device",
33108
+ addonId: null,
33109
+ access: "create"
33110
+ },
33111
+ "navigation.runAction": {
33112
+ capName: "navigation",
33113
+ capScope: "device",
33114
+ addonId: null,
33115
+ access: "create"
33116
+ },
33117
+ "navigation.setLightLevel": {
33118
+ capName: "navigation",
33119
+ capScope: "device",
33120
+ addonId: null,
33121
+ access: "create"
33122
+ },
33123
+ "navigation.setLightMode": {
33124
+ capName: "navigation",
33125
+ capScope: "device",
33126
+ addonId: null,
33127
+ access: "create"
33128
+ },
33129
+ "navigation.setLightOn": {
33130
+ capName: "navigation",
33131
+ capScope: "device",
33132
+ addonId: null,
33133
+ access: "create"
33134
+ },
33135
+ "navigation.stop": {
33136
+ capName: "navigation",
33137
+ capScope: "device",
33138
+ addonId: null,
33139
+ access: "create"
33140
+ },
32844
33141
  "networkAccess.getEndpoint": {
32845
33142
  capName: "network-access",
32846
33143
  capScope: "system",
@@ -36936,6 +37233,56 @@ Object.freeze({
36936
37233
  form: "single",
36937
37234
  optional: false
36938
37235
  }],
37236
+ "navigation.getFeatures": [{
37237
+ name: "deviceId",
37238
+ form: "single",
37239
+ optional: false
37240
+ }],
37241
+ "navigation.goToPoint": [{
37242
+ name: "deviceId",
37243
+ form: "single",
37244
+ optional: false
37245
+ }],
37246
+ "navigation.listActions": [{
37247
+ name: "deviceId",
37248
+ form: "single",
37249
+ optional: false
37250
+ }],
37251
+ "navigation.move": [{
37252
+ name: "deviceId",
37253
+ form: "single",
37254
+ optional: false
37255
+ }],
37256
+ "navigation.playSound": [{
37257
+ name: "deviceId",
37258
+ form: "single",
37259
+ optional: false
37260
+ }],
37261
+ "navigation.runAction": [{
37262
+ name: "deviceId",
37263
+ form: "single",
37264
+ optional: false
37265
+ }],
37266
+ "navigation.setLightLevel": [{
37267
+ name: "deviceId",
37268
+ form: "single",
37269
+ optional: false
37270
+ }],
37271
+ "navigation.setLightMode": [{
37272
+ name: "deviceId",
37273
+ form: "single",
37274
+ optional: false
37275
+ }],
37276
+ "navigation.setLightOn": [{
37277
+ name: "deviceId",
37278
+ form: "single",
37279
+ optional: false
37280
+ }],
37281
+ "navigation.stop": [{
37282
+ name: "deviceId",
37283
+ form: "single",
37284
+ optional: false
37285
+ }],
36939
37286
  "networkQuality.getDeviceStats": [{
36940
37287
  name: "deviceId",
36941
37288
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-onvif",
3
- "version": "1.2.68",
3
+ "version": "1.2.70",
4
4
  "description": "ONVIF camera device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",