@camstack/addon-export-google 0.1.33 → 0.1.35

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.
@@ -77,7 +77,7 @@ function createApiDeviceGateway(deps) {
77
77
  };
78
78
  }
79
79
  //#endregion
80
- //#region ../types/dist/event-category-BZL-fdNj.mjs
80
+ //#region ../types/dist/event-category-zAv7pMUz.mjs
81
81
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
82
82
  EventCategory["SystemBoot"] = "system.boot";
83
83
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -449,6 +449,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
449
449
  */
450
450
  EventCategory["BatteryOnStatusChanged"] = "battery.onStatusChanged";
451
451
  /**
452
+ * Cap event fired by every device that registers the `network-link`
453
+ * capability. Mirrors the cap definition's `onStatusChanged`. Carries
454
+ * `{ deviceId, status: NetworkLinkStatus }` — a link switch or a signal
455
+ * reading that moved.
456
+ */
457
+ EventCategory["NetworkLinkOnStatusChanged"] = "network-link.onStatusChanged";
458
+ /**
452
459
  * Emitted by the battery cap provider WHEN `wakeForStream` enters the
453
460
  * "wake in progress" window — between the Baichuan wake-up issue and
454
461
  * the camera's first dialed-back RTP packet. The stream-broker
@@ -23463,6 +23470,39 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, meth
23463
23470
  deviceId: number(),
23464
23471
  status: BatteryStatusSchema
23465
23472
  });
23473
+ /**
23474
+ * Network-link snapshot. Same shape for every provider (a Reolink wifi
23475
+ * camera, a Home Assistant device with a signal-strength sensor, a Tapo
23476
+ * plug): one slice under `device.runtimeState['network-link']`, one badge,
23477
+ * one Home Assistant projection.
23478
+ */
23479
+ var NetworkLinkStatusSchema = object({
23480
+ /** The link the device is on. `'unknown'` = not read yet, not "no link". */
23481
+ type: _enum([
23482
+ "wifi",
23483
+ "ethernet",
23484
+ "cellular",
23485
+ "unknown"
23486
+ ]),
23487
+ /**
23488
+ * Link quality, 0..100 inclusive, normalised by the provider from whatever
23489
+ * the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
23490
+ * KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
23491
+ * one whose reading has not landed must not be drawn at 0 %. Consumers
23492
+ * SKIP a null rather than coerce it.
23493
+ */
23494
+ signalPercent: number().min(0).max(100).nullable(),
23495
+ /** Raw received signal strength in dBm, when the firmware reports one. */
23496
+ rssiDbm: number().optional(),
23497
+ /** Network name of a wireless link, when the firmware reports it. */
23498
+ ssid: string().optional(),
23499
+ /** Ms epoch of the last observation. Lets consumers reason about freshness. */
23500
+ lastUpdated: number()
23501
+ });
23502
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
23503
+ deviceId: number(),
23504
+ status: NetworkLinkStatusSchema
23505
+ });
23466
23506
  object({
23467
23507
  on: boolean(),
23468
23508
  /** Ms epoch of the last transition. 0 if never observed. */
@@ -27069,6 +27109,203 @@ DeviceType.Camera, method(object({ deviceId: number() }), PtzAutotrackStatusSche
27069
27109
  deviceId: number(),
27070
27110
  status: PtzAutotrackStatusSchema
27071
27111
  });
27112
+ /**
27113
+ * `navigation` — a device-scoped capability that natively expresses the FULL
27114
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
27115
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
27116
+ *
27117
+ * Why a NEW cap rather than overloading `ptz`:
27118
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
27119
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
27120
+ * The two are different physical models: PTZ is absolute-position + presets,
27121
+ * navigation is momentary drive nudges + discrete robot ACTIONS
27122
+ * (dock / spot-clean / follow-pet / go-to-point / …).
27123
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
27124
+ * the reverse:
27125
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
27126
+ * / `getOptions`), and
27127
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
27128
+ * robot camera shows up in the existing PTZ control path without every
27129
+ * PTZ provider learning about robots. The mapping lives in the adapter,
27130
+ * not here (see the addon design note):
27131
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
27132
+ * ptz.stop() → navigation.stop()
27133
+ * ptz.goHome() → navigation.runAction('goHome')
27134
+ * ptz.getPresets() → navigation.listActions() (id→preset)
27135
+ * ptz.goToPreset(id) → navigation.runAction(id)
27136
+ *
27137
+ * ## Continuous drive
27138
+ *
27139
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
27140
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
27141
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
27142
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
27143
+ * coalesce them. The UI owns the cadence.
27144
+ *
27145
+ * ## The action dictionary
27146
+ *
27147
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
27148
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
27149
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
27150
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
27151
+ * vendor-specific list. `kind: 'action'` entries are triggered with
27152
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
27153
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
27154
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
27155
+ *
27156
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
27157
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
27158
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
27159
+ * every device handle. A future nodedreame publish adds a typed
27160
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
27161
+ * provider can then swap the raw calls for the typed methods with no change to
27162
+ * THIS contract.
27163
+ */
27164
+ /**
27165
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
27166
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
27167
+ * halts it.
27168
+ *
27169
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
27170
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
27171
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
27172
+ * vector by it (drivers without proportional drive ignore it).
27173
+ *
27174
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
27175
+ * axis alone; an all-undefined nudge is a no-op.
27176
+ */
27177
+ var NavigationMoveCommandSchema = object({
27178
+ pan: number().min(-1).max(1).optional(),
27179
+ tilt: number().min(-1).max(1).optional(),
27180
+ speed: number().min(0).max(1).optional()
27181
+ });
27182
+ /**
27183
+ * The enumerated discrete actions a navigation-capable robot can perform via
27184
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
27185
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
27186
+ * `playSound` (see the `sound` dictionary entries).
27187
+ */
27188
+ var NavigationActionIdSchema = _enum([
27189
+ "goHome",
27190
+ "locate",
27191
+ "spotClean",
27192
+ "findPet",
27193
+ "personFollow",
27194
+ "stop",
27195
+ "startClean",
27196
+ "pauseClean",
27197
+ "dockWash",
27198
+ "autoEmpty",
27199
+ "flashOn",
27200
+ "flashOff"
27201
+ ]);
27202
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
27203
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
27204
+ /**
27205
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
27206
+ * native panel and the PTZ mimic render as a button.
27207
+ *
27208
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
27209
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
27210
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
27211
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
27212
+ * - `label` — operator-facing English label.
27213
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
27214
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
27215
+ * PTZ render ONLY enabled entries. Data-driven: the provider
27216
+ * flips it from config, never by editing code.
27217
+ */
27218
+ var NavigationActionEntrySchema = object({
27219
+ id: string(),
27220
+ kind: NavigationEntryKindSchema,
27221
+ label: string(),
27222
+ icon: string(),
27223
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
27224
+ soundId: number().int().optional(),
27225
+ /** Per-device feature flag — render this entry only when true. */
27226
+ enabled: boolean()
27227
+ });
27228
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
27229
+ var NavigationPointSchema = object({
27230
+ x: number(),
27231
+ y: number()
27232
+ });
27233
+ /**
27234
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
27235
+ * The cap reports which are enabled so the UI / PTZ render only the controls
27236
+ * that are turned on for THIS device. Data-driven: the provider derives these
27237
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
27238
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
27239
+ * that are not dictionary entries.
27240
+ *
27241
+ * - `move` / `stop` — the momentary drive joystick.
27242
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
27243
+ * map-coordinate plumbing is wired.
27244
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
27245
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
27246
+ * - `light` — the on/off fill-light toggle (works anytime).
27247
+ * - `lightMode` — the auto/manual selector + manual level slider (a
27248
+ * camera-service control; needs an active stream).
27249
+ */
27250
+ var NavigationFeaturesSchema = object({
27251
+ move: boolean(),
27252
+ stop: boolean(),
27253
+ goToPoint: boolean(),
27254
+ runAction: boolean(),
27255
+ playSound: boolean(),
27256
+ light: boolean(),
27257
+ lightMode: boolean()
27258
+ });
27259
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
27260
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
27261
+ /**
27262
+ * Live navigation state so the UI can reflect what the robot is doing:
27263
+ * - `mode` — coarse activity (idle / cleaning / following / …).
27264
+ * - `following` — person/pet follow is currently armed.
27265
+ * - `flash` — the on-camera fill light is on.
27266
+ * - `lightMode` — auto vs manual fill-light mode.
27267
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
27268
+ * `lightMode === 'manual'`.
27269
+ */
27270
+ var NavigationStatusSchema = object({
27271
+ mode: _enum([
27272
+ "idle",
27273
+ "cleaning",
27274
+ "spot",
27275
+ "following",
27276
+ "goto",
27277
+ "returning",
27278
+ "paused",
27279
+ "unknown"
27280
+ ]),
27281
+ following: boolean(),
27282
+ flash: boolean(),
27283
+ lightMode: NavigationLightModeSchema,
27284
+ lightLevel: number().min(40).max(100),
27285
+ /** Ms epoch when the slice was last updated. */
27286
+ lastChangedAt: number()
27287
+ });
27288
+ NavigationStatusSchema.extend({ lastFetchedAt: number() });
27289
+ DeviceType.Camera, method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), _void(), { kind: "mutation" }), method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), array(NavigationActionEntrySchema)), method(object({
27290
+ deviceId: number(),
27291
+ actionId: NavigationActionIdSchema
27292
+ }), _void(), { kind: "mutation" }), method(object({
27293
+ deviceId: number(),
27294
+ soundId: number().int()
27295
+ }), _void(), { kind: "mutation" }), method(object({
27296
+ deviceId: number(),
27297
+ on: boolean()
27298
+ }), _void(), { kind: "mutation" }), method(object({
27299
+ deviceId: number(),
27300
+ mode: NavigationLightModeSchema,
27301
+ level: number().min(40).max(100).optional()
27302
+ }), _void(), { kind: "mutation" }), method(object({
27303
+ deviceId: number(),
27304
+ level: number().min(40).max(100)
27305
+ }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), NavigationFeaturesSchema), object({
27306
+ deviceId: number(),
27307
+ status: NavigationStatusSchema
27308
+ });
27072
27309
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
27073
27310
  kind: "mutation",
