@camstack/addon-provider-wyze 0.2.74 → 0.2.75

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
@@ -29108,6 +29108,287 @@ var ptzAutotrackCapability = {
29108
29108
  */
29109
29109
  durability: "session"
29110
29110
  };
29111
+ /**
29112
+ * `navigation` — a device-scoped capability that natively expresses the FULL
29113
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
29114
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
29115
+ *
29116
+ * Why a NEW cap rather than overloading `ptz`:
29117
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
29118
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
29119
+ * The two are different physical models: PTZ is absolute-position + presets,
29120
+ * navigation is momentary drive nudges + discrete robot ACTIONS
29121
+ * (dock / spot-clean / follow-pet / go-to-point / …).
29122
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
29123
+ * the reverse:
29124
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
29125
+ * / `getOptions`), and
29126
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
29127
+ * robot camera shows up in the existing PTZ control path without every
29128
+ * PTZ provider learning about robots. The mapping lives in the adapter,
29129
+ * not here (see the addon design note):
29130
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
29131
+ * ptz.stop() → navigation.stop()
29132
+ * ptz.goHome() → navigation.runAction('goHome')
29133
+ * ptz.getPresets() → navigation.listActions() (id→preset)
29134
+ * ptz.goToPreset(id) → navigation.runAction(id)
29135
+ *
29136
+ * ## Continuous drive
29137
+ *
29138
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
29139
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
29140
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
29141
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
29142
+ * coalesce them. The UI owns the cadence.
29143
+ *
29144
+ * ## The action dictionary
29145
+ *
29146
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
29147
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
29148
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
29149
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
29150
+ * vendor-specific list. `kind: 'action'` entries are triggered with
29151
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
29152
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
29153
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
29154
+ *
29155
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
29156
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
29157
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
29158
+ * every device handle. A future nodedreame publish adds a typed
29159
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
29160
+ * provider can then swap the raw calls for the typed methods with no change to
29161
+ * THIS contract.
29162
+ */
29163
+ /**
29164
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
29165
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
29166
+ * halts it.
29167
+ *
29168
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
29169
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
29170
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
29171
+ * vector by it (drivers without proportional drive ignore it).
29172
+ *
29173
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
29174
+ * axis alone; an all-undefined nudge is a no-op.
29175
+ */
29176
+ var NavigationMoveCommandSchema = object({
29177
+ pan: number().min(-1).max(1).optional(),
29178
+ tilt: number().min(-1).max(1).optional(),
29179
+ speed: number().min(0).max(1).optional()
29180
+ });
29181
+ /**
29182
+ * The enumerated discrete actions a navigation-capable robot can perform via
29183
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
29184
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
29185
+ * `playSound` (see the `sound` dictionary entries).
29186
+ */
29187
+ var NavigationActionIdSchema = _enum([
29188
+ "goHome",
29189
+ "locate",
29190
+ "spotClean",
29191
+ "findPet",
29192
+ "personFollow",
29193
+ "stop",
29194
+ "startClean",
29195
+ "pauseClean",
29196
+ "dockWash",
29197
+ "autoEmpty",
29198
+ "flashOn",
29199
+ "flashOff"
29200
+ ]);
29201
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
29202
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
29203
+ /**
29204
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
29205
+ * native panel and the PTZ mimic render as a button.
29206
+ *
29207
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
29208
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
29209
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
29210
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
29211
+ * - `label` — operator-facing English label.
29212
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
29213
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
29214
+ * PTZ render ONLY enabled entries. Data-driven: the provider
29215
+ * flips it from config, never by editing code.
29216
+ */
29217
+ var NavigationActionEntrySchema = object({
29218
+ id: string(),
29219
+ kind: NavigationEntryKindSchema,
29220
+ label: string(),
29221
+ icon: string(),
29222
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
29223
+ soundId: number().int().optional(),
29224
+ /** Per-device feature flag — render this entry only when true. */
29225
+ enabled: boolean()
29226
+ });
29227
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
29228
+ var NavigationPointSchema = object({
29229
+ x: number(),
29230
+ y: number()
29231
+ });
29232
+ /**
29233
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
29234
+ * The cap reports which are enabled so the UI / PTZ render only the controls
29235
+ * that are turned on for THIS device. Data-driven: the provider derives these
29236
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
29237
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
29238
+ * that are not dictionary entries.
29239
+ *
29240
+ * - `move` / `stop` — the momentary drive joystick.
29241
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
29242
+ * map-coordinate plumbing is wired.
29243
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
29244
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
29245
+ * - `light` — the on/off fill-light toggle (works anytime).
29246
+ * - `lightMode` — the auto/manual selector + manual level slider (a
29247
+ * camera-service control; needs an active stream).
29248
+ */
29249
+ var NavigationFeaturesSchema = object({
29250
+ move: boolean(),
29251
+ stop: boolean(),
29252
+ goToPoint: boolean(),
29253
+ runAction: boolean(),
29254
+ playSound: boolean(),
29255
+ light: boolean(),
29256
+ lightMode: boolean()
29257
+ });
29258
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
29259
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
29260
+ /**
29261
+ * Live navigation state so the UI can reflect what the robot is doing:
29262
+ * - `mode` — coarse activity (idle / cleaning / following / …).
29263
+ * - `following` — person/pet follow is currently armed.
29264
+ * - `flash` — the on-camera fill light is on.
29265
+ * - `lightMode` — auto vs manual fill-light mode.
29266
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
29267
+ * `lightMode === 'manual'`.
29268
+ */
29269
+ var NavigationStatusSchema = object({
29270
+ mode: _enum([
29271
+ "idle",
29272
+ "cleaning",
29273
+ "spot",
29274
+ "following",
29275
+ "goto",
29276
+ "returning",
29277
+ "paused",
29278
+ "unknown"
29279
+ ]),
29280
+ following: boolean(),
29281
+ flash: boolean(),
29282
+ lightMode: NavigationLightModeSchema,
29283
+ lightLevel: number().min(40).max(100),
29284
+ /** Ms epoch when the slice was last updated. */
29285
+ lastChangedAt: number()
29286
+ });
29287
+ /**
29288
+ * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
29289
+ * observable). Adds `lastFetchedAt` on top of the status shape per the
29290
+ * convention.
29291
+ */
29292
+ var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: number() });
29293
+ var navigationCapability = {
29294
+ name: "navigation",
29295
+ scope: "device",
29296
+ deviceNative: true,
29297
+ mode: "singleton",
29298
+ deviceTypes: [DeviceType.Camera],
29299
+ deviceConfig: { ui: {
29300
+ kind: "widget",
29301
+ widgetId: "host/navigation-panel",
29302
+ tab: "navigation",
29303
+ topTab: true,
29304
+ label: "Navigation",
29305
+ order: 0
29306
+ } },
29307
+ methods: {
29308
+ /**
29309
+ * Momentary drive nudge (the robot moves). `protected` — mirrors
29310
+ * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
29311
+ * path) works for any authenticated user, not admin-only. The UI sends
29312
+ * these at ~1 Hz while a control is held; the provider forwards each one to
29313
+ * a single drive write WITHOUT debouncing.
29314
+ */
29315
+ move: method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29316
+ /** Halt all motion immediately (zero drive vector). */
29317
+ stop: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
29318
+ /** Send the robot to a point on its live map. */
29319
+ goToPoint: method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29320
+ /**
29321
+ * Enumerate the discrete controls THIS device supports (data-driven UI +
29322
+ * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
29323
+ */
29324
+ listActions: method(object({ deviceId: number() }), array(NavigationActionEntrySchema)),
29325
+ /**
29326
+ * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
29327
+ * unsupported action ids are rejected by the provider.
29328
+ */
29329
+ runAction: method(object({
29330
+ deviceId: number(),
29331
+ actionId: NavigationActionIdSchema
29332
+ }), _void(), { kind: "mutation" }),
29333
+ /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
29334
+ playSound: method(object({
29335
+ deviceId: number(),
29336
+ soundId: number().int()
29337
+ }), _void(), { kind: "mutation" }),
29338
+ /**
29339
+ * Turn the on-camera fill light on / off (the `OpenFullLight` control —
29340
+ * works anytime, no active stream required).
29341
+ */
29342
+ setLightOn: method(object({
29343
+ deviceId: number(),
29344
+ on: boolean()
29345
+ }), _void(), { kind: "mutation" }),
29346
+ /**
29347
+ * Set the fill-light mode (auto vs manual). `manual` optionally carries the
29348
+ * initial `level`. The auto/manual + level control is a CAMERA-service
29349
+ * action that generally needs an active camera stream/monitor session — the
29350
+ * UI shows the manual level slider ONLY when `mode === 'manual'`.
29351
+ */
29352
+ setLightMode: method(object({
29353
+ deviceId: number(),
29354
+ mode: NavigationLightModeSchema,
29355
+ level: number().min(40).max(100).optional()
29356
+ }), _void(), { kind: "mutation" }),
29357
+ /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
29358
+ setLightLevel: method(object({
29359
+ deviceId: number(),
29360
+ level: number().min(40).max(100)
29361
+ }), _void(), { kind: "mutation" }),
29362
+ /**
29363
+ * Per-device FEATURE-FLAG report for the general primitives — drives which
29364
+ * controls the UI shows (the per-entry flags for the dictionary come back on
29365
+ * `listActions`).
29366
+ */
29367
+ getFeatures: method(object({ deviceId: number() }), NavigationFeaturesSchema)
29368
+ },
29369
+ events: { onStatusChanged: { data: object({
29370
+ deviceId: number(),
29371
+ status: NavigationStatusSchema
29372
+ }) } },
29373
+ status: {
29374
+ schema: NavigationStatusSchema,
29375
+ kind: "push"
29376
+ },
29377
+ /**
29378
+ * Runtime-state slice mirrored by the kernel. The navigation panel watches it
29379
+ * for live mode / follow / flash changes.
29380
+ */
29381
+ runtimeState: NavigationRuntimeStateSchema,
29382
+ /**
29383
+ * Runtime-state durability: **session** — like `vacuum-control`, a restored
29384
+ * `mode: cleaning` / `following: true` is a robot that is not actually doing
29385
+ * that. The live handle re-publishes on connect.
29386
+ *
29387
+ * See `RuntimeStateDurability`. Enforced by
29388
+ * `scripts/check-runtime-state-durability.ts`.
29389
+ */
29390
+ durability: "session"
29391
+ };
29111
29392
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
29112
29393
  kind: "mutation",
