@camstack/addon-provider-petkit 0.2.70 → 0.2.71

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
@@ -30074,6 +30074,287 @@ var ptzAutotrackCapability = {
30074
30074
  */
30075
30075
  durability: "session"
30076
30076
  };
30077
+ /**
30078
+ * `navigation` — a device-scoped capability that natively expresses the FULL
30079
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
30080
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
30081
+ *
30082
+ * Why a NEW cap rather than overloading `ptz`:
30083
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
30084
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
30085
+ * The two are different physical models: PTZ is absolute-position + presets,
30086
+ * navigation is momentary drive nudges + discrete robot ACTIONS
30087
+ * (dock / spot-clean / follow-pet / go-to-point / …).
30088
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
30089
+ * the reverse:
30090
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
30091
+ * / `getOptions`), and
30092
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
30093
+ * robot camera shows up in the existing PTZ control path without every
30094
+ * PTZ provider learning about robots. The mapping lives in the adapter,
30095
+ * not here (see the addon design note):
30096
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
30097
+ * ptz.stop() → navigation.stop()
30098
+ * ptz.goHome() → navigation.runAction('goHome')
30099
+ * ptz.getPresets() → navigation.listActions() (id→preset)
30100
+ * ptz.goToPreset(id) → navigation.runAction(id)
30101
+ *
30102
+ * ## Continuous drive
30103
+ *
30104
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
30105
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
30106
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
30107
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
30108
+ * coalesce them. The UI owns the cadence.
30109
+ *
30110
+ * ## The action dictionary
30111
+ *
30112
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
30113
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
30114
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
30115
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
30116
+ * vendor-specific list. `kind: 'action'` entries are triggered with
30117
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
30118
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
30119
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
30120
+ *
30121
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
30122
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
30123
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
30124
+ * every device handle. A future nodedreame publish adds a typed
30125
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
30126
+ * provider can then swap the raw calls for the typed methods with no change to
30127
+ * THIS contract.
30128
+ */
30129
+ /**
30130
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
30131
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
30132
+ * halts it.
30133
+ *
30134
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
30135
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
30136
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
30137
+ * vector by it (drivers without proportional drive ignore it).
30138
+ *
30139
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
30140
+ * axis alone; an all-undefined nudge is a no-op.
30141
+ */
30142
+ var NavigationMoveCommandSchema = object({
30143
+ pan: number().min(-1).max(1).optional(),
30144
+ tilt: number().min(-1).max(1).optional(),
30145
+ speed: number().min(0).max(1).optional()
30146
+ });
30147
+ /**
30148
+ * The enumerated discrete actions a navigation-capable robot can perform via
30149
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
30150
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
30151
+ * `playSound` (see the `sound` dictionary entries).
30152
+ */
30153
+ var NavigationActionIdSchema = _enum([
30154
+ "goHome",
30155
+ "locate",
30156
+ "spotClean",
30157
+ "findPet",
30158
+ "personFollow",
30159
+ "stop",
30160
+ "startClean",
30161
+ "pauseClean",
30162
+ "dockWash",
30163
+ "autoEmpty",
30164
+ "flashOn",
30165
+ "flashOff"
30166
+ ]);
30167
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
30168
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
30169
+ /**
30170
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
30171
+ * native panel and the PTZ mimic render as a button.
30172
+ *
30173
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
30174
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
30175
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
30176
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
30177
+ * - `label` — operator-facing English label.
30178
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
30179
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
30180
+ * PTZ render ONLY enabled entries. Data-driven: the provider
30181
+ * flips it from config, never by editing code.
30182
+ */
30183
+ var NavigationActionEntrySchema = object({
30184
+ id: string(),
30185
+ kind: NavigationEntryKindSchema,
30186
+ label: string(),
30187
+ icon: string(),
30188
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
30189
+ soundId: number().int().optional(),
30190
+ /** Per-device feature flag — render this entry only when true. */
30191
+ enabled: boolean()
30192
+ });
30193
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
30194
+ var NavigationPointSchema = object({
30195
+ x: number(),
30196
+ y: number()
30197
+ });
30198
+ /**
30199
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
30200
+ * The cap reports which are enabled so the UI / PTZ render only the controls
30201
+ * that are turned on for THIS device. Data-driven: the provider derives these
30202
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
30203
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
30204
+ * that are not dictionary entries.
30205
+ *
30206
+ * - `move` / `stop` — the momentary drive joystick.
30207
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
30208
+ * map-coordinate plumbing is wired.
30209
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
30210
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
30211
+ * - `light` — the on/off fill-light toggle (works anytime).
30212
+ * - `lightMode` — the auto/manual selector + manual level slider (a
30213
+ * camera-service control; needs an active stream).
30214
+ */
30215
+ var NavigationFeaturesSchema = object({
30216
+ move: boolean(),
30217
+ stop: boolean(),
30218
+ goToPoint: boolean(),
30219
+ runAction: boolean(),
30220
+ playSound: boolean(),
30221
+ light: boolean(),
30222
+ lightMode: boolean()
30223
+ });
30224
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
30225
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
30226
+ /**
30227
+ * Live navigation state so the UI can reflect what the robot is doing:
30228
+ * - `mode` — coarse activity (idle / cleaning / following / …).
30229
+ * - `following` — person/pet follow is currently armed.
30230
+ * - `flash` — the on-camera fill light is on.
30231
+ * - `lightMode` — auto vs manual fill-light mode.
30232
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
30233
+ * `lightMode === 'manual'`.
30234
+ */
30235
+ var NavigationStatusSchema = object({
30236
+ mode: _enum([
30237
+ "idle",
30238
+ "cleaning",
30239
+ "spot",
30240
+ "following",
30241
+ "goto",
30242
+ "returning",
30243
+ "paused",
30244
+ "unknown"
30245
+ ]),
30246
+ following: boolean(),
30247
+ flash: boolean(),
30248
+ lightMode: NavigationLightModeSchema,
30249
+ lightLevel: number().min(40).max(100),
30250
+ /** Ms epoch when the slice was last updated. */
30251
+ lastChangedAt: number()
30252
+ });
30253
+ /**
30254
+ * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
30255
+ * observable). Adds `lastFetchedAt` on top of the status shape per the
30256
+ * convention.
30257
+ */
30258
+ var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: number() });
30259
+ var navigationCapability = {
30260
+ name: "navigation",
30261
+ scope: "device",
30262
+ deviceNative: true,
30263
+ mode: "singleton",
30264
+ deviceTypes: [DeviceType.Camera],
30265
+ deviceConfig: { ui: {
30266
+ kind: "widget",
30267
+ widgetId: "host/navigation-panel",
30268
+ tab: "navigation",
30269
+ topTab: true,
30270
+ label: "Navigation",
30271
+ order: 0
30272
+ } },
30273
+ methods: {
30274
+ /**
30275
+ * Momentary drive nudge (the robot moves). `protected` — mirrors
30276
+ * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
30277
+ * path) works for any authenticated user, not admin-only. The UI sends
30278
+ * these at ~1 Hz while a control is held; the provider forwards each one to
30279
+ * a single drive write WITHOUT debouncing.
30280
+ */
30281
+ move: method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
30282
+ /** Halt all motion immediately (zero drive vector). */
30283
+ stop: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
30284
+ /** Send the robot to a point on its live map. */
30285
+ goToPoint: method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
30286
+ /**
30287
+ * Enumerate the discrete controls THIS device supports (data-driven UI +
30288
+ * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
30289
+ */
30290
+ listActions: method(object({ deviceId: number() }), array(NavigationActionEntrySchema)),
30291
+ /**
30292
+ * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
30293
+ * unsupported action ids are rejected by the provider.
30294
+ */
30295
+ runAction: method(object({
30296
+ deviceId: number(),
30297
+ actionId: NavigationActionIdSchema
30298
+ }), _void(), { kind: "mutation" }),
30299
+ /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
30300
+ playSound: method(object({
30301
+ deviceId: number(),
30302
+ soundId: number().int()
30303
+ }), _void(), { kind: "mutation" }),
30304
+ /**
30305
+ * Turn the on-camera fill light on / off (the `OpenFullLight` control —
30306
+ * works anytime, no active stream required).
30307
+ */
30308
+ setLightOn: method(object({
30309
+ deviceId: number(),
30310
+ on: boolean()
30311
+ }), _void(), { kind: "mutation" }),
30312
+ /**
30313
+ * Set the fill-light mode (auto vs manual). `manual` optionally carries the
30314
+ * initial `level`. The auto/manual + level control is a CAMERA-service
30315
+ * action that generally needs an active camera stream/monitor session — the
30316
+ * UI shows the manual level slider ONLY when `mode === 'manual'`.
30317
+ */
30318
+ setLightMode: method(object({
30319
+ deviceId: number(),
30320
+ mode: NavigationLightModeSchema,
30321
+ level: number().min(40).max(100).optional()
30322
+ }), _void(), { kind: "mutation" }),
30323
+ /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
30324
+ setLightLevel: method(object({
30325
+ deviceId: number(),
30326
+ level: number().min(40).max(100)
30327
+ }), _void(), { kind: "mutation" }),
30328
+ /**
30329
+ * Per-device FEATURE-FLAG report for the general primitives — drives which
30330
+ * controls the UI shows (the per-entry flags for the dictionary come back on
30331
+ * `listActions`).
30332
+ */
30333
+ getFeatures: method(object({ deviceId: number() }), NavigationFeaturesSchema)
30334
+ },
30335
+ events: { onStatusChanged: { data: object({
30336
+ deviceId: number(),
30337
+ status: NavigationStatusSchema
30338
+ }) } },
30339
+ status: {
30340
+ schema: NavigationStatusSchema,
30341
+ kind: "push"
30342
+ },
30343
+ /**
30344
+ * Runtime-state slice mirrored by the kernel. The navigation panel watches it
30345
+ * for live mode / follow / flash changes.
30346
+ */
30347
+ runtimeState: NavigationRuntimeStateSchema,
30348
+ /**
30349
+ * Runtime-state durability: **session** — like `vacuum-control`, a restored
30350
+ * `mode: cleaning` / `following: true` is a robot that is not actually doing
30351
+ * that. The live handle re-publishes on connect.
30352
+ *
30353
+ * See `RuntimeStateDurability`. Enforced by
30354
+ * `scripts/check-runtime-state-durability.ts`.
30355
+ */
30356
+ durability: "session"
30357
+ };
30077
30358
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
30078
30359
  kind: "mutation",