27074
27311
  auth: "admin"
@@ -32140,6 +32377,66 @@ Object.freeze({
32140
32377
  addonId: null,
32141
32378
  access: "create"
32142
32379
  },
32380
+ "navigation.getFeatures": {
32381
+ capName: "navigation",
32382
+ capScope: "device",
32383
+ addonId: null,
32384
+ access: "view"
32385
+ },
32386
+ "navigation.goToPoint": {
32387
+ capName: "navigation",
32388
+ capScope: "device",
32389
+ addonId: null,
32390
+ access: "create"
32391
+ },
32392
+ "navigation.listActions": {
32393
+ capName: "navigation",
32394
+ capScope: "device",
32395
+ addonId: null,
32396
+ access: "view"
32397
+ },
32398
+ "navigation.move": {
32399
+ capName: "navigation",
32400
+ capScope: "device",
32401
+ addonId: null,
32402
+ access: "create"
32403
+ },
32404
+ "navigation.playSound": {
32405
+ capName: "navigation",
32406
+ capScope: "device",
32407
+ addonId: null,
32408
+ access: "create"
32409
+ },
32410
+ "navigation.runAction": {
32411
+ capName: "navigation",
32412
+ capScope: "device",
32413
+ addonId: null,
32414
+ access: "create"
32415
+ },
32416
+ "navigation.setLightLevel": {
32417
+ capName: "navigation",
32418
+ capScope: "device",
32419
+ addonId: null,
32420
+ access: "create"
32421
+ },
32422
+ "navigation.setLightMode": {
32423
+ capName: "navigation",
32424
+ capScope: "device",
32425
+ addonId: null,
32426
+ access: "create"
32427
+ },
32428
+ "navigation.setLightOn": {
32429
+ capName: "navigation",
32430
+ capScope: "device",
32431
+ addonId: null,
32432
+ access: "create"
32433
+ },
32434
+ "navigation.stop": {
32435
+ capName: "navigation",
32436
+ capScope: "device",
32437
+ addonId: null,
32438
+ access: "create"
32439
+ },
32143
32440
  "networkAccess.getEndpoint": {
32144
32441
  capName: "network-access",
32145
32442
  capScope: "system",
@@ -36235,6 +36532,56 @@ Object.freeze({
36235
36532
  form: "single",
36236
36533
  optional: false
36237
36534
  }],
36535
+ "navigation.getFeatures": [{
36536
+ name: "deviceId",
36537
+ form: "single",
36538
+ optional: false
36539
+ }],
36540
+ "navigation.goToPoint": [{
36541
+ name: "deviceId",
36542
+ form: "single",
36543
+ optional: false
36544
+ }],
36545
+ "navigation.listActions": [{
36546
+ name: "deviceId",
36547
+ form: "single",
36548
+ optional: false
36549
+ }],
36550
+ "navigation.move": [{
36551
+ name: "deviceId",
36552
+ form: "single",
36553
+ optional: false
36554
+ }],
36555
+ "navigation.playSound": [{
36556
+ name: "deviceId",
36557
+ form: "single",
36558
+ optional: false
36559
+ }],
36560
+ "navigation.runAction": [{
36561
+ name: "deviceId",
36562
+ form: "single",
36563
+ optional: false
36564
+ }],
36565
+ "navigation.setLightLevel": [{
36566
+ name: "deviceId",
36567
+ form: "single",
36568
+ optional: false
36569
+ }],
36570
+ "navigation.setLightMode": [{
36571
+ name: "deviceId",
36572
+ form: "single",
36573
+ optional: false
36574
+ }],
36575
+ "navigation.setLightOn": [{
36576
+ name: "deviceId",
36577
+ form: "single",
36578
+ optional: false
36579
+ }],
36580
+ "navigation.stop": [{
36581
+ name: "deviceId",
36582
+ form: "single",
36583
+ optional: false
36584
+ }],
36238
36585
  "networkQuality.getDeviceStats": [{
36239
36586
  name: "deviceId",
36240
36587
  form: "single",
@@ -73,7 +73,7 @@ function createApiDeviceGateway(deps) {
73
73
  };
74
74
  }
75
75
  //#endregion
76
- //#region ../types/dist/event-category-BZL-fdNj.mjs
76
+ //#region ../types/dist/event-category-zAv7pMUz.mjs
77
77
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
78
78
  EventCategory["SystemBoot"] = "system.boot";
79
79
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -445,6 +445,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
445
445
  */
446
446
  EventCategory["BatteryOnStatusChanged"] = "battery.onStatusChanged";
447
447
  /**
448
+ * Cap event fired by every device that registers the `network-link`
449
+ * capability. Mirrors the cap definition's `onStatusChanged`. Carries
450
+ * `{ deviceId, status: NetworkLinkStatus }` — a link switch or a signal
451
+ * reading that moved.
452
+ */
453
+ EventCategory["NetworkLinkOnStatusChanged"] = "network-link.onStatusChanged";
454
+ /**
448
455
  * Emitted by the battery cap provider WHEN `wakeForStream` enters the
449
456
  * "wake in progress" window — between the Baichuan wake-up issue and
450
457
  * the camera's first dialed-back RTP packet. The stream-broker
@@ -23459,6 +23466,39 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, meth
23459
23466
  deviceId: number(),
23460
23467
  status: BatteryStatusSchema
23461
23468
  });
23469
+ /**
23470
+ * Network-link snapshot. Same shape for every provider (a Reolink wifi
23471
+ * camera, a Home Assistant device with a signal-strength sensor, a Tapo
23472
+ * plug): one slice under `device.runtimeState['network-link']`, one badge,
23473
+ * one Home Assistant projection.
23474
+ */
23475
+ var NetworkLinkStatusSchema = object({
23476
+ /** The link the device is on. `'unknown'` = not read yet, not "no link". */
23477
+ type: _enum([
23478
+ "wifi",
23479
+ "ethernet",
23480
+ "cellular",
23481
+ "unknown"
23482
+ ]),
23483
+ /**
23484
+ * Link quality, 0..100 inclusive, normalised by the provider from whatever
23485
+ * the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
23486
+ * KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
23487
+ * one whose reading has not landed must not be drawn at 0 %. Consumers
23488
+ * SKIP a null rather than coerce it.
23489
+ */
23490
+ signalPercent: number().min(0).max(100).nullable(),
23491
+ /** Raw received signal strength in dBm, when the firmware reports one. */
23492
+ rssiDbm: number().optional(),
23493
+ /** Network name of a wireless link, when the firmware reports it. */
23494
+ ssid: string().optional(),
23495
+ /** Ms epoch of the last observation. Lets consumers reason about freshness. */
23496
+ lastUpdated: number()
23497
+ });
23498
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
23499
+ deviceId: number(),
23500
+ status: NetworkLinkStatusSchema
23501
+ });
23462
23502
  object({
23463
23503
  on: boolean(),
23464
23504
  /** Ms epoch of the last transition. 0 if never observed. */
@@ -27065,6 +27105,203 @@ DeviceType.Camera, method(object({ deviceId: number() }), PtzAutotrackStatusSche
27065
27105
  deviceId: number(),
27066
27106
  status: PtzAutotrackStatusSchema
27067
27107
  });
27108
+ /**
27109
+ * `navigation` — a device-scoped capability that natively expresses the FULL
27110
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
27111
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
27112
+ *
27113
+ * Why a NEW cap rather than overloading `ptz`:
27114
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
27115
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
27116
+ * The two are different physical models: PTZ is absolute-position + presets,
27117
+ * navigation is momentary drive nudges + discrete robot ACTIONS
27118
+ * (dock / spot-clean / follow-pet / go-to-point / …).
27119
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
27120
+ * the reverse:
27121
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
27122
+ * / `getOptions`), and
27123
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
27124
+ * robot camera shows up in the existing PTZ control path without every
27125
+ * PTZ provider learning about robots. The mapping lives in the adapter,
27126
+ * not here (see the addon design note):
27127
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
27128
+ * ptz.stop() → navigation.stop()
27129
+ * ptz.goHome() → navigation.runAction('goHome')
27130
+ * ptz.getPresets() → navigation.listActions() (id→preset)
27131
+ * ptz.goToPreset(id) → navigation.runAction(id)
27132
+ *
27133
+ * ## Continuous drive
27134
+ *
27135
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
27136
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
27137
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
27138
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
27139
+ * coalesce them. The UI owns the cadence.
27140
+ *
27141
+ * ## The action dictionary
27142
+ *
27143
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
27144
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
27145
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
27146
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
27147
+ * vendor-specific list. `kind: 'action'` entries are triggered with
27148
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
27149
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
27150
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
27151
+ *
27152
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
27153
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
27154
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
27155
+ * every device handle. A future nodedreame publish adds a typed
27156
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
27157
+ * provider can then swap the raw calls for the typed methods with no change to
27158
+ * THIS contract.
27159
+ */
27160
+ /**
27161
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
27162
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
27163
+ * halts it.
27164
+ *
27165
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
27166
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
27167
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
27168
+ * vector by it (drivers without proportional drive ignore it).
27169
+ *
27170
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
27171
+ * axis alone; an all-undefined nudge is a no-op.
27172
+ */
27173
+ var NavigationMoveCommandSchema = object({
27174
+ pan: number().min(-1).max(1).optional(),
27175
+ tilt: number().min(-1).max(1).optional(),
27176
+ speed: number().min(0).max(1).optional()
27177
+ });
27178
+ /**
27179
+ * The enumerated discrete actions a navigation-capable robot can perform via
27180
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
27181
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
27182
+ * `playSound` (see the `sound` dictionary entries).
27183
+ */
27184
+ var NavigationActionIdSchema = _enum([
27185
+ "goHome",
27186
+ "locate",
27187
+ "spotClean",
27188
+ "findPet",
27189
+ "personFollow",
27190
+ "stop",
27191
+ "startClean",
27192
+ "pauseClean",
27193
+ "dockWash",
27194
+ "autoEmpty",
27195
+ "flashOn",
27196
+ "flashOff"
27197
+ ]);
27198
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
27199
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
27200
+ /**
27201
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
27202
+ * native panel and the PTZ mimic render as a button.
27203
+ *
27204
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
27205
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
27206
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
27207
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
27208
+ * - `label` — operator-facing English label.
27209
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
27210
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
27211
+ * PTZ render ONLY enabled entries. Data-driven: the provider
27212
+ * flips it from config, never by editing code.
27213
+ */
27214
+ var NavigationActionEntrySchema = object({
27215
+ id: string(),
27216
+ kind: NavigationEntryKindSchema,
27217
+ label: string(),
27218
+ icon: string(),
27219
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
27220
+ soundId: number().int().optional(),
27221
+ /** Per-device feature flag — render this entry only when true. */
27222
+ enabled: boolean()
27223
+ });
27224
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
27225
+ var NavigationPointSchema = object({
27226
+ x: number(),
27227
+ y: number()
27228
+ });
27229
+ /**
27230
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
27231
+ * The cap reports which are enabled so the UI / PTZ render only the controls
27232
+ * that are turned on for THIS device. Data-driven: the provider derives these
27233
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
27234
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
27235
+ * that are not dictionary entries.
27236
+ *
27237
+ * - `move` / `stop` — the momentary drive joystick.
27238
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
27239
+ * map-coordinate plumbing is wired.
27240
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
27241
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
27242
+ * - `light` — the on/off fill-light toggle (works anytime).
27243
+ * - `lightMode` — the auto/manual selector + manual level slider (a
27244
+ * camera-service control; needs an active stream).
27245
+ */
27246
+ var NavigationFeaturesSchema = object({
27247
+ move: boolean(),
27248
+ stop: boolean(),
27249
+ goToPoint: boolean(),
27250
+ runAction: boolean(),
27251
+ playSound: boolean(),
27252
+ light: boolean(),
27253
+ lightMode: boolean()
27254
+ });
27255
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
27256
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
27257
+ /**
27258
+ * Live navigation state so the UI can reflect what the robot is doing:
27259
+ * - `mode` — coarse activity (idle / cleaning / following / …).
27260
+ * - `following` — person/pet follow is currently armed.
27261
+ * - `flash` — the on-camera fill light is on.
27262
+ * - `lightMode` — auto vs manual fill-light mode.
27263
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
27264
+ * `lightMode === 'manual'`.
27265
+ */
27266
+ var NavigationStatusSchema = object({
27267
+ mode: _enum([
27268
+ "idle",
27269
+ "cleaning",
27270
+ "spot",
27271
+ "following",
27272
+ "goto",
27273
+ "returning",
27274
+ "paused",
27275
+ "unknown"
27276
+ ]),
27277
+ following: boolean(),
27278
+ flash: boolean(),
27279
+ lightMode: NavigationLightModeSchema,
27280
+ lightLevel: number().min(40).max(100),
27281
+ /** Ms epoch when the slice was last updated. */
27282
+ lastChangedAt: number()
27283
+ });
27284
+ NavigationStatusSchema.extend({ lastFetchedAt: number() });
27285
+ DeviceType.Camera, method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), _void(), { kind: "mutation" }), method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), array(NavigationActionEntrySchema)), method(object({
27286
+ deviceId: number(),
27287
+ actionId: NavigationActionIdSchema
27288
+ }), _void(), { kind: "mutation" }), method(object({
27289
+ deviceId: number(),
27290
+ soundId: number().int()
27291
+ }), _void(), { kind: "mutation" }), method(object({
27292
+ deviceId: number(),
27293
+ on: boolean()
27294
+ }), _void(), { kind: "mutation" }), method(object({
27295
+ deviceId: number(),
27296
+ mode: NavigationLightModeSchema,
27297
+ level: number().min(40).max(100).optional()
27298
+ }), _void(), { kind: "mutation" }), method(object({
27299
+ deviceId: number(),
27300
+ level: number().min(40).max(100)
27301
+ }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), NavigationFeaturesSchema), object({
27302
+ deviceId: number(),
27303
+ status: NavigationStatusSchema
27304
+ });
27068
27305
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
27069
27306
  kind: "mutation",
