@camstack/addon-provider-gree 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
@@ -5409,6 +5409,12 @@ Object.fromEntries([
5409
5409
  icon: "move",
5410
5410
  order: 40
5411
5411
  },
5412
+ {
5413
+ id: "navigation",
5414
+ label: "Navigation",
5415
+ icon: "compass",
5416
+ order: 41
5417
+ },
5412
5418
  {
5413
5419
  id: "consumables",
5414
5420
  label: "Consumables",
@@ -28950,6 +28956,287 @@ var ptzAutotrackCapability = {
28950
28956
  */
28951
28957
  durability: "session"
28952
28958
  };
28959
+ /**
28960
+ * `navigation` — a device-scoped capability that natively expresses the FULL
28961
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
28962
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
28963
+ *
28964
+ * Why a NEW cap rather than overloading `ptz`:
28965
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
28966
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
28967
+ * The two are different physical models: PTZ is absolute-position + presets,
28968
+ * navigation is momentary drive nudges + discrete robot ACTIONS
28969
+ * (dock / spot-clean / follow-pet / go-to-point / …).
28970
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
28971
+ * the reverse:
28972
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
28973
+ * / `getOptions`), and
28974
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
28975
+ * robot camera shows up in the existing PTZ control path without every
28976
+ * PTZ provider learning about robots. The mapping lives in the adapter,
28977
+ * not here (see the addon design note):
28978
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
28979
+ * ptz.stop() → navigation.stop()
28980
+ * ptz.goHome() → navigation.runAction('goHome')
28981
+ * ptz.getPresets() → navigation.listActions() (id→preset)
28982
+ * ptz.goToPreset(id) → navigation.runAction(id)
28983
+ *
28984
+ * ## Continuous drive
28985
+ *
28986
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
28987
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
28988
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
28989
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
28990
+ * coalesce them. The UI owns the cadence.
28991
+ *
28992
+ * ## The action dictionary
28993
+ *
28994
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
28995
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
28996
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
28997
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
28998
+ * vendor-specific list. `kind: 'action'` entries are triggered with
28999
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
29000
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
29001
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
29002
+ *
29003
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
29004
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
29005
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
29006
+ * every device handle. A future nodedreame publish adds a typed
29007
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
29008
+ * provider can then swap the raw calls for the typed methods with no change to
29009
+ * THIS contract.
29010
+ */
29011
+ /**
29012
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
29013
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
29014
+ * halts it.
29015
+ *
29016
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
29017
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
29018
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
29019
+ * vector by it (drivers without proportional drive ignore it).
29020
+ *
29021
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
29022
+ * axis alone; an all-undefined nudge is a no-op.
29023
+ */
29024
+ var NavigationMoveCommandSchema = object({
29025
+ pan: number().min(-1).max(1).optional(),
29026
+ tilt: number().min(-1).max(1).optional(),
29027
+ speed: number().min(0).max(1).optional()
29028
+ });
29029
+ /**
29030
+ * The enumerated discrete actions a navigation-capable robot can perform via
29031
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
29032
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
29033
+ * `playSound` (see the `sound` dictionary entries).
29034
+ */
29035
+ var NavigationActionIdSchema = _enum([
29036
+ "goHome",
29037
+ "locate",
29038
+ "spotClean",
29039
+ "findPet",
29040
+ "personFollow",
29041
+ "stop",
29042
+ "startClean",
29043
+ "pauseClean",
29044
+ "dockWash",
29045
+ "autoEmpty",
29046
+ "flashOn",
29047
+ "flashOff"
29048
+ ]);
29049
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
29050
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
29051
+ /**
29052
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
29053
+ * native panel and the PTZ mimic render as a button.
29054
+ *
29055
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
29056
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
29057
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
29058
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
29059
+ * - `label` — operator-facing English label.
29060
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
29061
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
29062
+ * PTZ render ONLY enabled entries. Data-driven: the provider
29063
+ * flips it from config, never by editing code.
29064
+ */
29065
+ var NavigationActionEntrySchema = object({
29066
+ id: string(),
29067
+ kind: NavigationEntryKindSchema,
29068
+ label: string(),
29069
+ icon: string(),
29070
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
29071
+ soundId: number().int().optional(),
29072
+ /** Per-device feature flag — render this entry only when true. */
29073
+ enabled: boolean()
29074
+ });
29075
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
29076
+ var NavigationPointSchema = object({
29077
+ x: number(),
29078
+ y: number()
29079
+ });
29080
+ /**
29081
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
29082
+ * The cap reports which are enabled so the UI / PTZ render only the controls
29083
+ * that are turned on for THIS device. Data-driven: the provider derives these
29084
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
29085
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
29086
+ * that are not dictionary entries.
29087
+ *
29088
+ * - `move` / `stop` — the momentary drive joystick.
29089
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
29090
+ * map-coordinate plumbing is wired.
29091
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
29092
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
29093
+ * - `light` — the on/off fill-light toggle (works anytime).
29094
+ * - `lightMode` — the auto/manual selector + manual level slider (a
29095
+ * camera-service control; needs an active stream).
29096
+ */
29097
+ var NavigationFeaturesSchema = object({
29098
+ move: boolean(),
29099
+ stop: boolean(),
29100
+ goToPoint: boolean(),
29101
+ runAction: boolean(),
29102
+ playSound: boolean(),
29103
+ light: boolean(),
29104
+ lightMode: boolean()
29105
+ });
29106
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
29107
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
29108
+ /**
29109
+ * Live navigation state so the UI can reflect what the robot is doing:
29110
+ * - `mode` — coarse activity (idle / cleaning / following / …).
29111
+ * - `following` — person/pet follow is currently armed.
29112
+ * - `flash` — the on-camera fill light is on.
29113
+ * - `lightMode` — auto vs manual fill-light mode.
29114
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
29115
+ * `lightMode === 'manual'`.
29116
+ */
29117
+ var NavigationStatusSchema = object({
29118
+ mode: _enum([
29119
+ "idle",
29120
+ "cleaning",
29121
+ "spot",
29122
+ "following",
29123
+ "goto",
29124
+ "returning",
29125
+ "paused",
29126
+ "unknown"
29127
+ ]),
29128
+ following: boolean(),
29129
+ flash: boolean(),
29130
+ lightMode: NavigationLightModeSchema,
29131
+ lightLevel: number().min(40).max(100),
29132
+ /** Ms epoch when the slice was last updated. */
29133
+ lastChangedAt: number()
29134
+ });
29135
+ /**
29136
+ * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
29137
+ * observable). Adds `lastFetchedAt` on top of the status shape per the
29138
+ * convention.
29139
+ */
29140
+ var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: number() });
29141
+ var navigationCapability = {
29142
+ name: "navigation",
29143
+ scope: "device",
29144
+ deviceNative: true,
29145
+ mode: "singleton",
29146
+ deviceTypes: [DeviceType.Camera],
29147
+ deviceConfig: { ui: {
29148
+ kind: "widget",
29149
+ widgetId: "host/navigation-panel",
29150
+ tab: "navigation",
29151
+ topTab: true,
29152
+ label: "Navigation",
29153
+ order: 0
29154
+ } },
29155
+ methods: {
29156
+ /**
29157
+ * Momentary drive nudge (the robot moves). `protected` — mirrors
29158
+ * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
29159
+ * path) works for any authenticated user, not admin-only. The UI sends
29160
+ * these at ~1 Hz while a control is held; the provider forwards each one to
29161
+ * a single drive write WITHOUT debouncing.
29162
+ */
29163
+ move: method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29164
+ /** Halt all motion immediately (zero drive vector). */
29165
+ stop: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
29166
+ /** Send the robot to a point on its live map. */
29167
+ goToPoint: method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29168
+ /**
29169
+ * Enumerate the discrete controls THIS device supports (data-driven UI +
29170
+ * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
29171
+ */
29172
+ listActions: method(object({ deviceId: number() }), array(NavigationActionEntrySchema)),
29173
+ /**
29174
+ * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
29175
+ * unsupported action ids are rejected by the provider.
29176
+ */
29177
+ runAction: method(object({
29178
+ deviceId: number(),
29179
+ actionId: NavigationActionIdSchema
29180
+ }), _void(), { kind: "mutation" }),
29181
+ /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
29182
+ playSound: method(object({
29183
+ deviceId: number(),
29184
+ soundId: number().int()
29185
+ }), _void(), { kind: "mutation" }),
29186
+ /**
29187
+ * Turn the on-camera fill light on / off (the `OpenFullLight` control —
29188
+ * works anytime, no active stream required).
29189
+ */
29190
+ setLightOn: method(object({
29191
+ deviceId: number(),
29192
+ on: boolean()
29193
+ }), _void(), { kind: "mutation" }),
29194
+ /**
29195
+ * Set the fill-light mode (auto vs manual). `manual` optionally carries the
29196
+ * initial `level`. The auto/manual + level control is a CAMERA-service
29197
+ * action that generally needs an active camera stream/monitor session — the
29198
+ * UI shows the manual level slider ONLY when `mode === 'manual'`.
29199
+ */
29200
+ setLightMode: method(object({
29201
+ deviceId: number(),
29202
+ mode: NavigationLightModeSchema,
29203
+ level: number().min(40).max(100).optional()
29204
+ }), _void(), { kind: "mutation" }),
29205
+ /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
29206
+ setLightLevel: method(object({
29207
+ deviceId: number(),
29208
+ level: number().min(40).max(100)
29209
+ }), _void(), { kind: "mutation" }),
29210
+ /**
29211
+ * Per-device FEATURE-FLAG report for the general primitives — drives which
29212
+ * controls the UI shows (the per-entry flags for the dictionary come back on
29213
+ * `listActions`).
29214
+ */
29215
+ getFeatures: method(object({ deviceId: number() }), NavigationFeaturesSchema)
29216
+ },
29217
+ events: { onStatusChanged: { data: object({
29218
+ deviceId: number(),
29219
+ status: NavigationStatusSchema
29220
+ }) } },
29221
+ status: {
29222
+ schema: NavigationStatusSchema,
29223
+ kind: "push"
29224
+ },
29225
+ /**
29226
+ * Runtime-state slice mirrored by the kernel. The navigation panel watches it
29227
+ * for live mode / follow / flash changes.
29228
+ */
29229
+ runtimeState: NavigationRuntimeStateSchema,
29230
+ /**
29231
+ * Runtime-state durability: **session** — like `vacuum-control`, a restored
29232
+ * `mode: cleaning` / `following: true` is a robot that is not actually doing
29233
+ * that. The live handle re-publishes on connect.
29234
+ *
29235
+ * See `RuntimeStateDurability`. Enforced by
29236
+ * `scripts/check-runtime-state-durability.ts`.
29237
+ */
29238
+ durability: "session"
29239
+ };
28953
29240
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
28954
29241
  kind: "mutation",