29113
29394
  auth: "admin"
@@ -32383,6 +32664,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
32383
32664
  motionTrigger: motionTriggerCapability,
32384
32665
  motionZones: motionZonesCapability,
32385
32666
  nativeObjectDetection: nativeObjectDetectionCapability,
32667
+ navigation: navigationCapability,
32386
32668
  networkLink: networkLinkCapability,
32387
32669
  notifier: notifierCapability,
32388
32670
  numericSensor: numericSensorCapability,
@@ -36237,6 +36519,66 @@ Object.freeze({
36237
36519
  addonId: null,
36238
36520
  access: "create"
36239
36521
  },
36522
+ "navigation.getFeatures": {
36523
+ capName: "navigation",
36524
+ capScope: "device",
36525
+ addonId: null,
36526
+ access: "view"
36527
+ },
36528
+ "navigation.goToPoint": {
36529
+ capName: "navigation",
36530
+ capScope: "device",
36531
+ addonId: null,
36532
+ access: "create"
36533
+ },
36534
+ "navigation.listActions": {
36535
+ capName: "navigation",
36536
+ capScope: "device",
36537
+ addonId: null,
36538
+ access: "view"
36539
+ },
36540
+ "navigation.move": {
36541
+ capName: "navigation",
36542
+ capScope: "device",
36543
+ addonId: null,
36544
+ access: "create"
36545
+ },
36546
+ "navigation.playSound": {
36547
+ capName: "navigation",
36548
+ capScope: "device",
36549
+ addonId: null,
36550
+ access: "create"
36551
+ },
36552
+ "navigation.runAction": {
36553
+ capName: "navigation",
36554
+ capScope: "device",
36555
+ addonId: null,
36556
+ access: "create"
36557
+ },
36558
+ "navigation.setLightLevel": {
36559
+ capName: "navigation",
36560
+ capScope: "device",
36561
+ addonId: null,
36562
+ access: "create"
36563
+ },
36564
+ "navigation.setLightMode": {
36565
+ capName: "navigation",
36566
+ capScope: "device",
36567
+ addonId: null,
36568
+ access: "create"
36569
+ },
36570
+ "navigation.setLightOn": {
36571
+ capName: "navigation",
36572
+ capScope: "device",
36573
+ addonId: null,
36574
+ access: "create"
36575
+ },
36576
+ "navigation.stop": {
36577
+ capName: "navigation",
36578
+ capScope: "device",
36579
+ addonId: null,
36580
+ access: "create"
36581
+ },
36240
36582
  "networkAccess.getEndpoint": {
36241
36583
  capName: "network-access",
36242
36584
  capScope: "system",
@@ -40332,6 +40674,56 @@ Object.freeze({
40332
40674
  form: "single",
40333
40675
  optional: false
40334
40676
  }],
40677
+ "navigation.getFeatures": [{
40678
+ name: "deviceId",
40679
+ form: "single",
40680
+ optional: false
40681
+ }],
40682
+ "navigation.goToPoint": [{
40683
+ name: "deviceId",
40684
+ form: "single",
40685
+ optional: false
40686
+ }],
40687
+ "navigation.listActions": [{
40688
+ name: "deviceId",
40689
+ form: "single",
40690
+ optional: false
40691
+ }],
40692
+ "navigation.move": [{
40693
+ name: "deviceId",
40694
+ form: "single",
40695
+ optional: false
40696
+ }],
40697
+ "navigation.playSound": [{
40698
+ name: "deviceId",
40699
+ form: "single",
40700
+ optional: false
40701
+ }],
40702
+ "navigation.runAction": [{
40703
+ name: "deviceId",
40704
+ form: "single",
40705
+ optional: false
40706
+ }],
40707
+ "navigation.setLightLevel": [{
40708
+ name: "deviceId",
40709
+ form: "single",
40710
+ optional: false
40711
+ }],
40712
+ "navigation.setLightMode": [{
40713
+ name: "deviceId",
40714
+ form: "single",
40715
+ optional: false
40716
+ }],
40717
+ "navigation.setLightOn": [{
40718
+ name: "deviceId",
40719
+ form: "single",
40720
+ optional: false
40721
+ }],
40722
+ "navigation.stop": [{
40723
+ name: "deviceId",
40724
+ form: "single",
40725
+ optional: false
40726
+ }],
40335
40727
  "networkQuality.getDeviceStats": [{
40336
40728
  name: "deviceId",
40337
40729
  form: "single",
package/dist/addon.mjs CHANGED
@@ -29087,6 +29087,287 @@ var ptzAutotrackCapability = {
29087
29087
  */
29088
29088
  durability: "session"
29089
29089
  };
29090
+ /**
29091
+ * `navigation` — a device-scoped capability that natively expresses the FULL
29092
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
29093
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
29094
+ *
29095
+ * Why a NEW cap rather than overloading `ptz`:
29096
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
29097
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
29098
+ * The two are different physical models: PTZ is absolute-position + presets,
29099
+ * navigation is momentary drive nudges + discrete robot ACTIONS
29100
+ * (dock / spot-clean / follow-pet / go-to-point / …).
29101
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
29102
+ * the reverse:
29103
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
29104
+ * / `getOptions`), and
29105
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
29106
+ * robot camera shows up in the existing PTZ control path without every
29107
+ * PTZ provider learning about robots. The mapping lives in the adapter,
29108
+ * not here (see the addon design note):
29109
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
29110
+ * ptz.stop() → navigation.stop()
29111
+ * ptz.goHome() → navigation.runAction('goHome')
29112
+ * ptz.getPresets() → navigation.listActions() (id→preset)
29113
+ * ptz.goToPreset(id) → navigation.runAction(id)
29114
+ *
29115
+ * ## Continuous drive
29116
+ *
29117
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
29118
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
29119
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
29120
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
29121
+ * coalesce them. The UI owns the cadence.
29122
+ *
29123
+ * ## The action dictionary
29124
+ *
29125
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
29126
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
29127
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
29128
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
29129
+ * vendor-specific list. `kind: 'action'` entries are triggered with
29130
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
29131
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
29132
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
29133
+ *
29134
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
29135
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
29136
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
29137
+ * every device handle. A future nodedreame publish adds a typed
29138
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
29139
+ * provider can then swap the raw calls for the typed methods with no change to
29140
+ * THIS contract.
29141
+ */
29142
+ /**
29143
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
29144
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
29145
+ * halts it.
29146
+ *
29147
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
29148
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
29149
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
29150
+ * vector by it (drivers without proportional drive ignore it).
29151
+ *
29152
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
29153
+ * axis alone; an all-undefined nudge is a no-op.
29154
+ */
29155
+ var NavigationMoveCommandSchema = object({
29156
+ pan: number().min(-1).max(1).optional(),
29157
+ tilt: number().min(-1).max(1).optional(),
29158
+ speed: number().min(0).max(1).optional()
29159
+ });
29160
+ /**
29161
+ * The enumerated discrete actions a navigation-capable robot can perform via
29162
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
29163
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
29164
+ * `playSound` (see the `sound` dictionary entries).
29165
+ */
29166
+ var NavigationActionIdSchema = _enum([
29167
+ "goHome",
29168
+ "locate",
29169
+ "spotClean",
29170
+ "findPet",
29171
+ "personFollow",
29172
+ "stop",
29173
+ "startClean",
29174
+ "pauseClean",
29175
+ "dockWash",
29176
+ "autoEmpty",
29177
+ "flashOn",
29178
+ "flashOff"
29179
+ ]);
29180
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
29181
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
29182
+ /**
29183
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
29184
+ * native panel and the PTZ mimic render as a button.
29185
+ *
29186
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
29187
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
29188
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
29189
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
29190
+ * - `label` — operator-facing English label.
29191
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
29192
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
29193
+ * PTZ render ONLY enabled entries. Data-driven: the provider
29194
+ * flips it from config, never by editing code.
29195
+ */
29196
+ var NavigationActionEntrySchema = object({
29197
+ id: string(),
29198
+ kind: NavigationEntryKindSchema,
29199
+ label: string(),
29200
+ icon: string(),
29201
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
29202
+ soundId: number().int().optional(),
29203
+ /** Per-device feature flag — render this entry only when true. */
29204
+ enabled: boolean()
29205
+ });
29206
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
29207
+ var NavigationPointSchema = object({
29208
+ x: number(),
29209
+ y: number()
29210
+ });
29211
+ /**
29212
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
29213
+ * The cap reports which are enabled so the UI / PTZ render only the controls
29214
+ * that are turned on for THIS device. Data-driven: the provider derives these
29215
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
29216
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
29217
+ * that are not dictionary entries.
29218
+ *
29219
+ * - `move` / `stop` — the momentary drive joystick.
29220
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
29221
+ * map-coordinate plumbing is wired.
29222
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
29223
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
29224
+ * - `light` — the on/off fill-light toggle (works anytime).
29225
+ * - `lightMode` — the auto/manual selector + manual level slider (a
29226
+ * camera-service control; needs an active stream).
29227
+ */
29228
+ var NavigationFeaturesSchema = object({
29229
+ move: boolean(),
29230
+ stop: boolean(),
29231
+ goToPoint: boolean(),
29232
+ runAction: boolean(),
29233
+ playSound: boolean(),
29234
+ light: boolean(),
29235
+ lightMode: boolean()
29236
+ });
29237
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
29238
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
29239
+ /**
29240
+ * Live navigation state so the UI can reflect what the robot is doing:
29241
+ * - `mode` — coarse activity (idle / cleaning / following / …).
29242
+ * - `following` — person/pet follow is currently armed.
29243
+ * - `flash` — the on-camera fill light is on.
29244
+ * - `lightMode` — auto vs manual fill-light mode.
29245
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
29246
+ * `lightMode === 'manual'`.
29247
+ */
29248
+ var NavigationStatusSchema = object({
29249
+ mode: _enum([
29250
+ "idle",
29251
+ "cleaning",
29252
+ "spot",
29253
+ "following",
29254
+ "goto",
29255
+ "returning",
29256
+ "paused",
29257
+ "unknown"
29258
+ ]),
29259
+ following: boolean(),
29260
+ flash: boolean(),
29261
+ lightMode: NavigationLightModeSchema,
29262
+ lightLevel: number().min(40).max(100),
29263
+ /** Ms epoch when the slice was last updated. */
29264
+ lastChangedAt: number()
29265
+ });
29266
+ /**
29267
+ * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
29268
+ * observable). Adds `lastFetchedAt` on top of the status shape per the
29269
+ * convention.
29270
+ */
29271
+ var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: number() });
29272
+ var navigationCapability = {
29273
+ name: "navigation",
29274
+ scope: "device",
29275
+ deviceNative: true,
29276
+ mode: "singleton",
29277
+ deviceTypes: [DeviceType.Camera],
29278
+ deviceConfig: { ui: {
29279
+ kind: "widget",
29280
+ widgetId: "host/navigation-panel",
29281
+ tab: "navigation",
29282
+ topTab: true,
29283
+ label: "Navigation",
29284
+ order: 0
29285
+ } },
29286
+ methods: {
29287
+ /**
29288
+ * Momentary drive nudge (the robot moves). `protected` — mirrors
29289
+ * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
29290
+ * path) works for any authenticated user, not admin-only. The UI sends
29291
+ * these at ~1 Hz while a control is held; the provider forwards each one to
29292
+ * a single drive write WITHOUT debouncing.
29293
+ */
29294
+ move: method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29295
+ /** Halt all motion immediately (zero drive vector). */
29296
+ stop: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
29297
+ /** Send the robot to a point on its live map. */
29298
+ goToPoint: method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29299
+ /**
29300
+ * Enumerate the discrete controls THIS device supports (data-driven UI +
29301
+ * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
29302
+ */
29303
+ listActions: method(object({ deviceId: number() }), array(NavigationActionEntrySchema)),
29304
+ /**
29305
+ * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
29306
+ * unsupported action ids are rejected by the provider.
29307
+ */
29308
+ runAction: method(object({
29309
+ deviceId: number(),
29310
+ actionId: NavigationActionIdSchema
29311
+ }), _void(), { kind: "mutation" }),
29312
+ /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
29313
+ playSound: method(object({
29314
+ deviceId: number(),
29315
+ soundId: number().int()
29316
+ }), _void(), { kind: "mutation" }),
29317
+ /**
29318
+ * Turn the on-camera fill light on / off (the `OpenFullLight` control —
29319
+ * works anytime, no active stream required).
29320
+ */
29321
+ setLightOn: method(object({
29322
+ deviceId: number(),
29323
+ on: boolean()
29324
+ }), _void(), { kind: "mutation" }),
29325
+ /**
29326
+ * Set the fill-light mode (auto vs manual). `manual` optionally carries the
29327
+ * initial `level`. The auto/manual + level control is a CAMERA-service
29328
+ * action that generally needs an active camera stream/monitor session — the
29329
+ * UI shows the manual level slider ONLY when `mode === 'manual'`.
29330
+ */
29331
+ setLightMode: method(object({
29332
+ deviceId: number(),
29333
+ mode: NavigationLightModeSchema,
29334
+ level: number().min(40).max(100).optional()
29335
+ }), _void(), { kind: "mutation" }),
29336
+ /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
29337
+ setLightLevel: method(object({
29338
+ deviceId: number(),
29339
+ level: number().min(40).max(100)
29340
+ }), _void(), { kind: "mutation" }),
29341
+ /**
29342
+ * Per-device FEATURE-FLAG report for the general primitives — drives which
29343
+ * controls the UI shows (the per-entry flags for the dictionary come back on
29344
+ * `listActions`).
29345
+ */
29346
+ getFeatures: method(object({ deviceId: number() }), NavigationFeaturesSchema)
29347
+ },
29348
+ events: { onStatusChanged: { data: object({
29349
+ deviceId: number(),
29350
+ status: NavigationStatusSchema
29351
+ }) } },
29352
+ status: {
29353
+ schema: NavigationStatusSchema,
29354
+ kind: "push"
29355
+ },
29356
+ /**
29357
+ * Runtime-state slice mirrored by the kernel. The navigation panel watches it
29358
+ * for live mode / follow / flash changes.
29359
+ */
29360
+ runtimeState: NavigationRuntimeStateSchema,
29361
+ /**
29362
+ * Runtime-state durability: **session** — like `vacuum-control`, a restored
29363
+ * `mode: cleaning` / `following: true` is a robot that is not actually doing
29364
+ * that. The live handle re-publishes on connect.
29365
+ *
29366
+ * See `RuntimeStateDurability`. Enforced by
29367
+ * `scripts/check-runtime-state-durability.ts`.
29368
+ */
29369
+ durability: "session"
29370
+ };
29090
29371
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
29091
29372
  kind: "mutation",
29092
29373
  auth: "admin"
@@ -32362,6 +32643,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
32362
32643
  motionTrigger: motionTriggerCapability,
32363
32644
  motionZones: motionZonesCapability,
32364
32645
  nativeObjectDetection: nativeObjectDetectionCapability,
32646
+ navigation: navigationCapability,
32365
32647
  networkLink: networkLinkCapability,
32366
32648
  notifier: notifierCapability,
32367
32649
  numericSensor: numericSensorCapability,
@@ -36216,6 +36498,66 @@ Object.freeze({
36216
36498
  addonId: null,
36217
36499
  access: "create"
36218
36500
  },
36501
+ "navigation.getFeatures": {
36502
+ capName: "navigation",
36503
+ capScope: "device",
36504
+ addonId: null,
36505
+ access: "view"
36506
+ },
36507
+ "navigation.goToPoint": {
36508
+ capName: "navigation",
36509
+ capScope: "device",
36510
+ addonId: null,
36511
+ access: "create"
36512
+ },
36513
+ "navigation.listActions": {
36514
+ capName: "navigation",
36515
+ capScope: "device",
36516
+ addonId: null,
36517
+ access: "view"
36518
+ },
36519
+ "navigation.move": {
36520
+ capName: "navigation",
36521
+ capScope: "device",
36522
+ addonId: null,
36523
+ access: "create"
36524
+ },
36525
+ "navigation.playSound": {
36526
+ capName: "navigation",
36527
+ capScope: "device",
36528
+ addonId: null,
36529
+ access: "create"
36530
+ },
36531
+ "navigation.runAction": {
36532
+ capName: "navigation",
36533
+ capScope: "device",
36534
+ addonId: null,
36535
+ access: "create"
36536
+ },
36537
+ "navigation.setLightLevel": {
36538
+ capName: "navigation",
36539
+ capScope: "device",
36540
+ addonId: null,
36541
+ access: "create"
36542
+ },
36543
+ "navigation.setLightMode": {
36544
+ capName: "navigation",
36545
+ capScope: "device",
36546
+ addonId: null,
36547
+ access: "create"
36548
+ },
36549
+ "navigation.setLightOn": {
36550
+ capName: "navigation",
36551
+ capScope: "device",
36552
+ addonId: null,
36553
+ access: "create"
36554
+ },
36555
+ "navigation.stop": {
36556
+ capName: "navigation",
36557
+ capScope: "device",
36558
+ addonId: null,
36559
+ access: "create"
36560
+ },
36219
36561
  "networkAccess.getEndpoint": {
36220
36562
  capName: "network-access",
36221
36563
  capScope: "system",
@@ -40311,6 +40653,56 @@ Object.freeze({
40311
40653
  form: "single",
40312
40654
  optional: false
40313
40655
  }],
40656
+ "navigation.getFeatures": [{
40657
+ name: "deviceId",
40658
+ form: "single",
40659
+ optional: false
40660
+ }],
40661
+ "navigation.goToPoint": [{
40662
+ name: "deviceId",
40663
+ form: "single",
40664
+ optional: false
40665
+ }],
40666
+ "navigation.listActions": [{
40667
+ name: "deviceId",
40668
+ form: "single",
40669
+ optional: false
40670
+ }],
40671
+ "navigation.move": [{
40672
+ name: "deviceId",
40673
+ form: "single",
40674
+ optional: false
40675
+ }],
40676
+ "navigation.playSound": [{
40677
+ name: "deviceId",
40678
+ form: "single",
40679
+ optional: false
40680
+ }],
40681
+ "navigation.runAction": [{
40682
+ name: "deviceId",
40683
+ form: "single",
40684
+ optional: false
40685
+ }],
40686
+ "navigation.setLightLevel": [{
40687
+ name: "deviceId",
40688
+ form: "single",
40689
+ optional: false
40690
+ }],
40691
+ "navigation.setLightMode": [{
40692
+ name: "deviceId",
40693
+ form: "single",
40694
+ optional: false
40695
+ }],
40696
+ "navigation.setLightOn": [{
40697
+ name: "deviceId",
40698
+ form: "single",
40699
+ optional: false
40700
+ }],
40701
+ "navigation.stop": [{
40702
+ name: "deviceId",
40703
+ form: "single",
40704
+ optional: false
40705
+ }],
40314
40706
  "networkQuality.getDeviceStats": [{
40315
40707
  name: "deviceId",
40316
40708
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-wyze",
3
- "version": "0.2.74",
3
+ "version": "0.2.75",
4
4
  "description": "Wyze camera device-provider addon for CamStack — wraps the @apocaliss92/wyze-bridge-js P2P/DTLS client, feeding the stream-broker via the pull-rfc4571 lazy-publish path (a structural twin of addon-provider-reolink)",
5
5
  "keywords": [
6
6
  "camstack",