27070
27307
  auth: "admin"
@@ -32136,6 +32373,66 @@ Object.freeze({
32136
32373
  addonId: null,
32137
32374
  access: "create"
32138
32375
  },
32376
+ "navigation.getFeatures": {
32377
+ capName: "navigation",
32378
+ capScope: "device",
32379
+ addonId: null,
32380
+ access: "view"
32381
+ },
32382
+ "navigation.goToPoint": {
32383
+ capName: "navigation",
32384
+ capScope: "device",
32385
+ addonId: null,
32386
+ access: "create"
32387
+ },
32388
+ "navigation.listActions": {
32389
+ capName: "navigation",
32390
+ capScope: "device",
32391
+ addonId: null,
32392
+ access: "view"
32393
+ },
32394
+ "navigation.move": {
32395
+ capName: "navigation",
32396
+ capScope: "device",
32397
+ addonId: null,
32398
+ access: "create"
32399
+ },
32400
+ "navigation.playSound": {
32401
+ capName: "navigation",
32402
+ capScope: "device",
32403
+ addonId: null,
32404
+ access: "create"
32405
+ },
32406
+ "navigation.runAction": {
32407
+ capName: "navigation",
32408
+ capScope: "device",
32409
+ addonId: null,
32410
+ access: "create"
32411
+ },
32412
+ "navigation.setLightLevel": {
32413
+ capName: "navigation",
32414
+ capScope: "device",
32415
+ addonId: null,
32416
+ access: "create"
32417
+ },
32418
+ "navigation.setLightMode": {
32419
+ capName: "navigation",
32420
+ capScope: "device",
32421
+ addonId: null,
32422
+ access: "create"
32423
+ },
32424
+ "navigation.setLightOn": {
32425
+ capName: "navigation",
32426
+ capScope: "device",
32427
+ addonId: null,
32428
+ access: "create"
32429
+ },
32430
+ "navigation.stop": {
32431
+ capName: "navigation",
32432
+ capScope: "device",
32433
+ addonId: null,
32434
+ access: "create"
32435
+ },
32139
32436
  "networkAccess.getEndpoint": {
32140
32437
  capName: "network-access",
32141
32438
  capScope: "system",
@@ -36231,6 +36528,56 @@ Object.freeze({
36231
36528
  form: "single",
36232
36529
  optional: false
36233
36530
  }],
36531
+ "navigation.getFeatures": [{
36532
+ name: "deviceId",
36533
+ form: "single",
36534
+ optional: false
36535
+ }],
36536
+ "navigation.goToPoint": [{
36537
+ name: "deviceId",
36538
+ form: "single",
36539
+ optional: false
36540
+ }],
36541
+ "navigation.listActions": [{
36542
+ name: "deviceId",
36543
+ form: "single",
36544
+ optional: false
36545
+ }],
36546
+ "navigation.move": [{
36547
+ name: "deviceId",
36548
+ form: "single",
36549
+ optional: false
36550
+ }],
36551
+ "navigation.playSound": [{
36552
+ name: "deviceId",
36553
+ form: "single",
36554
+ optional: false
36555
+ }],
36556
+ "navigation.runAction": [{
36557
+ name: "deviceId",
36558
+ form: "single",
36559
+ optional: false
36560
+ }],
36561
+ "navigation.setLightLevel": [{
36562
+ name: "deviceId",
36563
+ form: "single",
36564
+ optional: false
36565
+ }],
36566
+ "navigation.setLightMode": [{
36567
+ name: "deviceId",
36568
+ form: "single",
36569
+ optional: false
36570
+ }],
36571
+ "navigation.setLightOn": [{
36572
+ name: "deviceId",
36573
+ form: "single",
36574
+ optional: false
36575
+ }],
36576
+ "navigation.stop": [{
36577
+ name: "deviceId",
36578
+ form: "single",
36579
+ optional: false
36580
+ }],
36234
36581
  "networkQuality.getDeviceStats": [{
36235
36582
  name: "deviceId",
36236
36583
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-export-google",
3
- "version": "0.1.33",
3
+ "version": "0.1.35",
4
4
  "description": "Google Home export — hub-side smart-home fulfillment (SYNC / QUERY / EXECUTE / DISCONNECT) for the non-camera fleet, served over the hub's own OAuth account link. No Google credential is stored, sent or required.",
5
5
  "keywords": [
6
6
  "camstack",