@camstack/addon-provider-homematic 1.2.71 → 1.2.73

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