28955
29242
  auth: "admin"
@@ -32225,6 +32512,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
32225
32512
  motionTrigger: motionTriggerCapability,
32226
32513
  motionZones: motionZonesCapability,
32227
32514
  nativeObjectDetection: nativeObjectDetectionCapability,
32515
+ navigation: navigationCapability,
32228
32516
  networkLink: networkLinkCapability,
32229
32517
  notifier: notifierCapability,
32230
32518
  numericSensor: numericSensorCapability,
@@ -36079,6 +36367,66 @@ Object.freeze({
36079
36367
  addonId: null,
36080
36368
  access: "create"
36081
36369
  },
36370
+ "navigation.getFeatures": {
36371
+ capName: "navigation",
36372
+ capScope: "device",
36373
+ addonId: null,
36374
+ access: "view"
36375
+ },
36376
+ "navigation.goToPoint": {
36377
+ capName: "navigation",
36378
+ capScope: "device",
36379
+ addonId: null,
36380
+ access: "create"
36381
+ },
36382
+ "navigation.listActions": {
36383
+ capName: "navigation",
36384
+ capScope: "device",
36385
+ addonId: null,
36386
+ access: "view"
36387
+ },
36388
+ "navigation.move": {
36389
+ capName: "navigation",
36390
+ capScope: "device",
36391
+ addonId: null,
36392
+ access: "create"
36393
+ },
36394
+ "navigation.playSound": {
36395
+ capName: "navigation",
36396
+ capScope: "device",
36397
+ addonId: null,
36398
+ access: "create"
36399
+ },
36400
+ "navigation.runAction": {
36401
+ capName: "navigation",
36402
+ capScope: "device",
36403
+ addonId: null,
36404
+ access: "create"
36405
+ },
36406
+ "navigation.setLightLevel": {
36407
+ capName: "navigation",
36408
+ capScope: "device",
36409
+ addonId: null,
36410
+ access: "create"
36411
+ },
36412
+ "navigation.setLightMode": {
36413
+ capName: "navigation",
36414
+ capScope: "device",
36415
+ addonId: null,
36416
+ access: "create"
36417
+ },
36418
+ "navigation.setLightOn": {
36419
+ capName: "navigation",
36420
+ capScope: "device",
36421
+ addonId: null,
36422
+ access: "create"
36423
+ },
36424
+ "navigation.stop": {
36425
+ capName: "navigation",
36426
+ capScope: "device",
36427
+ addonId: null,
36428
+ access: "create"
36429
+ },
36082
36430
  "networkAccess.getEndpoint": {
36083
36431
  capName: "network-access",
36084
36432
  capScope: "system",
@@ -40174,6 +40522,56 @@ Object.freeze({
40174
40522
  form: "single",
40175
40523
  optional: false
40176
40524
  }],
40525
+ "navigation.getFeatures": [{
40526
+ name: "deviceId",
40527
+ form: "single",
40528
+ optional: false
40529
+ }],
40530
+ "navigation.goToPoint": [{
40531
+ name: "deviceId",
40532
+ form: "single",
40533
+ optional: false
40534
+ }],
40535
+ "navigation.listActions": [{
40536
+ name: "deviceId",
40537
+ form: "single",
40538
+ optional: false
40539
+ }],
40540
+ "navigation.move": [{
40541
+ name: "deviceId",
40542
+ form: "single",
40543
+ optional: false
40544
+ }],
40545
+ "navigation.playSound": [{
40546
+ name: "deviceId",
40547
+ form: "single",
40548
+ optional: false
40549
+ }],
40550
+ "navigation.runAction": [{
40551
+ name: "deviceId",
40552
+ form: "single",
40553
+ optional: false
40554
+ }],
40555
+ "navigation.setLightLevel": [{
40556
+ name: "deviceId",
40557
+ form: "single",
40558
+ optional: false
40559
+ }],
40560
+ "navigation.setLightMode": [{
40561
+ name: "deviceId",
40562
+ form: "single",
40563
+ optional: false
40564
+ }],
40565
+ "navigation.setLightOn": [{
40566
+ name: "deviceId",
40567
+ form: "single",
40568
+ optional: false
40569
+ }],
40570
+ "navigation.stop": [{
40571
+ name: "deviceId",
40572
+ form: "single",
40573
+ optional: false
40574
+ }],
40177
40575
  "networkQuality.getDeviceStats": [{
40178
40576
  name: "deviceId",
40179
40577
  form: "single",
package/dist/addon.mjs CHANGED
@@ -5408,6 +5408,12 @@ Object.fromEntries([
5408
5408
  icon: "move",
5409
5409
  order: 40
5410
5410
  },
5411
+ {
5412
+ id: "navigation",
5413
+ label: "Navigation",
5414
+ icon: "compass",
5415
+ order: 41
5416
+ },
5411
5417
  {
5412
5418
  id: "consumables",
5413
5419
  label: "Consumables",
@@ -28949,6 +28955,287 @@ var ptzAutotrackCapability = {
28949
28955
  */
28950
28956
  durability: "session"
28951
28957
  };
28958
+ /**
28959
+ * `navigation` — a device-scoped capability that natively expresses the FULL
28960
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
28961
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
28962
+ *
28963
+ * Why a NEW cap rather than overloading `ptz`:
28964
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
28965
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
28966
+ * The two are different physical models: PTZ is absolute-position + presets,
28967
+ * navigation is momentary drive nudges + discrete robot ACTIONS
28968
+ * (dock / spot-clean / follow-pet / go-to-point / …).
28969
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
28970
+ * the reverse:
28971
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
28972
+ * / `getOptions`), and
28973
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
28974
+ * robot camera shows up in the existing PTZ control path without every
28975
+ * PTZ provider learning about robots. The mapping lives in the adapter,
28976
+ * not here (see the addon design note):
28977
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
28978
+ * ptz.stop() → navigation.stop()
28979
+ * ptz.goHome() → navigation.runAction('goHome')
28980
+ * ptz.getPresets() → navigation.listActions() (id→preset)
28981
+ * ptz.goToPreset(id) → navigation.runAction(id)
28982
+ *
28983
+ * ## Continuous drive
28984
+ *
28985
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
28986
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
28987
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
28988
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
28989
+ * coalesce them. The UI owns the cadence.
28990
+ *
28991
+ * ## The action dictionary
28992
+ *
28993
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
28994
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
28995
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
28996
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
28997
+ * vendor-specific list. `kind: 'action'` entries are triggered with
28998
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
28999
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
29000
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
29001
+ *
29002
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
29003
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
29004
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
29005
+ * every device handle. A future nodedreame publish adds a typed
29006
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
29007
+ * provider can then swap the raw calls for the typed methods with no change to
29008
+ * THIS contract.
29009
+ */
29010
+ /**
29011
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
29012
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
29013
+ * halts it.
29014
+ *
29015
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
29016
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
29017
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
29018
+ * vector by it (drivers without proportional drive ignore it).
29019
+ *
29020
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
29021
+ * axis alone; an all-undefined nudge is a no-op.
29022
+ */
29023
+ var NavigationMoveCommandSchema = object({
29024
+ pan: number().min(-1).max(1).optional(),
29025
+ tilt: number().min(-1).max(1).optional(),
29026
+ speed: number().min(0).max(1).optional()
29027
+ });
29028
+ /**
29029
+ * The enumerated discrete actions a navigation-capable robot can perform via
29030
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
29031
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
29032
+ * `playSound` (see the `sound` dictionary entries).
29033
+ */
29034
+ var NavigationActionIdSchema = _enum([
29035
+ "goHome",
29036
+ "locate",
29037
+ "spotClean",
29038
+ "findPet",
29039
+ "personFollow",
29040
+ "stop",
29041
+ "startClean",
29042
+ "pauseClean",
29043
+ "dockWash",
29044
+ "autoEmpty",
29045
+ "flashOn",
29046
+ "flashOff"
29047
+ ]);
29048
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
29049
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
29050
+ /**
29051
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
29052
+ * native panel and the PTZ mimic render as a button.
29053
+ *
29054
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
29055
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
29056
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
29057
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
29058
+ * - `label` — operator-facing English label.
29059
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
29060
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
29061
+ * PTZ render ONLY enabled entries. Data-driven: the provider
29062
+ * flips it from config, never by editing code.
29063
+ */
29064
+ var NavigationActionEntrySchema = object({
29065
+ id: string(),
29066
+ kind: NavigationEntryKindSchema,
29067
+ label: string(),
29068
+ icon: string(),
29069
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
29070
+ soundId: number().int().optional(),
29071
+ /** Per-device feature flag — render this entry only when true. */
29072
+ enabled: boolean()
29073
+ });
29074
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
29075
+ var NavigationPointSchema = object({
29076
+ x: number(),
29077
+ y: number()
29078
+ });
29079
+ /**
29080
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
29081
+ * The cap reports which are enabled so the UI / PTZ render only the controls
29082
+ * that are turned on for THIS device. Data-driven: the provider derives these
29083
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
29084
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
29085
+ * that are not dictionary entries.
29086
+ *
29087
+ * - `move` / `stop` — the momentary drive joystick.
29088
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
29089
+ * map-coordinate plumbing is wired.
29090
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
29091
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
29092
+ * - `light` — the on/off fill-light toggle (works anytime).
29093
+ * - `lightMode` — the auto/manual selector + manual level slider (a
29094
+ * camera-service control; needs an active stream).
29095
+ */
29096
+ var NavigationFeaturesSchema = object({
29097
+ move: boolean(),
29098
+ stop: boolean(),
29099
+ goToPoint: boolean(),
29100
+ runAction: boolean(),
29101
+ playSound: boolean(),
29102
+ light: boolean(),
29103
+ lightMode: boolean()
29104
+ });
29105
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
29106
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
29107
+ /**
29108
+ * Live navigation state so the UI can reflect what the robot is doing:
29109
+ * - `mode` — coarse activity (idle / cleaning / following / …).
29110
+ * - `following` — person/pet follow is currently armed.
29111
+ * - `flash` — the on-camera fill light is on.
29112
+ * - `lightMode` — auto vs manual fill-light mode.
29113
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
29114
+ * `lightMode === 'manual'`.
29115
+ */
29116
+ var NavigationStatusSchema = object({
29117
+ mode: _enum([
29118
+ "idle",
29119
+ "cleaning",
29120
+ "spot",
29121
+ "following",
29122
+ "goto",
29123
+ "returning",
29124
+ "paused",
29125
+ "unknown"
29126
+ ]),
29127
+ following: boolean(),
29128
+ flash: boolean(),
29129
+ lightMode: NavigationLightModeSchema,
29130
+ lightLevel: number().min(40).max(100),
29131
+ /** Ms epoch when the slice was last updated. */
29132
+ lastChangedAt: number()
29133
+ });
29134
+ /**
29135
+ * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
29136
+ * observable). Adds `lastFetchedAt` on top of the status shape per the
29137
+ * convention.
29138
+ */
29139
+ var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: number() });
29140
+ var navigationCapability = {
29141
+ name: "navigation",
29142
+ scope: "device",
29143
+ deviceNative: true,
29144
+ mode: "singleton",
29145
+ deviceTypes: [DeviceType.Camera],
29146
+ deviceConfig: { ui: {
29147
+ kind: "widget",
29148
+ widgetId: "host/navigation-panel",
29149
+ tab: "navigation",
29150
+ topTab: true,
29151
+ label: "Navigation",
29152
+ order: 0
29153
+ } },
29154
+ methods: {
29155
+ /**
29156
+ * Momentary drive nudge (the robot moves). `protected` — mirrors
29157
+ * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
29158
+ * path) works for any authenticated user, not admin-only. The UI sends
29159
+ * these at ~1 Hz while a control is held; the provider forwards each one to
29160
+ * a single drive write WITHOUT debouncing.
29161
+ */
29162
+ move: method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29163
+ /** Halt all motion immediately (zero drive vector). */
29164
+ stop: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
29165
+ /** Send the robot to a point on its live map. */
29166
+ goToPoint: method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29167
+ /**
29168
+ * Enumerate the discrete controls THIS device supports (data-driven UI +
29169
+ * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
29170
+ */
29171
+ listActions: method(object({ deviceId: number() }), array(NavigationActionEntrySchema)),
29172
+ /**
29173
+ * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
29174
+ * unsupported action ids are rejected by the provider.
29175
+ */
29176
+ runAction: method(object({
29177
+ deviceId: number(),
29178
+ actionId: NavigationActionIdSchema
29179
+ }), _void(), { kind: "mutation" }),
29180
+ /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
29181
+ playSound: method(object({
29182
+ deviceId: number(),
29183
+ soundId: number().int()
29184
+ }), _void(), { kind: "mutation" }),
29185
+ /**
29186
+ * Turn the on-camera fill light on / off (the `OpenFullLight` control —
29187
+ * works anytime, no active stream required).
29188
+ */
29189
+ setLightOn: method(object({
29190
+ deviceId: number(),
29191
+ on: boolean()
29192
+ }), _void(), { kind: "mutation" }),
29193
+ /**
29194
+ * Set the fill-light mode (auto vs manual). `manual` optionally carries the
29195
+ * initial `level`. The auto/manual + level control is a CAMERA-service
29196
+ * action that generally needs an active camera stream/monitor session — the
29197
+ * UI shows the manual level slider ONLY when `mode === 'manual'`.
29198
+ */
29199
+ setLightMode: method(object({
29200
+ deviceId: number(),
29201
+ mode: NavigationLightModeSchema,
29202
+ level: number().min(40).max(100).optional()
29203
+ }), _void(), { kind: "mutation" }),
29204
+ /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
29205
+ setLightLevel: method(object({
29206
+ deviceId: number(),
29207
+ level: number().min(40).max(100)
29208
+ }), _void(), { kind: "mutation" }),
29209
+ /**
29210
+ * Per-device FEATURE-FLAG report for the general primitives — drives which
29211
+ * controls the UI shows (the per-entry flags for the dictionary come back on
29212
+ * `listActions`).
29213
+ */
29214
+ getFeatures: method(object({ deviceId: number() }), NavigationFeaturesSchema)
29215
+ },
29216
+ events: { onStatusChanged: { data: object({
29217
+ deviceId: number(),
29218
+ status: NavigationStatusSchema
29219
+ }) } },
29220
+ status: {
29221
+ schema: NavigationStatusSchema,
29222
+ kind: "push"
29223
+ },
29224
+ /**
29225
+ * Runtime-state slice mirrored by the kernel. The navigation panel watches it
29226
+ * for live mode / follow / flash changes.
29227
+ */
29228
+ runtimeState: NavigationRuntimeStateSchema,
29229
+ /**
29230
+ * Runtime-state durability: **session** — like `vacuum-control`, a restored
29231
+ * `mode: cleaning` / `following: true` is a robot that is not actually doing
29232
+ * that. The live handle re-publishes on connect.
29233
+ *
29234
+ * See `RuntimeStateDurability`. Enforced by
29235
+ * `scripts/check-runtime-state-durability.ts`.
29236
+ */
29237
+ durability: "session"
29238
+ };
28952
29239
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
28953
29240
  kind: "mutation",
28954
29241
  auth: "admin"
@@ -32224,6 +32511,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
32224
32511
  motionTrigger: motionTriggerCapability,
32225
32512
  motionZones: motionZonesCapability,
32226
32513
  nativeObjectDetection: nativeObjectDetectionCapability,
32514
+ navigation: navigationCapability,
32227
32515
  networkLink: networkLinkCapability,
32228
32516
  notifier: notifierCapability,
32229
32517
  numericSensor: numericSensorCapability,
@@ -36078,6 +36366,66 @@ Object.freeze({
36078
36366
  addonId: null,
36079
36367
  access: "create"
36080
36368
  },
36369
+ "navigation.getFeatures": {
36370
+ capName: "navigation",
36371
+ capScope: "device",
36372
+ addonId: null,
36373
+ access: "view"
36374
+ },
36375
+ "navigation.goToPoint": {
36376
+ capName: "navigation",
36377
+ capScope: "device",
36378
+ addonId: null,
36379
+ access: "create"
36380
+ },
36381
+ "navigation.listActions": {
36382
+ capName: "navigation",
36383
+ capScope: "device",
36384
+ addonId: null,
36385
+ access: "view"
36386
+ },
36387
+ "navigation.move": {
36388
+ capName: "navigation",
36389
+ capScope: "device",
36390
+ addonId: null,
36391
+ access: "create"
36392
+ },
36393
+ "navigation.playSound": {
36394
+ capName: "navigation",
36395
+ capScope: "device",
36396
+ addonId: null,
36397
+ access: "create"
36398
+ },
36399
+ "navigation.runAction": {
36400
+ capName: "navigation",
36401
+ capScope: "device",
36402
+ addonId: null,
36403
+ access: "create"
36404
+ },
36405
+ "navigation.setLightLevel": {
36406
+ capName: "navigation",
36407
+ capScope: "device",
36408
+ addonId: null,
36409
+ access: "create"
36410
+ },
36411
+ "navigation.setLightMode": {
36412
+ capName: "navigation",
36413
+ capScope: "device",
36414
+ addonId: null,
36415
+ access: "create"
36416
+ },
36417
+ "navigation.setLightOn": {
36418
+ capName: "navigation",
36419
+ capScope: "device",
36420
+ addonId: null,
36421
+ access: "create"
36422
+ },
36423
+ "navigation.stop": {
36424
+ capName: "navigation",
36425
+ capScope: "device",
36426
+ addonId: null,
36427
+ access: "create"
36428
+ },
36081
36429
  "networkAccess.getEndpoint": {
36082
36430
  capName: "network-access",
36083
36431
  capScope: "system",
@@ -40173,6 +40521,56 @@ Object.freeze({
40173
40521
  form: "single",
40174
40522
  optional: false
40175
40523
  }],
40524
+ "navigation.getFeatures": [{
40525
+ name: "deviceId",
40526
+ form: "single",
40527
+ optional: false
40528
+ }],
40529
+ "navigation.goToPoint": [{
40530
+ name: "deviceId",
40531
+ form: "single",
40532
+ optional: false
40533
+ }],
40534
+ "navigation.listActions": [{
40535
+ name: "deviceId",
40536
+ form: "single",
40537
+ optional: false
40538
+ }],
40539
+ "navigation.move": [{
40540
+ name: "deviceId",
40541
+ form: "single",
40542
+ optional: false
40543
+ }],
40544
+ "navigation.playSound": [{
40545
+ name: "deviceId",
40546
+ form: "single",
40547
+ optional: false
40548
+ }],
40549
+ "navigation.runAction": [{
40550
+ name: "deviceId",
40551
+ form: "single",
40552
+ optional: false
40553
+ }],
40554
+ "navigation.setLightLevel": [{
40555
+ name: "deviceId",
40556
+ form: "single",
40557
+ optional: false
40558
+ }],
40559
+ "navigation.setLightMode": [{
40560
+ name: "deviceId",
40561
+ form: "single",
40562
+ optional: false
40563
+ }],
40564
+ "navigation.setLightOn": [{
40565
+ name: "deviceId",
40566
+ form: "single",
40567
+ optional: false
40568
+ }],
40569
+ "navigation.stop": [{
40570
+ name: "deviceId",
40571
+ form: "single",
40572
+ optional: false
40573
+ }],
40176
40574
  "networkQuality.getDeviceStats": [{
40177
40575
  name: "deviceId",
40178
40576
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-gree",
3
- "version": "0.2.69",
3
+ "version": "0.2.71",
4
4
  "description": "Gree air-conditioner device-provider addon for CamStack — wraps the @apocaliss92/nodegree local-UDP client (LAN discovery + AES control), exposing climate-control and fan-control",
5
5
  "keywords": [
6
6
  "camstack",