@camstack/addon-matter-broker 0.2.69 → 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
@@ -5420,6 +5420,12 @@ Object.fromEntries([
5420
5420
  icon: "move",
5421
5421
  order: 40
5422
5422
  },
5423
+ {
5424
+ id: "navigation",
5425
+ label: "Navigation",
5426
+ icon: "compass",
5427
+ order: 41
5428
+ },
5423
5429
  {
5424
5430
  id: "consumables",
5425
5431
  label: "Consumables",
@@ -29044,6 +29050,287 @@ var ptzAutotrackCapability = {
29044
29050
  */
29045
29051
  durability: "session"
29046
29052
  };
29053
+ /**
29054
+ * `navigation` — a device-scoped capability that natively expresses the FULL
29055
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
29056
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
29057
+ *
29058
+ * Why a NEW cap rather than overloading `ptz`:
29059
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
29060
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
29061
+ * The two are different physical models: PTZ is absolute-position + presets,
29062
+ * navigation is momentary drive nudges + discrete robot ACTIONS
29063
+ * (dock / spot-clean / follow-pet / go-to-point / …).
29064
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
29065
+ * the reverse:
29066
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
29067
+ * / `getOptions`), and
29068
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
29069
+ * robot camera shows up in the existing PTZ control path without every
29070
+ * PTZ provider learning about robots. The mapping lives in the adapter,
29071
+ * not here (see the addon design note):
29072
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
29073
+ * ptz.stop() → navigation.stop()
29074
+ * ptz.goHome() → navigation.runAction('goHome')
29075
+ * ptz.getPresets() → navigation.listActions() (id→preset)
29076
+ * ptz.goToPreset(id) → navigation.runAction(id)
29077
+ *
29078
+ * ## Continuous drive
29079
+ *
29080
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
29081
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
29082
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
29083
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
29084
+ * coalesce them. The UI owns the cadence.
29085
+ *
29086
+ * ## The action dictionary
29087
+ *
29088
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
29089
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
29090
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
29091
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
29092
+ * vendor-specific list. `kind: 'action'` entries are triggered with
29093
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
29094
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
29095
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
29096
+ *
29097
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
29098
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
29099
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
29100
+ * every device handle. A future nodedreame publish adds a typed
29101
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
29102
+ * provider can then swap the raw calls for the typed methods with no change to
29103
+ * THIS contract.
29104
+ */
29105
+ /**
29106
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
29107
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
29108
+ * halts it.
29109
+ *
29110
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
29111
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
29112
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
29113
+ * vector by it (drivers without proportional drive ignore it).
29114
+ *
29115
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
29116
+ * axis alone; an all-undefined nudge is a no-op.
29117
+ */
29118
+ var NavigationMoveCommandSchema = object({
29119
+ pan: number().min(-1).max(1).optional(),
29120
+ tilt: number().min(-1).max(1).optional(),
29121
+ speed: number().min(0).max(1).optional()
29122
+ });
29123
+ /**
29124
+ * The enumerated discrete actions a navigation-capable robot can perform via
29125
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
29126
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
29127
+ * `playSound` (see the `sound` dictionary entries).
29128
+ */
29129
+ var NavigationActionIdSchema = _enum([
29130
+ "goHome",
29131
+ "locate",
29132
+ "spotClean",
29133
+ "findPet",
29134
+ "personFollow",
29135
+ "stop",
29136
+ "startClean",
29137
+ "pauseClean",
29138
+ "dockWash",
29139
+ "autoEmpty",
29140
+ "flashOn",
29141
+ "flashOff"
29142
+ ]);
29143
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
29144
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
29145
+ /**
29146
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
29147
+ * native panel and the PTZ mimic render as a button.
29148
+ *
29149
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
29150
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
29151
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
29152
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
29153
+ * - `label` — operator-facing English label.
29154
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
29155
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
29156
+ * PTZ render ONLY enabled entries. Data-driven: the provider
29157
+ * flips it from config, never by editing code.
29158
+ */
29159
+ var NavigationActionEntrySchema = object({
29160
+ id: string$2(),
29161
+ kind: NavigationEntryKindSchema,
29162
+ label: string$2(),
29163
+ icon: string$2(),
29164
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
29165
+ soundId: number().int().optional(),
29166
+ /** Per-device feature flag — render this entry only when true. */
29167
+ enabled: boolean()
29168
+ });
29169
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
29170
+ var NavigationPointSchema = object({
29171
+ x: number(),
29172
+ y: number()
29173
+ });
29174
+ /**
29175
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
29176
+ * The cap reports which are enabled so the UI / PTZ render only the controls
29177
+ * that are turned on for THIS device. Data-driven: the provider derives these
29178
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
29179
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
29180
+ * that are not dictionary entries.
29181
+ *
29182
+ * - `move` / `stop` — the momentary drive joystick.
29183
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
29184
+ * map-coordinate plumbing is wired.
29185
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
29186
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
29187
+ * - `light` — the on/off fill-light toggle (works anytime).
29188
+ * - `lightMode` — the auto/manual selector + manual level slider (a
29189
+ * camera-service control; needs an active stream).
29190
+ */
29191
+ var NavigationFeaturesSchema = object({
29192
+ move: boolean(),
29193
+ stop: boolean(),
29194
+ goToPoint: boolean(),
29195
+ runAction: boolean(),
29196
+ playSound: boolean(),
29197
+ light: boolean(),
29198
+ lightMode: boolean()
29199
+ });
29200
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
29201
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
29202
+ /**
29203
+ * Live navigation state so the UI can reflect what the robot is doing:
29204
+ * - `mode` — coarse activity (idle / cleaning / following / …).
29205
+ * - `following` — person/pet follow is currently armed.
29206
+ * - `flash` — the on-camera fill light is on.
29207
+ * - `lightMode` — auto vs manual fill-light mode.
29208
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
29209
+ * `lightMode === 'manual'`.
29210
+ */
29211
+ var NavigationStatusSchema = object({
29212
+ mode: _enum([
29213
+ "idle",
29214
+ "cleaning",
29215
+ "spot",
29216
+ "following",
29217
+ "goto",
29218
+ "returning",
29219
+ "paused",
29220
+ "unknown"
29221
+ ]),
29222
+ following: boolean(),
29223
+ flash: boolean(),
29224
+ lightMode: NavigationLightModeSchema,
29225
+ lightLevel: number().min(40).max(100),
29226
+ /** Ms epoch when the slice was last updated. */
29227
+ lastChangedAt: number()
29228
+ });
29229
+ /**
29230
+ * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
29231
+ * observable). Adds `lastFetchedAt` on top of the status shape per the
29232
+ * convention.
29233
+ */
29234
+ var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: number() });
29235
+ var navigationCapability = {
29236
+ name: "navigation",
29237
+ scope: "device",
29238
+ deviceNative: true,
29239
+ mode: "singleton",
29240
+ deviceTypes: [DeviceType.Camera],
29241
+ deviceConfig: { ui: {
29242
+ kind: "widget",
29243
+ widgetId: "host/navigation-panel",
29244
+ tab: "navigation",
29245
+ topTab: true,
29246
+ label: "Navigation",
29247
+ order: 0
29248
+ } },
29249
+ methods: {
29250
+ /**
29251
+ * Momentary drive nudge (the robot moves). `protected` — mirrors
29252
+ * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
29253
+ * path) works for any authenticated user, not admin-only. The UI sends
29254
+ * these at ~1 Hz while a control is held; the provider forwards each one to
29255
+ * a single drive write WITHOUT debouncing.
29256
+ */
29257
+ move: method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29258
+ /** Halt all motion immediately (zero drive vector). */
29259
+ stop: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
29260
+ /** Send the robot to a point on its live map. */
29261
+ goToPoint: method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29262
+ /**
29263
+ * Enumerate the discrete controls THIS device supports (data-driven UI +
29264
+ * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
29265
+ */
29266
+ listActions: method(object({ deviceId: number() }), array(NavigationActionEntrySchema)),
29267
+ /**
29268
+ * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
29269
+ * unsupported action ids are rejected by the provider.
29270
+ */
29271
+ runAction: method(object({
29272
+ deviceId: number(),
29273
+ actionId: NavigationActionIdSchema
29274
+ }), _void(), { kind: "mutation" }),
29275
+ /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
29276
+ playSound: method(object({
29277
+ deviceId: number(),
29278
+ soundId: number().int()
29279
+ }), _void(), { kind: "mutation" }),
29280
+ /**
29281
+ * Turn the on-camera fill light on / off (the `OpenFullLight` control —
29282
+ * works anytime, no active stream required).
29283
+ */
29284
+ setLightOn: method(object({
29285
+ deviceId: number(),
29286
+ on: boolean()
29287
+ }), _void(), { kind: "mutation" }),
29288
+ /**
29289
+ * Set the fill-light mode (auto vs manual). `manual` optionally carries the
29290
+ * initial `level`. The auto/manual + level control is a CAMERA-service
29291
+ * action that generally needs an active camera stream/monitor session — the
29292
+ * UI shows the manual level slider ONLY when `mode === 'manual'`.
29293
+ */
29294
+ setLightMode: method(object({
29295
+ deviceId: number(),
29296
+ mode: NavigationLightModeSchema,
29297
+ level: number().min(40).max(100).optional()
29298
+ }), _void(), { kind: "mutation" }),
29299
+ /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
29300
+ setLightLevel: method(object({
29301
+ deviceId: number(),
29302
+ level: number().min(40).max(100)
29303
+ }), _void(), { kind: "mutation" }),
29304
+ /**
29305
+ * Per-device FEATURE-FLAG report for the general primitives — drives which
29306
+ * controls the UI shows (the per-entry flags for the dictionary come back on
29307
+ * `listActions`).
29308
+ */
29309
+ getFeatures: method(object({ deviceId: number() }), NavigationFeaturesSchema)
29310
+ },
29311
+ events: { onStatusChanged: { data: object({
29312
+ deviceId: number(),
29313
+ status: NavigationStatusSchema
29314
+ }) } },
29315
+ status: {
29316
+ schema: NavigationStatusSchema,
29317
+ kind: "push"
29318
+ },
29319
+ /**
29320
+ * Runtime-state slice mirrored by the kernel. The navigation panel watches it
29321
+ * for live mode / follow / flash changes.
29322
+ */
29323
+ runtimeState: NavigationRuntimeStateSchema,
29324
+ /**
29325
+ * Runtime-state durability: **session** — like `vacuum-control`, a restored
29326
+ * `mode: cleaning` / `following: true` is a robot that is not actually doing
29327
+ * that. The live handle re-publishes on connect.
29328
+ *
29329
+ * See `RuntimeStateDurability`. Enforced by
29330
+ * `scripts/check-runtime-state-durability.ts`.
29331
+ */
29332
+ durability: "session"
29333
+ };
29047
29334
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
29048
29335
  kind: "mutation",
