@camstack/addon-provider-rademacher 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
@@ -6391,6 +6391,12 @@ Object.fromEntries([
6391
6391
  icon: "move",
6392
6392
  order: 40
6393
6393
  },
6394
+ {
6395
+ id: "navigation",
6396
+ label: "Navigation",
6397
+ icon: "compass",
6398
+ order: 41
6399
+ },
6394
6400
  {
6395
6401
  id: "consumables",
6396
6402
  label: "Consumables",
@@ -29932,6 +29938,287 @@ var ptzAutotrackCapability = {
29932
29938
  */
29933
29939
  durability: "session"
29934
29940
  };
29941
+ /**
29942
+ * `navigation` — a device-scoped capability that natively expresses the FULL
29943
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
29944
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
29945
+ *
29946
+ * Why a NEW cap rather than overloading `ptz`:
29947
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
29948
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
29949
+ * The two are different physical models: PTZ is absolute-position + presets,
29950
+ * navigation is momentary drive nudges + discrete robot ACTIONS
29951
+ * (dock / spot-clean / follow-pet / go-to-point / …).
29952
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
29953
+ * the reverse:
29954
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
29955
+ * / `getOptions`), and
29956
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
29957
+ * robot camera shows up in the existing PTZ control path without every
29958
+ * PTZ provider learning about robots. The mapping lives in the adapter,
29959
+ * not here (see the addon design note):
29960
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
29961
+ * ptz.stop() → navigation.stop()
29962
+ * ptz.goHome() → navigation.runAction('goHome')
29963
+ * ptz.getPresets() → navigation.listActions() (id→preset)
29964
+ * ptz.goToPreset(id) → navigation.runAction(id)
29965
+ *
29966
+ * ## Continuous drive
29967
+ *
29968
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
29969
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
29970
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
29971
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
29972
+ * coalesce them. The UI owns the cadence.
29973
+ *
29974
+ * ## The action dictionary
29975
+ *
29976
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
29977
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
29978
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
29979
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
29980
+ * vendor-specific list. `kind: 'action'` entries are triggered with
29981
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
29982
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
29983
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
29984
+ *
29985
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
29986
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
29987
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
29988
+ * every device handle. A future nodedreame publish adds a typed
29989
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
29990
+ * provider can then swap the raw calls for the typed methods with no change to
29991
+ * THIS contract.
29992
+ */
29993
+ /**
29994
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
29995
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
29996
+ * halts it.
29997
+ *
29998
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
29999
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
30000
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
30001
+ * vector by it (drivers without proportional drive ignore it).
30002
+ *
30003
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
30004
+ * axis alone; an all-undefined nudge is a no-op.
30005
+ */
30006
+ var NavigationMoveCommandSchema = object({
30007
+ pan: number().min(-1).max(1).optional(),
30008
+ tilt: number().min(-1).max(1).optional(),
30009
+ speed: number().min(0).max(1).optional()
30010
+ });
30011
+ /**
30012
+ * The enumerated discrete actions a navigation-capable robot can perform via
30013
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
30014
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
30015
+ * `playSound` (see the `sound` dictionary entries).
30016
+ */
30017
+ var NavigationActionIdSchema = _enum([
30018
+ "goHome",
30019
+ "locate",
30020
+ "spotClean",
30021
+ "findPet",
30022
+ "personFollow",
30023
+ "stop",
30024
+ "startClean",
30025
+ "pauseClean",
30026
+ "dockWash",
30027
+ "autoEmpty",
30028
+ "flashOn",
30029
+ "flashOff"
30030
+ ]);
30031
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
30032
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
30033
+ /**
30034
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
30035
+ * native panel and the PTZ mimic render as a button.
30036
+ *
30037
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
30038
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
30039
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
30040
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
30041
+ * - `label` — operator-facing English label.
30042
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
30043
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
30044
+ * PTZ render ONLY enabled entries. Data-driven: the provider
30045
+ * flips it from config, never by editing code.
30046
+ */
30047
+ var NavigationActionEntrySchema = object({
30048
+ id: string(),
30049
+ kind: NavigationEntryKindSchema,
30050
+ label: string(),
30051
+ icon: string(),
30052
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
30053
+ soundId: number().int().optional(),
30054
+ /** Per-device feature flag — render this entry only when true. */
30055
+ enabled: boolean()
30056
+ });
30057
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
30058
+ var NavigationPointSchema = object({
30059
+ x: number(),
30060
+ y: number()
30061
+ });
30062
+ /**
30063
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
30064
+ * The cap reports which are enabled so the UI / PTZ render only the controls
30065
+ * that are turned on for THIS device. Data-driven: the provider derives these
30066
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
30067
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
30068
+ * that are not dictionary entries.
30069
+ *
30070
+ * - `move` / `stop` — the momentary drive joystick.
30071
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
30072
+ * map-coordinate plumbing is wired.
30073
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
30074
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
30075
+ * - `light` — the on/off fill-light toggle (works anytime).
30076
+ * - `lightMode` — the auto/manual selector + manual level slider (a
30077
+ * camera-service control; needs an active stream).
30078
+ */
30079
+ var NavigationFeaturesSchema = object({
30080
+ move: boolean(),
30081
+ stop: boolean(),
30082
+ goToPoint: boolean(),
30083
+ runAction: boolean(),
30084
+ playSound: boolean(),
30085
+ light: boolean(),
30086
+ lightMode: boolean()
30087
+ });
30088
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
30089
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
30090
+ /**
30091
+ * Live navigation state so the UI can reflect what the robot is doing:
30092
+ * - `mode` — coarse activity (idle / cleaning / following / …).
30093
+ * - `following` — person/pet follow is currently armed.
30094
+ * - `flash` — the on-camera fill light is on.
30095
+ * - `lightMode` — auto vs manual fill-light mode.
30096
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
30097
+ * `lightMode === 'manual'`.
30098
+ */
30099
+ var NavigationStatusSchema = object({
30100
+ mode: _enum([
30101
+ "idle",
30102
+ "cleaning",
30103
+ "spot",
30104
+ "following",
30105
+ "goto",
30106
+ "returning",
30107
+ "paused",
30108
+ "unknown"
30109
+ ]),
30110
+ following: boolean(),
30111
+ flash: boolean(),
30112
+ lightMode: NavigationLightModeSchema,
30113
+ lightLevel: number().min(40).max(100),
30114
+ /** Ms epoch when the slice was last updated. */
30115
+ lastChangedAt: number()
30116
+ });
30117
+ /**
30118
+ * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
30119
+ * observable). Adds `lastFetchedAt` on top of the status shape per the
30120
+ * convention.
30121
+ */
30122
+ var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: number() });
30123
+ var navigationCapability = {
30124
+ name: "navigation",
30125
+ scope: "device",
30126
+ deviceNative: true,
30127
+ mode: "singleton",
30128
+ deviceTypes: [DeviceType.Camera],
30129
+ deviceConfig: { ui: {
30130
+ kind: "widget",
30131
+ widgetId: "host/navigation-panel",
30132
+ tab: "navigation",
30133
+ topTab: true,
30134
+ label: "Navigation",
30135
+ order: 0
30136
+ } },
30137
+ methods: {
30138
+ /**
30139
+ * Momentary drive nudge (the robot moves). `protected` — mirrors
30140
+ * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
30141
+ * path) works for any authenticated user, not admin-only. The UI sends
30142
+ * these at ~1 Hz while a control is held; the provider forwards each one to
30143
+ * a single drive write WITHOUT debouncing.
30144
+ */
30145
+ move: method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
30146
+ /** Halt all motion immediately (zero drive vector). */
30147
+ stop: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
30148
+ /** Send the robot to a point on its live map. */
30149
+ goToPoint: method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
30150
+ /**
30151
+ * Enumerate the discrete controls THIS device supports (data-driven UI +
30152
+ * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
30153
+ */
30154
+ listActions: method(object({ deviceId: number() }), array(NavigationActionEntrySchema)),
30155
+ /**
30156
+ * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
30157
+ * unsupported action ids are rejected by the provider.
30158
+ */
30159
+ runAction: method(object({
30160
+ deviceId: number(),
30161
+ actionId: NavigationActionIdSchema
30162
+ }), _void(), { kind: "mutation" }),
30163
+ /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
30164
+ playSound: method(object({
30165
+ deviceId: number(),
30166
+ soundId: number().int()
30167
+ }), _void(), { kind: "mutation" }),
30168
+ /**
30169
+ * Turn the on-camera fill light on / off (the `OpenFullLight` control —
30170
+ * works anytime, no active stream required).
30171
+ */
30172
+ setLightOn: method(object({
30173
+ deviceId: number(),
30174
+ on: boolean()
30175
+ }), _void(), { kind: "mutation" }),
30176
+ /**
30177
+ * Set the fill-light mode (auto vs manual). `manual` optionally carries the
30178
+ * initial `level`. The auto/manual + level control is a CAMERA-service
30179
+ * action that generally needs an active camera stream/monitor session — the
30180
+ * UI shows the manual level slider ONLY when `mode === 'manual'`.
30181
+ */
30182
+ setLightMode: method(object({
30183
+ deviceId: number(),
30184
+ mode: NavigationLightModeSchema,
30185
+ level: number().min(40).max(100).optional()
30186
+ }), _void(), { kind: "mutation" }),
30187
+ /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
30188
+ setLightLevel: method(object({
30189
+ deviceId: number(),
30190
+ level: number().min(40).max(100)
30191
+ }), _void(), { kind: "mutation" }),
30192
+ /**
30193
+ * Per-device FEATURE-FLAG report for the general primitives — drives which
30194
+ * controls the UI shows (the per-entry flags for the dictionary come back on
30195
+ * `listActions`).
30196
+ */
30197
+ getFeatures: method(object({ deviceId: number() }), NavigationFeaturesSchema)
30198
+ },
30199
+ events: { onStatusChanged: { data: object({
30200
+ deviceId: number(),
30201
+ status: NavigationStatusSchema
30202
+ }) } },
30203
+ status: {
30204
+ schema: NavigationStatusSchema,
30205
+ kind: "push"
30206
+ },
30207
+ /**
30208
+ * Runtime-state slice mirrored by the kernel. The navigation panel watches it
30209
+ * for live mode / follow / flash changes.
30210
+ */
30211
+ runtimeState: NavigationRuntimeStateSchema,
30212
+ /**
30213
+ * Runtime-state durability: **session** — like `vacuum-control`, a restored
30214
+ * `mode: cleaning` / `following: true` is a robot that is not actually doing
30215
+ * that. The live handle re-publishes on connect.
30216
+ *
30217
+ * See `RuntimeStateDurability`. Enforced by
30218
+ * `scripts/check-runtime-state-durability.ts`.
30219
+ */
30220
+ durability: "session"
30221
+ };
29935
30222
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
29936
30223
  kind: "mutation",