30079
30360
  auth: "admin"
@@ -33349,6 +33630,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
33349
33630
  motionTrigger: motionTriggerCapability,
33350
33631
  motionZones: motionZonesCapability,
33351
33632
  nativeObjectDetection: nativeObjectDetectionCapability,
33633
+ navigation: navigationCapability,
33352
33634
  networkLink: networkLinkCapability,
33353
33635
  notifier: notifierCapability,
33354
33636
  numericSensor: numericSensorCapability,
@@ -37203,6 +37485,66 @@ Object.freeze({
37203
37485
  addonId: null,
37204
37486
  access: "create"
37205
37487
  },
37488
+ "navigation.getFeatures": {
37489
+ capName: "navigation",
37490
+ capScope: "device",
37491
+ addonId: null,
37492
+ access: "view"
37493
+ },
37494
+ "navigation.goToPoint": {
37495
+ capName: "navigation",
37496
+ capScope: "device",
37497
+ addonId: null,
37498
+ access: "create"
37499
+ },
37500
+ "navigation.listActions": {
37501
+ capName: "navigation",
37502
+ capScope: "device",
37503
+ addonId: null,
37504
+ access: "view"
37505
+ },
37506
+ "navigation.move": {
37507
+ capName: "navigation",
37508
+ capScope: "device",
37509
+ addonId: null,
37510
+ access: "create"
37511
+ },
37512
+ "navigation.playSound": {
37513
+ capName: "navigation",
37514
+ capScope: "device",
37515
+ addonId: null,
37516
+ access: "create"
37517
+ },
37518
+ "navigation.runAction": {
37519
+ capName: "navigation",
37520
+ capScope: "device",
37521
+ addonId: null,
37522
+ access: "create"
37523
+ },
37524
+ "navigation.setLightLevel": {
37525
+ capName: "navigation",
37526
+ capScope: "device",
37527
+ addonId: null,
37528
+ access: "create"
37529
+ },
37530
+ "navigation.setLightMode": {
37531
+ capName: "navigation",
37532
+ capScope: "device",
37533
+ addonId: null,
37534
+ access: "create"
37535
+ },
37536
+ "navigation.setLightOn": {
37537
+ capName: "navigation",
37538
+ capScope: "device",
37539
+ addonId: null,
37540
+ access: "create"
37541
+ },
37542
+ "navigation.stop": {
37543
+ capName: "navigation",
37544
+ capScope: "device",
37545
+ addonId: null,
37546
+ access: "create"
37547
+ },
37206
37548
  "networkAccess.getEndpoint": {
37207
37549
  capName: "network-access",
37208
37550
  capScope: "system",
@@ -41298,6 +41640,56 @@ Object.freeze({
41298
41640
  form: "single",
41299
41641
  optional: false
41300
41642
  }],
41643
+ "navigation.getFeatures": [{
41644
+ name: "deviceId",
41645
+ form: "single",
41646
+ optional: false
41647
+ }],
41648
+ "navigation.goToPoint": [{
41649
+ name: "deviceId",
41650
+ form: "single",
41651
+ optional: false
41652
+ }],
41653
+ "navigation.listActions": [{
41654
+ name: "deviceId",
41655
+ form: "single",
41656
+ optional: false
41657
+ }],
41658
+ "navigation.move": [{
41659
+ name: "deviceId",
41660
+ form: "single",
41661
+ optional: false
41662
+ }],
41663
+ "navigation.playSound": [{
41664
+ name: "deviceId",
41665
+ form: "single",
41666
+ optional: false
41667
+ }],
41668
+ "navigation.runAction": [{
41669
+ name: "deviceId",
41670
+ form: "single",
41671
+ optional: false
41672
+ }],
41673
+ "navigation.setLightLevel": [{
41674
+ name: "deviceId",
41675
+ form: "single",
41676
+ optional: false
41677
+ }],
41678
+ "navigation.setLightMode": [{
41679
+ name: "deviceId",
41680
+ form: "single",
41681
+ optional: false
41682
+ }],
41683
+ "navigation.setLightOn": [{
41684
+ name: "deviceId",
41685
+ form: "single",
41686
+ optional: false
41687
+ }],
41688
+ "navigation.stop": [{
41689
+ name: "deviceId",
41690
+ form: "single",
41691
+ optional: false
41692
+ }],
41301
41693
  "networkQuality.getDeviceStats": [{
41302
41694
  name: "deviceId",
41303
41695
  form: "single",
package/dist/addon.mjs CHANGED
@@ -30073,6 +30073,287 @@ var ptzAutotrackCapability = {
30073
30073
  */
30074
30074
  durability: "session"
30075
30075
  };
30076
+ /**
30077
+ * `navigation` — a device-scoped capability that natively expresses the FULL
30078
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
30079
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
30080
+ *
30081
+ * Why a NEW cap rather than overloading `ptz`:
30082
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
30083
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
30084
+ * The two are different physical models: PTZ is absolute-position + presets,
30085
+ * navigation is momentary drive nudges + discrete robot ACTIONS
30086
+ * (dock / spot-clean / follow-pet / go-to-point / …).
30087
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
30088
+ * the reverse:
30089
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
30090
+ * / `getOptions`), and
30091
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
30092
+ * robot camera shows up in the existing PTZ control path without every
30093
+ * PTZ provider learning about robots. The mapping lives in the adapter,
30094
+ * not here (see the addon design note):
30095
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
30096
+ * ptz.stop() → navigation.stop()
30097
+ * ptz.goHome() → navigation.runAction('goHome')
30098
+ * ptz.getPresets() → navigation.listActions() (id→preset)
30099
+ * ptz.goToPreset(id) → navigation.runAction(id)
30100
+ *
30101
+ * ## Continuous drive
30102
+ *
30103
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
30104
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
30105
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
30106
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
30107
+ * coalesce them. The UI owns the cadence.
30108
+ *
30109
+ * ## The action dictionary
30110
+ *
30111
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
30112
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
30113
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
30114
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
30115
+ * vendor-specific list. `kind: 'action'` entries are triggered with
30116
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
30117
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
30118
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
30119
+ *
30120
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
30121
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
30122
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
30123
+ * every device handle. A future nodedreame publish adds a typed
30124
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
30125
+ * provider can then swap the raw calls for the typed methods with no change to
30126
+ * THIS contract.
30127
+ */
30128
+ /**
30129
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
30130
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
30131
+ * halts it.
30132
+ *
30133
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
30134
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
30135
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
30136
+ * vector by it (drivers without proportional drive ignore it).
30137
+ *
30138
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
30139
+ * axis alone; an all-undefined nudge is a no-op.
30140
+ */
30141
+ var NavigationMoveCommandSchema = object({
30142
+ pan: number().min(-1).max(1).optional(),
30143
+ tilt: number().min(-1).max(1).optional(),
30144
+ speed: number().min(0).max(1).optional()
30145
+ });
30146
+ /**
30147
+ * The enumerated discrete actions a navigation-capable robot can perform via
30148
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
30149
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
30150
+ * `playSound` (see the `sound` dictionary entries).
30151
+ */
30152
+ var NavigationActionIdSchema = _enum([
30153
+ "goHome",
30154
+ "locate",
30155
+ "spotClean",
30156
+ "findPet",
30157
+ "personFollow",
30158
+ "stop",
30159
+ "startClean",
30160
+ "pauseClean",
30161
+ "dockWash",
30162
+ "autoEmpty",
30163
+ "flashOn",
30164
+ "flashOff"
30165
+ ]);
30166
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
30167
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
30168
+ /**
30169
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
30170
+ * native panel and the PTZ mimic render as a button.
30171
+ *
30172
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
30173
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
30174
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
30175
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
30176
+ * - `label` — operator-facing English label.
30177
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
30178
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
30179
+ * PTZ render ONLY enabled entries. Data-driven: the provider
30180
+ * flips it from config, never by editing code.
30181
+ */
30182
+ var NavigationActionEntrySchema = object({
30183
+ id: string(),
30184
+ kind: NavigationEntryKindSchema,
30185
+ label: string(),
30186
+ icon: string(),
30187
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
30188
+ soundId: number().int().optional(),
30189
+ /** Per-device feature flag — render this entry only when true. */
30190
+ enabled: boolean()
30191
+ });
30192
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
30193
+ var NavigationPointSchema = object({
30194
+ x: number(),
30195
+ y: number()
30196
+ });
30197
+ /**
30198
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
30199
+ * The cap reports which are enabled so the UI / PTZ render only the controls
30200
+ * that are turned on for THIS device. Data-driven: the provider derives these
30201
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
30202
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
30203
+ * that are not dictionary entries.
30204
+ *
30205
+ * - `move` / `stop` — the momentary drive joystick.
30206
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
30207
+ * map-coordinate plumbing is wired.
30208
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
30209
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
30210
+ * - `light` — the on/off fill-light toggle (works anytime).
30211
+ * - `lightMode` — the auto/manual selector + manual level slider (a
30212
+ * camera-service control; needs an active stream).
30213
+ */
30214
+ var NavigationFeaturesSchema = object({
30215
+ move: boolean(),
30216
+ stop: boolean(),
30217
+ goToPoint: boolean(),
30218
+ runAction: boolean(),
30219
+ playSound: boolean(),
30220
+ light: boolean(),
30221
+ lightMode: boolean()
30222
+ });
30223
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
30224
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
30225
+ /**
30226
+ * Live navigation state so the UI can reflect what the robot is doing:
30227
+ * - `mode` — coarse activity (idle / cleaning / following / …).
30228
+ * - `following` — person/pet follow is currently armed.
30229
+ * - `flash` — the on-camera fill light is on.
30230
+ * - `lightMode` — auto vs manual fill-light mode.
30231
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
30232
+ * `lightMode === 'manual'`.
30233
+ */
30234
+ var NavigationStatusSchema = object({
30235
+ mode: _enum([
30236
+ "idle",
30237
+ "cleaning",
30238
+ "spot",
30239
+ "following",
30240
+ "goto",
30241
+ "returning",
30242
+ "paused",
30243
+ "unknown"
30244
+ ]),
30245
+ following: boolean(),
30246
+ flash: boolean(),
30247
+ lightMode: NavigationLightModeSchema,
30248
+ lightLevel: number().min(40).max(100),
30249
+ /** Ms epoch when the slice was last updated. */
30250
+ lastChangedAt: number()
30251
+ });
30252
+ /**
30253
+ * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
30254
+ * observable). Adds `lastFetchedAt` on top of the status shape per the
30255
+ * convention.
30256
+ */
30257
+ var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: number() });
30258
+ var navigationCapability = {
30259
+ name: "navigation",
30260
+ scope: "device",
30261
+ deviceNative: true,
30262
+ mode: "singleton",
30263
+ deviceTypes: [DeviceType.Camera],
30264
+ deviceConfig: { ui: {
30265
+ kind: "widget",
30266
+ widgetId: "host/navigation-panel",
30267
+ tab: "navigation",
30268
+ topTab: true,
30269
+ label: "Navigation",
30270
+ order: 0
30271
+ } },
30272
+ methods: {
30273
+ /**
30274
+ * Momentary drive nudge (the robot moves). `protected` — mirrors
30275
+ * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
30276
+ * path) works for any authenticated user, not admin-only. The UI sends
30277
+ * these at ~1 Hz while a control is held; the provider forwards each one to
30278
+ * a single drive write WITHOUT debouncing.
30279
+ */
30280
+ move: method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
30281
+ /** Halt all motion immediately (zero drive vector). */
30282
+ stop: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
30283
+ /** Send the robot to a point on its live map. */
30284
+ goToPoint: method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
30285
+ /**
30286
+ * Enumerate the discrete controls THIS device supports (data-driven UI +
30287
+ * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
30288
+ */
30289
+ listActions: method(object({ deviceId: number() }), array(NavigationActionEntrySchema)),
30290
+ /**
30291
+ * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
30292
+ * unsupported action ids are rejected by the provider.
30293
+ */
30294
+ runAction: method(object({
30295
+ deviceId: number(),
30296
+ actionId: NavigationActionIdSchema
30297
+ }), _void(), { kind: "mutation" }),
30298
+ /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
30299
+ playSound: method(object({
30300
+ deviceId: number(),
30301
+ soundId: number().int()
30302
+ }), _void(), { kind: "mutation" }),
30303
+ /**
30304
+ * Turn the on-camera fill light on / off (the `OpenFullLight` control —
30305
+ * works anytime, no active stream required).
30306
+ */
30307
+ setLightOn: method(object({
30308
+ deviceId: number(),
30309
+ on: boolean()
30310
+ }), _void(), { kind: "mutation" }),
30311
+ /**
30312
+ * Set the fill-light mode (auto vs manual). `manual` optionally carries the
30313
+ * initial `level`. The auto/manual + level control is a CAMERA-service
30314
+ * action that generally needs an active camera stream/monitor session — the
30315
+ * UI shows the manual level slider ONLY when `mode === 'manual'`.
30316
+ */
30317
+ setLightMode: method(object({
30318
+ deviceId: number(),
30319
+ mode: NavigationLightModeSchema,
30320
+ level: number().min(40).max(100).optional()
30321
+ }), _void(), { kind: "mutation" }),
30322
+ /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
30323
+ setLightLevel: method(object({
30324
+ deviceId: number(),
30325
+ level: number().min(40).max(100)
30326
+ }), _void(), { kind: "mutation" }),
30327
+ /**
30328
+ * Per-device FEATURE-FLAG report for the general primitives — drives which
30329
+ * controls the UI shows (the per-entry flags for the dictionary come back on
30330
+ * `listActions`).
30331
+ */
30332
+ getFeatures: method(object({ deviceId: number() }), NavigationFeaturesSchema)
30333
+ },
30334
+ events: { onStatusChanged: { data: object({
30335
+ deviceId: number(),
30336
+ status: NavigationStatusSchema
30337
+ }) } },
30338
+ status: {
30339
+ schema: NavigationStatusSchema,
30340
+ kind: "push"
30341
+ },
30342
+ /**
30343
+ * Runtime-state slice mirrored by the kernel. The navigation panel watches it
30344
+ * for live mode / follow / flash changes.
30345
+ */
30346
+ runtimeState: NavigationRuntimeStateSchema,
30347
+ /**
30348
+ * Runtime-state durability: **session** — like `vacuum-control`, a restored
30349
+ * `mode: cleaning` / `following: true` is a robot that is not actually doing
30350
+ * that. The live handle re-publishes on connect.
30351
+ *
30352
+ * See `RuntimeStateDurability`. Enforced by
30353
+ * `scripts/check-runtime-state-durability.ts`.
30354
+ */
30355
+ durability: "session"
30356
+ };
30076
30357
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
30077
30358
  kind: "mutation",
30078
30359
  auth: "admin"
@@ -33348,6 +33629,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
33348
33629
  motionTrigger: motionTriggerCapability,
33349
33630
  motionZones: motionZonesCapability,
33350
33631
  nativeObjectDetection: nativeObjectDetectionCapability,
33632
+ navigation: navigationCapability,
33351
33633
  networkLink: networkLinkCapability,
33352
33634
  notifier: notifierCapability,
33353
33635
  numericSensor: numericSensorCapability,
@@ -37202,6 +37484,66 @@ Object.freeze({
37202
37484
  addonId: null,
37203
37485
  access: "create"
37204
37486
  },
37487
+ "navigation.getFeatures": {
37488
+ capName: "navigation",
37489
+ capScope: "device",
37490
+ addonId: null,
37491
+ access: "view"
37492
+ },
37493
+ "navigation.goToPoint": {
37494
+ capName: "navigation",
37495
+ capScope: "device",
37496
+ addonId: null,
37497
+ access: "create"
37498
+ },
37499
+ "navigation.listActions": {
37500
+ capName: "navigation",
37501
+ capScope: "device",
37502
+ addonId: null,
37503
+ access: "view"
37504
+ },
37505
+ "navigation.move": {
37506
+ capName: "navigation",
37507
+ capScope: "device",
37508
+ addonId: null,
37509
+ access: "create"
37510
+ },
37511
+ "navigation.playSound": {
37512
+ capName: "navigation",
37513
+ capScope: "device",
37514
+ addonId: null,
37515
+ access: "create"
37516
+ },
37517
+ "navigation.runAction": {
37518
+ capName: "navigation",
37519
+ capScope: "device",
37520
+ addonId: null,
37521
+ access: "create"
37522
+ },
37523
+ "navigation.setLightLevel": {
37524
+ capName: "navigation",
37525
+ capScope: "device",
37526
+ addonId: null,
37527
+ access: "create"
37528
+ },
37529
+ "navigation.setLightMode": {
37530
+ capName: "navigation",
37531
+ capScope: "device",
37532
+ addonId: null,
37533
+ access: "create"
37534
+ },
37535
+ "navigation.setLightOn": {
37536
+ capName: "navigation",
37537
+ capScope: "device",
37538
+ addonId: null,
37539
+ access: "create"
37540
+ },
37541
+ "navigation.stop": {
37542
+ capName: "navigation",
37543
+ capScope: "device",
37544
+ addonId: null,
37545
+ access: "create"
37546
+ },
37205
37547
  "networkAccess.getEndpoint": {
37206
37548
  capName: "network-access",
37207
37549
  capScope: "system",
@@ -41297,6 +41639,56 @@ Object.freeze({
41297
41639
  form: "single",
41298
41640
  optional: false
41299
41641
  }],
41642
+ "navigation.getFeatures": [{
41643
+ name: "deviceId",
41644
+ form: "single",
41645
+ optional: false
41646
+ }],
41647
+ "navigation.goToPoint": [{
41648
+ name: "deviceId",
41649
+ form: "single",
41650
+ optional: false
41651
+ }],
41652
+ "navigation.listActions": [{
41653
+ name: "deviceId",
41654
+ form: "single",
41655
+ optional: false
41656
+ }],
41657
+ "navigation.move": [{
41658
+ name: "deviceId",
41659
+ form: "single",
41660
+ optional: false
41661
+ }],
41662
+ "navigation.playSound": [{
41663
+ name: "deviceId",
41664
+ form: "single",
41665
+ optional: false
41666
+ }],
41667
+ "navigation.runAction": [{
41668
+ name: "deviceId",
41669
+ form: "single",
41670
+ optional: false
41671
+ }],
41672
+ "navigation.setLightLevel": [{
41673
+ name: "deviceId",
41674
+ form: "single",
41675
+ optional: false
41676
+ }],
41677
+ "navigation.setLightMode": [{
41678
+ name: "deviceId",
41679
+ form: "single",
41680
+ optional: false
41681
+ }],
41682
+ "navigation.setLightOn": [{
41683
+ name: "deviceId",
41684
+ form: "single",
41685
+ optional: false
41686
+ }],
41687
+ "navigation.stop": [{
41688
+ name: "deviceId",
41689
+ form: "single",
41690
+ optional: false
41691
+ }],
41300
41692
  "networkQuality.getDeviceStats": [{
41301
41693
  name: "deviceId",
41302
41694
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-petkit",
3
- "version": "0.2.70",
3
+ "version": "0.2.71",
4
4
  "description": "PetKit smart-feeder device-provider addon for CamStack — wraps the @apocaliss92/nodepetkit PetKit cloud client",
5
5
  "keywords": [
6
6
  "camstack",