29049
29336
  auth: "admin"
@@ -32319,6 +32606,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
32319
32606
  motionTrigger: motionTriggerCapability,
32320
32607
  motionZones: motionZonesCapability,
32321
32608
  nativeObjectDetection: nativeObjectDetectionCapability,
32609
+ navigation: navigationCapability,
32322
32610
  networkLink: networkLinkCapability,
32323
32611
  notifier: notifierCapability,
32324
32612
  numericSensor: numericSensorCapability,
@@ -36173,6 +36461,66 @@ Object.freeze({
36173
36461
  addonId: null,
36174
36462
  access: "create"
36175
36463
  },
36464
+ "navigation.getFeatures": {
36465
+ capName: "navigation",
36466
+ capScope: "device",
36467
+ addonId: null,
36468
+ access: "view"
36469
+ },
36470
+ "navigation.goToPoint": {
36471
+ capName: "navigation",
36472
+ capScope: "device",
36473
+ addonId: null,
36474
+ access: "create"
36475
+ },
36476
+ "navigation.listActions": {
36477
+ capName: "navigation",
36478
+ capScope: "device",
36479
+ addonId: null,
36480
+ access: "view"
36481
+ },
36482
+ "navigation.move": {
36483
+ capName: "navigation",
36484
+ capScope: "device",
36485
+ addonId: null,
36486
+ access: "create"
36487
+ },
36488
+ "navigation.playSound": {
36489
+ capName: "navigation",
36490
+ capScope: "device",
36491
+ addonId: null,
36492
+ access: "create"
36493
+ },
36494
+ "navigation.runAction": {
36495
+ capName: "navigation",
36496
+ capScope: "device",
36497
+ addonId: null,
36498
+ access: "create"
36499
+ },
36500
+ "navigation.setLightLevel": {
36501
+ capName: "navigation",
36502
+ capScope: "device",
36503
+ addonId: null,
36504
+ access: "create"
36505
+ },
36506
+ "navigation.setLightMode": {
36507
+ capName: "navigation",
36508
+ capScope: "device",
36509
+ addonId: null,
36510
+ access: "create"
36511
+ },
36512
+ "navigation.setLightOn": {
36513
+ capName: "navigation",
36514
+ capScope: "device",
36515
+ addonId: null,
36516
+ access: "create"
36517
+ },
36518
+ "navigation.stop": {
36519
+ capName: "navigation",
36520
+ capScope: "device",
36521
+ addonId: null,
36522
+ access: "create"
36523
+ },
36176
36524
  "networkAccess.getEndpoint": {
36177
36525
  capName: "network-access",
36178
36526
  capScope: "system",
@@ -40268,6 +40616,56 @@ Object.freeze({
40268
40616
  form: "single",
40269
40617
  optional: false
40270
40618
  }],
40619
+ "navigation.getFeatures": [{
40620
+ name: "deviceId",
40621
+ form: "single",
40622
+ optional: false
40623
+ }],
40624
+ "navigation.goToPoint": [{
40625
+ name: "deviceId",
40626
+ form: "single",
40627
+ optional: false
40628
+ }],
40629
+ "navigation.listActions": [{
40630
+ name: "deviceId",
40631
+ form: "single",
40632
+ optional: false
40633
+ }],
40634
+ "navigation.move": [{
40635
+ name: "deviceId",
40636
+ form: "single",
40637
+ optional: false
40638
+ }],
40639
+ "navigation.playSound": [{
40640
+ name: "deviceId",
40641
+ form: "single",
40642
+ optional: false
40643
+ }],
40644
+ "navigation.runAction": [{
40645
+ name: "deviceId",
40646
+ form: "single",
40647
+ optional: false
40648
+ }],
40649
+ "navigation.setLightLevel": [{
40650
+ name: "deviceId",
40651
+ form: "single",
40652
+ optional: false
40653
+ }],
40654
+ "navigation.setLightMode": [{
40655
+ name: "deviceId",
40656
+ form: "single",
40657
+ optional: false
40658
+ }],
40659
+ "navigation.setLightOn": [{
40660
+ name: "deviceId",
40661
+ form: "single",
40662
+ optional: false
40663
+ }],
40664
+ "navigation.stop": [{
40665
+ name: "deviceId",
40666
+ form: "single",
40667
+ optional: false
40668
+ }],
40271
40669
  "networkQuality.getDeviceStats": [{
40272
40670
  name: "deviceId",
40273
40671
  form: "single",
package/dist/addon.mjs CHANGED
@@ -5418,6 +5418,12 @@ Object.fromEntries([
5418
5418
  icon: "move",
5419
5419
  order: 40
5420
5420
  },
5421
+ {
5422
+ id: "navigation",
5423
+ label: "Navigation",
5424
+ icon: "compass",
5425
+ order: 41
5426
+ },
5421
5427
  {
5422
5428
  id: "consumables",
5423
5429
  label: "Consumables",
@@ -29042,6 +29048,287 @@ var ptzAutotrackCapability = {
29042
29048
  */
29043
29049
  durability: "session"
29044
29050
  };
29051
+ /**
29052
+ * `navigation` — a device-scoped capability that natively expresses the FULL
29053
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
29054
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
29055
+ *
29056
+ * Why a NEW cap rather than overloading `ptz`:
29057
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
29058
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
29059
+ * The two are different physical models: PTZ is absolute-position + presets,
29060
+ * navigation is momentary drive nudges + discrete robot ACTIONS
29061
+ * (dock / spot-clean / follow-pet / go-to-point / …).
29062
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
29063
+ * the reverse:
29064
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
29065
+ * / `getOptions`), and
29066
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
29067
+ * robot camera shows up in the existing PTZ control path without every
29068
+ * PTZ provider learning about robots. The mapping lives in the adapter,
29069
+ * not here (see the addon design note):
29070
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
29071
+ * ptz.stop() → navigation.stop()
29072
+ * ptz.goHome() → navigation.runAction('goHome')
29073
+ * ptz.getPresets() → navigation.listActions() (id→preset)
29074
+ * ptz.goToPreset(id) → navigation.runAction(id)
29075
+ *
29076
+ * ## Continuous drive
29077
+ *
29078
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
29079
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
29080
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
29081
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
29082
+ * coalesce them. The UI owns the cadence.
29083
+ *
29084
+ * ## The action dictionary
29085
+ *
29086
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
29087
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
29088
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
29089
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
29090
+ * vendor-specific list. `kind: 'action'` entries are triggered with
29091
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
29092
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
29093
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
29094
+ *
29095
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
29096
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
29097
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
29098
+ * every device handle. A future nodedreame publish adds a typed
29099
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
29100
+ * provider can then swap the raw calls for the typed methods with no change to
29101
+ * THIS contract.
29102
+ */
29103
+ /**
29104
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
29105
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
29106
+ * halts it.
29107
+ *
29108
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
29109
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
29110
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
29111
+ * vector by it (drivers without proportional drive ignore it).
29112
+ *
29113
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
29114
+ * axis alone; an all-undefined nudge is a no-op.
29115
+ */
29116
+ var NavigationMoveCommandSchema = object({
29117
+ pan: number().min(-1).max(1).optional(),
29118
+ tilt: number().min(-1).max(1).optional(),
29119
+ speed: number().min(0).max(1).optional()
29120
+ });
29121
+ /**
29122
+ * The enumerated discrete actions a navigation-capable robot can perform via
29123
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
29124
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
29125
+ * `playSound` (see the `sound` dictionary entries).
29126
+ */
29127
+ var NavigationActionIdSchema = _enum([
29128
+ "goHome",
29129
+ "locate",
29130
+ "spotClean",
29131
+ "findPet",
29132
+ "personFollow",
29133
+ "stop",
29134
+ "startClean",
29135
+ "pauseClean",
29136
+ "dockWash",
29137
+ "autoEmpty",
29138
+ "flashOn",
29139
+ "flashOff"
29140
+ ]);
29141
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
29142
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
29143
+ /**
29144
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
29145
+ * native panel and the PTZ mimic render as a button.
29146
+ *
29147
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
29148
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
29149
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
29150
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
29151
+ * - `label` — operator-facing English label.
29152
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
29153
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
29154
+ * PTZ render ONLY enabled entries. Data-driven: the provider
29155
+ * flips it from config, never by editing code.
29156
+ */
29157
+ var NavigationActionEntrySchema = object({
29158
+ id: string$2(),
29159
+ kind: NavigationEntryKindSchema,
29160
+ label: string$2(),
29161
+ icon: string$2(),
29162
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
29163
+ soundId: number().int().optional(),
29164
+ /** Per-device feature flag — render this entry only when true. */
29165
+ enabled: boolean()
29166
+ });
29167
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
29168
+ var NavigationPointSchema = object({
29169
+ x: number(),
29170
+ y: number()
29171
+ });
29172
+ /**
29173
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
29174
+ * The cap reports which are enabled so the UI / PTZ render only the controls
29175
+ * that are turned on for THIS device. Data-driven: the provider derives these
29176
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
29177
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
29178
+ * that are not dictionary entries.
29179
+ *
29180
+ * - `move` / `stop` — the momentary drive joystick.
29181
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
29182
+ * map-coordinate plumbing is wired.
29183
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
29184
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
29185
+ * - `light` — the on/off fill-light toggle (works anytime).
29186
+ * - `lightMode` — the auto/manual selector + manual level slider (a
29187
+ * camera-service control; needs an active stream).
29188
+ */
29189
+ var NavigationFeaturesSchema = object({
29190
+ move: boolean(),
29191
+ stop: boolean(),
29192
+ goToPoint: boolean(),
29193
+ runAction: boolean(),
29194
+ playSound: boolean(),
29195
+ light: boolean(),
29196
+ lightMode: boolean()
29197
+ });
29198
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
29199
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
29200
+ /**
29201
+ * Live navigation state so the UI can reflect what the robot is doing:
29202
+ * - `mode` — coarse activity (idle / cleaning / following / …).
29203
+ * - `following` — person/pet follow is currently armed.
29204
+ * - `flash` — the on-camera fill light is on.
29205
+ * - `lightMode` — auto vs manual fill-light mode.
29206
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
29207
+ * `lightMode === 'manual'`.
29208
+ */
29209
+ var NavigationStatusSchema = object({
29210
+ mode: _enum([
29211
+ "idle",
29212
+ "cleaning",
29213
+ "spot",
29214
+ "following",
29215
+ "goto",
29216
+ "returning",
29217
+ "paused",
29218
+ "unknown"
29219
+ ]),
29220
+ following: boolean(),
29221
+ flash: boolean(),
29222
+ lightMode: NavigationLightModeSchema,
29223
+ lightLevel: number().min(40).max(100),
29224
+ /** Ms epoch when the slice was last updated. */
29225
+ lastChangedAt: number()
29226
+ });
29227
+ /**
29228
+ * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
29229
+ * observable). Adds `lastFetchedAt` on top of the status shape per the
29230
+ * convention.
29231
+ */
29232
+ var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: number() });
29233
+ var navigationCapability = {
29234
+ name: "navigation",
29235
+ scope: "device",
29236
+ deviceNative: true,
29237
+ mode: "singleton",
29238
+ deviceTypes: [DeviceType.Camera],
29239
+ deviceConfig: { ui: {
29240
+ kind: "widget",
29241
+ widgetId: "host/navigation-panel",
29242
+ tab: "navigation",
29243
+ topTab: true,
29244
+ label: "Navigation",
29245
+ order: 0
29246
+ } },
29247
+ methods: {
29248
+ /**
29249
+ * Momentary drive nudge (the robot moves). `protected` — mirrors
29250
+ * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
29251
+ * path) works for any authenticated user, not admin-only. The UI sends
29252
+ * these at ~1 Hz while a control is held; the provider forwards each one to
29253
+ * a single drive write WITHOUT debouncing.
29254
+ */
29255
+ move: method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29256
+ /** Halt all motion immediately (zero drive vector). */
29257
+ stop: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
29258
+ /** Send the robot to a point on its live map. */
29259
+ goToPoint: method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29260
+ /**
29261
+ * Enumerate the discrete controls THIS device supports (data-driven UI +
29262
+ * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
29263
+ */
29264
+ listActions: method(object({ deviceId: number() }), array(NavigationActionEntrySchema)),
29265
+ /**
29266
+ * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
29267
+ * unsupported action ids are rejected by the provider.
29268
+ */
29269
+ runAction: method(object({
29270
+ deviceId: number(),
29271
+ actionId: NavigationActionIdSchema
29272
+ }), _void(), { kind: "mutation" }),
29273
+ /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
29274
+ playSound: method(object({
29275
+ deviceId: number(),
29276
+ soundId: number().int()
29277
+ }), _void(), { kind: "mutation" }),
29278
+ /**
29279
+ * Turn the on-camera fill light on / off (the `OpenFullLight` control —
29280
+ * works anytime, no active stream required).
29281
+ */
29282
+ setLightOn: method(object({
29283
+ deviceId: number(),
29284
+ on: boolean()
29285
+ }), _void(), { kind: "mutation" }),
29286
+ /**
29287
+ * Set the fill-light mode (auto vs manual). `manual` optionally carries the
29288
+ * initial `level`. The auto/manual + level control is a CAMERA-service
29289
+ * action that generally needs an active camera stream/monitor session — the
29290
+ * UI shows the manual level slider ONLY when `mode === 'manual'`.
29291
+ */
29292
+ setLightMode: method(object({
29293
+ deviceId: number(),
29294
+ mode: NavigationLightModeSchema,
29295
+ level: number().min(40).max(100).optional()
29296
+ }), _void(), { kind: "mutation" }),
29297
+ /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
29298
+ setLightLevel: method(object({
29299
+ deviceId: number(),
29300
+ level: number().min(40).max(100)
29301
+ }), _void(), { kind: "mutation" }),
29302
+ /**
29303
+ * Per-device FEATURE-FLAG report for the general primitives — drives which
29304
+ * controls the UI shows (the per-entry flags for the dictionary come back on
29305
+ * `listActions`).
29306
+ */
29307
+ getFeatures: method(object({ deviceId: number() }), NavigationFeaturesSchema)
29308
+ },
29309
+ events: { onStatusChanged: { data: object({
29310
+ deviceId: number(),
29311
+ status: NavigationStatusSchema
29312
+ }) } },
29313
+ status: {
29314
+ schema: NavigationStatusSchema,
29315
+ kind: "push"
29316
+ },
29317
+ /**
29318
+ * Runtime-state slice mirrored by the kernel. The navigation panel watches it
29319
+ * for live mode / follow / flash changes.
29320
+ */
29321
+ runtimeState: NavigationRuntimeStateSchema,
29322
+ /**
29323
+ * Runtime-state durability: **session** — like `vacuum-control`, a restored
29324
+ * `mode: cleaning` / `following: true` is a robot that is not actually doing
29325
+ * that. The live handle re-publishes on connect.
29326
+ *
29327
+ * See `RuntimeStateDurability`. Enforced by
29328
+ * `scripts/check-runtime-state-durability.ts`.
29329
+ */
29330
+ durability: "session"
29331
+ };
29045
29332
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
29046
29333
  kind: "mutation",
29047
29334
  auth: "admin"
@@ -32317,6 +32604,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
32317
32604
  motionTrigger: motionTriggerCapability,
32318
32605
  motionZones: motionZonesCapability,
32319
32606
  nativeObjectDetection: nativeObjectDetectionCapability,
32607
+ navigation: navigationCapability,
32320
32608
  networkLink: networkLinkCapability,
32321
32609
  notifier: notifierCapability,
32322
32610
  numericSensor: numericSensorCapability,
@@ -36171,6 +36459,66 @@ Object.freeze({
36171
36459
  addonId: null,
36172
36460
  access: "create"
36173
36461
  },
36462
+ "navigation.getFeatures": {
36463
+ capName: "navigation",
36464
+ capScope: "device",
36465
+ addonId: null,
36466
+ access: "view"
36467
+ },
36468
+ "navigation.goToPoint": {
36469
+ capName: "navigation",
36470
+ capScope: "device",
36471
+ addonId: null,
36472
+ access: "create"
36473
+ },
36474
+ "navigation.listActions": {
36475
+ capName: "navigation",
36476
+ capScope: "device",
36477
+ addonId: null,
36478
+ access: "view"
36479
+ },
36480
+ "navigation.move": {
36481
+ capName: "navigation",
36482
+ capScope: "device",
36483
+ addonId: null,
36484
+ access: "create"
36485
+ },
36486
+ "navigation.playSound": {
36487
+ capName: "navigation",
36488
+ capScope: "device",
36489
+ addonId: null,
36490
+ access: "create"
36491
+ },
36492
+ "navigation.runAction": {
36493
+ capName: "navigation",
36494
+ capScope: "device",
36495
+ addonId: null,
36496
+ access: "create"
36497
+ },
36498
+ "navigation.setLightLevel": {
36499
+ capName: "navigation",
36500
+ capScope: "device",
36501
+ addonId: null,
36502
+ access: "create"
36503
+ },
36504
+ "navigation.setLightMode": {
36505
+ capName: "navigation",
36506
+ capScope: "device",
36507
+ addonId: null,
36508
+ access: "create"
36509
+ },
36510
+ "navigation.setLightOn": {
36511
+ capName: "navigation",
36512
+ capScope: "device",
36513
+ addonId: null,
36514
+ access: "create"
36515
+ },
36516
+ "navigation.stop": {
36517
+ capName: "navigation",
36518
+ capScope: "device",
36519
+ addonId: null,
36520
+ access: "create"
36521
+ },
36174
36522
  "networkAccess.getEndpoint": {
36175
36523
  capName: "network-access",
36176
36524
  capScope: "system",
@@ -40266,6 +40614,56 @@ Object.freeze({
40266
40614
  form: "single",
40267
40615
  optional: false
40268
40616
  }],
40617
+ "navigation.getFeatures": [{
40618
+ name: "deviceId",
40619
+ form: "single",
40620
+ optional: false
40621
+ }],
40622
+ "navigation.goToPoint": [{
40623
+ name: "deviceId",
40624
+ form: "single",
40625
+ optional: false
40626
+ }],
40627
+ "navigation.listActions": [{
40628
+ name: "deviceId",
40629
+ form: "single",
40630
+ optional: false
40631
+ }],
40632
+ "navigation.move": [{
40633
+ name: "deviceId",
40634
+ form: "single",
40635
+ optional: false
40636
+ }],
40637
+ "navigation.playSound": [{
40638
+ name: "deviceId",
40639
+ form: "single",
40640
+ optional: false
40641
+ }],
40642
+ "navigation.runAction": [{
40643
+ name: "deviceId",
40644
+ form: "single",
40645
+ optional: false
40646
+ }],
40647
+ "navigation.setLightLevel": [{
40648
+ name: "deviceId",
40649
+ form: "single",
40650
+ optional: false
40651
+ }],
40652
+ "navigation.setLightMode": [{
40653
+ name: "deviceId",
40654
+ form: "single",
40655
+ optional: false
40656
+ }],
40657
+ "navigation.setLightOn": [{
40658
+ name: "deviceId",
40659
+ form: "single",
40660
+ optional: false
40661
+ }],
40662
+ "navigation.stop": [{
40663
+ name: "deviceId",
40664
+ form: "single",
40665
+ optional: false
40666
+ }],
40269
40667
  "networkQuality.getDeviceStats": [{
40270
40668
  name: "deviceId",
40271
40669
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-matter-broker",
3
- "version": "0.2.69",
3
+ "version": "0.2.71",
4
4
  "description": "Matter broker addon for CamStack — owns a Matter fabric (commissioning + the long-lived controller) via the matter.js controller and brokers commissioned Matter nodes into CamStack",
5
5
  "keywords": [
6
6
  "camstack",