29937
30224
  auth: "admin"
@@ -33207,6 +33494,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
33207
33494
  motionTrigger: motionTriggerCapability,
33208
33495
  motionZones: motionZonesCapability,
33209
33496
  nativeObjectDetection: nativeObjectDetectionCapability,
33497
+ navigation: navigationCapability,
33210
33498
  networkLink: networkLinkCapability,
33211
33499
  notifier: notifierCapability,
33212
33500
  numericSensor: numericSensorCapability,
@@ -37061,6 +37349,66 @@ Object.freeze({
37061
37349
  addonId: null,
37062
37350
  access: "create"
37063
37351
  },
37352
+ "navigation.getFeatures": {
37353
+ capName: "navigation",
37354
+ capScope: "device",
37355
+ addonId: null,
37356
+ access: "view"
37357
+ },
37358
+ "navigation.goToPoint": {
37359
+ capName: "navigation",
37360
+ capScope: "device",
37361
+ addonId: null,
37362
+ access: "create"
37363
+ },
37364
+ "navigation.listActions": {
37365
+ capName: "navigation",
37366
+ capScope: "device",
37367
+ addonId: null,
37368
+ access: "view"
37369
+ },
37370
+ "navigation.move": {
37371
+ capName: "navigation",
37372
+ capScope: "device",
37373
+ addonId: null,
37374
+ access: "create"
37375
+ },
37376
+ "navigation.playSound": {
37377
+ capName: "navigation",
37378
+ capScope: "device",
37379
+ addonId: null,
37380
+ access: "create"
37381
+ },
37382
+ "navigation.runAction": {
37383
+ capName: "navigation",
37384
+ capScope: "device",
37385
+ addonId: null,
37386
+ access: "create"
37387
+ },
37388
+ "navigation.setLightLevel": {
37389
+ capName: "navigation",
37390
+ capScope: "device",
37391
+ addonId: null,
37392
+ access: "create"
37393
+ },
37394
+ "navigation.setLightMode": {
37395
+ capName: "navigation",
37396
+ capScope: "device",
37397
+ addonId: null,
37398
+ access: "create"
37399
+ },
37400
+ "navigation.setLightOn": {
37401
+ capName: "navigation",
37402
+ capScope: "device",
37403
+ addonId: null,
37404
+ access: "create"
37405
+ },
37406
+ "navigation.stop": {
37407
+ capName: "navigation",
37408
+ capScope: "device",
37409
+ addonId: null,
37410
+ access: "create"
37411
+ },
37064
37412
  "networkAccess.getEndpoint": {
37065
37413
  capName: "network-access",
37066
37414
  capScope: "system",
@@ -41156,6 +41504,56 @@ Object.freeze({
41156
41504
  form: "single",
41157
41505
  optional: false
41158
41506
  }],
41507
+ "navigation.getFeatures": [{
41508
+ name: "deviceId",
41509
+ form: "single",
41510
+ optional: false
41511
+ }],
41512
+ "navigation.goToPoint": [{
41513
+ name: "deviceId",
41514
+ form: "single",
41515
+ optional: false
41516
+ }],
41517
+ "navigation.listActions": [{
41518
+ name: "deviceId",
41519
+ form: "single",
41520
+ optional: false
41521
+ }],
41522
+ "navigation.move": [{
41523
+ name: "deviceId",
41524
+ form: "single",
41525
+ optional: false
41526
+ }],
41527
+ "navigation.playSound": [{
41528
+ name: "deviceId",
41529
+ form: "single",
41530
+ optional: false
41531
+ }],
41532
+ "navigation.runAction": [{
41533
+ name: "deviceId",
41534
+ form: "single",
41535
+ optional: false
41536
+ }],
41537
+ "navigation.setLightLevel": [{
41538
+ name: "deviceId",
41539
+ form: "single",
41540
+ optional: false
41541
+ }],
41542
+ "navigation.setLightMode": [{
41543
+ name: "deviceId",
41544
+ form: "single",
41545
+ optional: false
41546
+ }],
41547
+ "navigation.setLightOn": [{
41548
+ name: "deviceId",
41549
+ form: "single",
41550
+ optional: false
41551
+ }],
41552
+ "navigation.stop": [{
41553
+ name: "deviceId",
41554
+ form: "single",
41555
+ optional: false
41556
+ }],
41159
41557
  "networkQuality.getDeviceStats": [{
41160
41558
  name: "deviceId",
41161
41559
  form: "single",
package/dist/addon.mjs CHANGED
@@ -6390,6 +6390,12 @@ Object.fromEntries([
6390
6390
  icon: "move",
6391
6391
  order: 40
6392
6392
  },
6393
+ {
6394
+ id: "navigation",
6395
+ label: "Navigation",
6396
+ icon: "compass",
6397
+ order: 41
6398
+ },
6393
6399
  {
6394
6400
  id: "consumables",
6395
6401
  label: "Consumables",
@@ -29931,6 +29937,287 @@ var ptzAutotrackCapability = {
29931
29937
  */
29932
29938
  durability: "session"
29933
29939
  };
29940
+ /**
29941
+ * `navigation` — a device-scoped capability that natively expresses the FULL
29942
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
29943
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
29944
+ *
29945
+ * Why a NEW cap rather than overloading `ptz`:
29946
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
29947
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
29948
+ * The two are different physical models: PTZ is absolute-position + presets,
29949
+ * navigation is momentary drive nudges + discrete robot ACTIONS
29950
+ * (dock / spot-clean / follow-pet / go-to-point / …).
29951
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
29952
+ * the reverse:
29953
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
29954
+ * / `getOptions`), and
29955
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
29956
+ * robot camera shows up in the existing PTZ control path without every
29957
+ * PTZ provider learning about robots. The mapping lives in the adapter,
29958
+ * not here (see the addon design note):
29959
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
29960
+ * ptz.stop() → navigation.stop()
29961
+ * ptz.goHome() → navigation.runAction('goHome')
29962
+ * ptz.getPresets() → navigation.listActions() (id→preset)
29963
+ * ptz.goToPreset(id) → navigation.runAction(id)
29964
+ *
29965
+ * ## Continuous drive
29966
+ *
29967
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
29968
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
29969
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
29970
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
29971
+ * coalesce them. The UI owns the cadence.
29972
+ *
29973
+ * ## The action dictionary
29974
+ *
29975
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
29976
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
29977
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
29978
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
29979
+ * vendor-specific list. `kind: 'action'` entries are triggered with
29980
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
29981
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
29982
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
29983
+ *
29984
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
29985
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
29986
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
29987
+ * every device handle. A future nodedreame publish adds a typed
29988
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
29989
+ * provider can then swap the raw calls for the typed methods with no change to
29990
+ * THIS contract.
29991
+ */
29992
+ /**
29993
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
29994
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
29995
+ * halts it.
29996
+ *
29997
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
29998
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
29999
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
30000
+ * vector by it (drivers without proportional drive ignore it).
30001
+ *
30002
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
30003
+ * axis alone; an all-undefined nudge is a no-op.
30004
+ */
30005
+ var NavigationMoveCommandSchema = object({
30006
+ pan: number().min(-1).max(1).optional(),
30007
+ tilt: number().min(-1).max(1).optional(),
30008
+ speed: number().min(0).max(1).optional()
30009
+ });
30010
+ /**
30011
+ * The enumerated discrete actions a navigation-capable robot can perform via
30012
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
30013
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
30014
+ * `playSound` (see the `sound` dictionary entries).
30015
+ */
30016
+ var NavigationActionIdSchema = _enum([
30017
+ "goHome",
30018
+ "locate",
30019
+ "spotClean",
30020
+ "findPet",
30021
+ "personFollow",
30022
+ "stop",
30023
+ "startClean",
30024
+ "pauseClean",
30025
+ "dockWash",
30026
+ "autoEmpty",
30027
+ "flashOn",
30028
+ "flashOff"
30029
+ ]);
30030
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
30031
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
30032
+ /**
30033
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
30034
+ * native panel and the PTZ mimic render as a button.
30035
+ *
30036
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
30037
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
30038
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
30039
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
30040
+ * - `label` — operator-facing English label.
30041
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
30042
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
30043
+ * PTZ render ONLY enabled entries. Data-driven: the provider
30044
+ * flips it from config, never by editing code.
30045
+ */
30046
+ var NavigationActionEntrySchema = object({
30047
+ id: string(),
30048
+ kind: NavigationEntryKindSchema,
30049
+ label: string(),
30050
+ icon: string(),
30051
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
30052
+ soundId: number().int().optional(),
30053
+ /** Per-device feature flag — render this entry only when true. */
30054
+ enabled: boolean()
30055
+ });
30056
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
30057
+ var NavigationPointSchema = object({
30058
+ x: number(),
30059
+ y: number()
30060
+ });
30061
+ /**
30062
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
30063
+ * The cap reports which are enabled so the UI / PTZ render only the controls
30064
+ * that are turned on for THIS device. Data-driven: the provider derives these
30065
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
30066
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
30067
+ * that are not dictionary entries.
30068
+ *
30069
+ * - `move` / `stop` — the momentary drive joystick.
30070
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
30071
+ * map-coordinate plumbing is wired.
30072
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
30073
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
30074
+ * - `light` — the on/off fill-light toggle (works anytime).
30075
+ * - `lightMode` — the auto/manual selector + manual level slider (a
30076
+ * camera-service control; needs an active stream).
30077
+ */
30078
+ var NavigationFeaturesSchema = object({
30079
+ move: boolean(),
30080
+ stop: boolean(),
30081
+ goToPoint: boolean(),
30082
+ runAction: boolean(),
30083
+ playSound: boolean(),
30084
+ light: boolean(),
30085
+ lightMode: boolean()
30086
+ });
30087
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
30088
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
30089
+ /**
30090
+ * Live navigation state so the UI can reflect what the robot is doing:
30091
+ * - `mode` — coarse activity (idle / cleaning / following / …).
30092
+ * - `following` — person/pet follow is currently armed.
30093
+ * - `flash` — the on-camera fill light is on.
30094
+ * - `lightMode` — auto vs manual fill-light mode.
30095
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
30096
+ * `lightMode === 'manual'`.
30097
+ */
30098
+ var NavigationStatusSchema = object({
30099
+ mode: _enum([
30100
+ "idle",
30101
+ "cleaning",
30102
+ "spot",
30103
+ "following",
30104
+ "goto",
30105
+ "returning",
30106
+ "paused",
30107
+ "unknown"
30108
+ ]),
30109
+ following: boolean(),
30110
+ flash: boolean(),
30111
+ lightMode: NavigationLightModeSchema,
30112
+ lightLevel: number().min(40).max(100),
30113
+ /** Ms epoch when the slice was last updated. */
30114
+ lastChangedAt: number()
30115
+ });
30116
+ /**
30117
+ * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
30118
+ * observable). Adds `lastFetchedAt` on top of the status shape per the
30119
+ * convention.
30120
+ */
30121
+ var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: number() });
30122
+ var navigationCapability = {
30123
+ name: "navigation",
30124
+ scope: "device",
30125
+ deviceNative: true,
30126
+ mode: "singleton",
30127
+ deviceTypes: [DeviceType.Camera],
30128
+ deviceConfig: { ui: {
30129
+ kind: "widget",
30130
+ widgetId: "host/navigation-panel",
30131
+ tab: "navigation",
30132
+ topTab: true,
30133
+ label: "Navigation",
30134
+ order: 0
30135
+ } },
30136
+ methods: {
30137
+ /**
30138
+ * Momentary drive nudge (the robot moves). `protected` — mirrors
30139
+ * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
30140
+ * path) works for any authenticated user, not admin-only. The UI sends
30141
+ * these at ~1 Hz while a control is held; the provider forwards each one to
30142
+ * a single drive write WITHOUT debouncing.
30143
+ */
30144
+ move: method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
30145
+ /** Halt all motion immediately (zero drive vector). */
30146
+ stop: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
30147
+ /** Send the robot to a point on its live map. */
30148
+ goToPoint: method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
30149
+ /**
30150
+ * Enumerate the discrete controls THIS device supports (data-driven UI +
30151
+ * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
30152
+ */
30153
+ listActions: method(object({ deviceId: number() }), array(NavigationActionEntrySchema)),
30154
+ /**
30155
+ * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
30156
+ * unsupported action ids are rejected by the provider.
30157
+ */
30158
+ runAction: method(object({
30159
+ deviceId: number(),
30160
+ actionId: NavigationActionIdSchema
30161
+ }), _void(), { kind: "mutation" }),
30162
+ /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
30163
+ playSound: method(object({
30164
+ deviceId: number(),
30165
+ soundId: number().int()
30166
+ }), _void(), { kind: "mutation" }),
30167
+ /**
30168
+ * Turn the on-camera fill light on / off (the `OpenFullLight` control —
30169
+ * works anytime, no active stream required).
30170
+ */
30171
+ setLightOn: method(object({
30172
+ deviceId: number(),
30173
+ on: boolean()
30174
+ }), _void(), { kind: "mutation" }),
30175
+ /**
30176
+ * Set the fill-light mode (auto vs manual). `manual` optionally carries the
30177
+ * initial `level`. The auto/manual + level control is a CAMERA-service
30178
+ * action that generally needs an active camera stream/monitor session — the
30179
+ * UI shows the manual level slider ONLY when `mode === 'manual'`.
30180
+ */
30181
+ setLightMode: method(object({
30182
+ deviceId: number(),
30183
+ mode: NavigationLightModeSchema,
30184
+ level: number().min(40).max(100).optional()
30185
+ }), _void(), { kind: "mutation" }),
30186
+ /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
30187
+ setLightLevel: method(object({
30188
+ deviceId: number(),
30189
+ level: number().min(40).max(100)
30190
+ }), _void(), { kind: "mutation" }),
30191
+ /**
30192
+ * Per-device FEATURE-FLAG report for the general primitives — drives which
30193
+ * controls the UI shows (the per-entry flags for the dictionary come back on
30194
+ * `listActions`).
30195
+ */
30196
+ getFeatures: method(object({ deviceId: number() }), NavigationFeaturesSchema)
30197
+ },
30198
+ events: { onStatusChanged: { data: object({
30199
+ deviceId: number(),
30200
+ status: NavigationStatusSchema
30201
+ }) } },
30202
+ status: {
30203
+ schema: NavigationStatusSchema,
30204
+ kind: "push"
30205
+ },
30206
+ /**
30207
+ * Runtime-state slice mirrored by the kernel. The navigation panel watches it
30208
+ * for live mode / follow / flash changes.
30209
+ */
30210
+ runtimeState: NavigationRuntimeStateSchema,
30211
+ /**
30212
+ * Runtime-state durability: **session** — like `vacuum-control`, a restored
30213
+ * `mode: cleaning` / `following: true` is a robot that is not actually doing
30214
+ * that. The live handle re-publishes on connect.
30215
+ *
30216
+ * See `RuntimeStateDurability`. Enforced by
30217
+ * `scripts/check-runtime-state-durability.ts`.
30218
+ */
30219
+ durability: "session"
30220
+ };
29934
30221
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
29935
30222
  kind: "mutation",
29936
30223
  auth: "admin"
@@ -33206,6 +33493,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
33206
33493
  motionTrigger: motionTriggerCapability,
33207
33494
  motionZones: motionZonesCapability,
33208
33495
  nativeObjectDetection: nativeObjectDetectionCapability,
33496
+ navigation: navigationCapability,
33209
33497
  networkLink: networkLinkCapability,
33210
33498
  notifier: notifierCapability,
33211
33499
  numericSensor: numericSensorCapability,
@@ -37060,6 +37348,66 @@ Object.freeze({
37060
37348
  addonId: null,
37061
37349
  access: "create"
37062
37350
  },
37351
+ "navigation.getFeatures": {
37352
+ capName: "navigation",
37353
+ capScope: "device",
37354
+ addonId: null,
37355
+ access: "view"
37356
+ },
37357
+ "navigation.goToPoint": {
37358
+ capName: "navigation",
37359
+ capScope: "device",
37360
+ addonId: null,
37361
+ access: "create"
37362
+ },
37363
+ "navigation.listActions": {
37364
+ capName: "navigation",
37365
+ capScope: "device",
37366
+ addonId: null,
37367
+ access: "view"
37368
+ },
37369
+ "navigation.move": {
37370
+ capName: "navigation",
37371
+ capScope: "device",
37372
+ addonId: null,
37373
+ access: "create"
37374
+ },
37375
+ "navigation.playSound": {
37376
+ capName: "navigation",
37377
+ capScope: "device",
37378
+ addonId: null,
37379
+ access: "create"
37380
+ },
37381
+ "navigation.runAction": {
37382
+ capName: "navigation",
37383
+ capScope: "device",
37384
+ addonId: null,
37385
+ access: "create"
37386
+ },
37387
+ "navigation.setLightLevel": {
37388
+ capName: "navigation",
37389
+ capScope: "device",
37390
+ addonId: null,
37391
+ access: "create"
37392
+ },
37393
+ "navigation.setLightMode": {
37394
+ capName: "navigation",
37395
+ capScope: "device",
37396
+ addonId: null,
37397
+ access: "create"
37398
+ },
37399
+ "navigation.setLightOn": {
37400
+ capName: "navigation",
37401
+ capScope: "device",
37402
+ addonId: null,
37403
+ access: "create"
37404
+ },
37405
+ "navigation.stop": {
37406
+ capName: "navigation",
37407
+ capScope: "device",
37408
+ addonId: null,
37409
+ access: "create"
37410
+ },
37063
37411
  "networkAccess.getEndpoint": {
37064
37412
  capName: "network-access",
37065
37413
  capScope: "system",
@@ -41155,6 +41503,56 @@ Object.freeze({
41155
41503
  form: "single",
41156
41504
  optional: false
41157
41505
  }],
41506
+ "navigation.getFeatures": [{
41507
+ name: "deviceId",
41508
+ form: "single",
41509
+ optional: false
41510
+ }],
41511
+ "navigation.goToPoint": [{
41512
+ name: "deviceId",
41513
+ form: "single",
41514
+ optional: false
41515
+ }],
41516
+ "navigation.listActions": [{
41517
+ name: "deviceId",
41518
+ form: "single",
41519
+ optional: false
41520
+ }],
41521
+ "navigation.move": [{
41522
+ name: "deviceId",
41523
+ form: "single",
41524
+ optional: false
41525
+ }],
41526
+ "navigation.playSound": [{
41527
+ name: "deviceId",
41528
+ form: "single",
41529
+ optional: false
41530
+ }],
41531
+ "navigation.runAction": [{
41532
+ name: "deviceId",
41533
+ form: "single",
41534
+ optional: false
41535
+ }],
41536
+ "navigation.setLightLevel": [{
41537
+ name: "deviceId",
41538
+ form: "single",
41539
+ optional: false
41540
+ }],
41541
+ "navigation.setLightMode": [{
41542
+ name: "deviceId",
41543
+ form: "single",
41544
+ optional: false
41545
+ }],
41546
+ "navigation.setLightOn": [{
41547
+ name: "deviceId",
41548
+ form: "single",
41549
+ optional: false
41550
+ }],
41551
+ "navigation.stop": [{
41552
+ name: "deviceId",
41553
+ form: "single",
41554
+ optional: false
41555
+ }],
41158
41556
  "networkQuality.getDeviceStats": [{
41159
41557
  name: "deviceId",
41160
41558
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-rademacher",
3
- "version": "0.2.69",
3
+ "version": "0.2.71",
4
4
  "description": "Rademacher HomePilot device-provider addon for CamStack — wraps the @apocaliss92/noderademacher local-hub client (roller shutters over the cover cap)",
5
5
  "keywords": [
6
6
  "camstack",