@camstack/addon-provider-tuya 0.2.70 → 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
@@ -29792,6 +29792,287 @@ var ptzAutotrackCapability = {
29792
29792
  */
29793
29793
  durability: "session"
29794
29794
  };
29795
+ /**
29796
+ * `navigation` — a device-scoped capability that natively expresses the FULL
29797
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
29798
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
29799
+ *
29800
+ * Why a NEW cap rather than overloading `ptz`:
29801
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
29802
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
29803
+ * The two are different physical models: PTZ is absolute-position + presets,
29804
+ * navigation is momentary drive nudges + discrete robot ACTIONS
29805
+ * (dock / spot-clean / follow-pet / go-to-point / …).
29806
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
29807
+ * the reverse:
29808
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
29809
+ * / `getOptions`), and
29810
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
29811
+ * robot camera shows up in the existing PTZ control path without every
29812
+ * PTZ provider learning about robots. The mapping lives in the adapter,
29813
+ * not here (see the addon design note):
29814
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
29815
+ * ptz.stop() → navigation.stop()
29816
+ * ptz.goHome() → navigation.runAction('goHome')
29817
+ * ptz.getPresets() → navigation.listActions() (id→preset)
29818
+ * ptz.goToPreset(id) → navigation.runAction(id)
29819
+ *
29820
+ * ## Continuous drive
29821
+ *
29822
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
29823
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
29824
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
29825
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
29826
+ * coalesce them. The UI owns the cadence.
29827
+ *
29828
+ * ## The action dictionary
29829
+ *
29830
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
29831
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
29832
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
29833
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
29834
+ * vendor-specific list. `kind: 'action'` entries are triggered with
29835
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
29836
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
29837
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
29838
+ *
29839
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
29840
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
29841
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
29842
+ * every device handle. A future nodedreame publish adds a typed
29843
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
29844
+ * provider can then swap the raw calls for the typed methods with no change to
29845
+ * THIS contract.
29846
+ */
29847
+ /**
29848
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
29849
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
29850
+ * halts it.
29851
+ *
29852
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
29853
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
29854
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
29855
+ * vector by it (drivers without proportional drive ignore it).
29856
+ *
29857
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
29858
+ * axis alone; an all-undefined nudge is a no-op.
29859
+ */
29860
+ var NavigationMoveCommandSchema = object({
29861
+ pan: number().min(-1).max(1).optional(),
29862
+ tilt: number().min(-1).max(1).optional(),
29863
+ speed: number().min(0).max(1).optional()
29864
+ });
29865
+ /**
29866
+ * The enumerated discrete actions a navigation-capable robot can perform via
29867
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
29868
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
29869
+ * `playSound` (see the `sound` dictionary entries).
29870
+ */
29871
+ var NavigationActionIdSchema = _enum([
29872
+ "goHome",
29873
+ "locate",
29874
+ "spotClean",
29875
+ "findPet",
29876
+ "personFollow",
29877
+ "stop",
29878
+ "startClean",
29879
+ "pauseClean",
29880
+ "dockWash",
29881
+ "autoEmpty",
29882
+ "flashOn",
29883
+ "flashOff"
29884
+ ]);
29885
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
29886
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
29887
+ /**
29888
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
29889
+ * native panel and the PTZ mimic render as a button.
29890
+ *
29891
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
29892
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
29893
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
29894
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
29895
+ * - `label` — operator-facing English label.
29896
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
29897
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
29898
+ * PTZ render ONLY enabled entries. Data-driven: the provider
29899
+ * flips it from config, never by editing code.
29900
+ */
29901
+ var NavigationActionEntrySchema = object({
29902
+ id: string(),
29903
+ kind: NavigationEntryKindSchema,
29904
+ label: string(),
29905
+ icon: string(),
29906
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
29907
+ soundId: number().int().optional(),
29908
+ /** Per-device feature flag — render this entry only when true. */
29909
+ enabled: boolean()
29910
+ });
29911
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
29912
+ var NavigationPointSchema = object({
29913
+ x: number(),
29914
+ y: number()
29915
+ });
29916
+ /**
29917
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
29918
+ * The cap reports which are enabled so the UI / PTZ render only the controls
29919
+ * that are turned on for THIS device. Data-driven: the provider derives these
29920
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
29921
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
29922
+ * that are not dictionary entries.
29923
+ *
29924
+ * - `move` / `stop` — the momentary drive joystick.
29925
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
29926
+ * map-coordinate plumbing is wired.
29927
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
29928
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
29929
+ * - `light` — the on/off fill-light toggle (works anytime).
29930
+ * - `lightMode` — the auto/manual selector + manual level slider (a
29931
+ * camera-service control; needs an active stream).
29932
+ */
29933
+ var NavigationFeaturesSchema = object({
29934
+ move: boolean(),
29935
+ stop: boolean(),
29936
+ goToPoint: boolean(),
29937
+ runAction: boolean(),
29938
+ playSound: boolean(),
29939
+ light: boolean(),
29940
+ lightMode: boolean()
29941
+ });
29942
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
29943
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
29944
+ /**
29945
+ * Live navigation state so the UI can reflect what the robot is doing:
29946
+ * - `mode` — coarse activity (idle / cleaning / following / …).
29947
+ * - `following` — person/pet follow is currently armed.
29948
+ * - `flash` — the on-camera fill light is on.
29949
+ * - `lightMode` — auto vs manual fill-light mode.
29950
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
29951
+ * `lightMode === 'manual'`.
29952
+ */
29953
+ var NavigationStatusSchema = object({
29954
+ mode: _enum([
29955
+ "idle",
29956
+ "cleaning",
29957
+ "spot",
29958
+ "following",
29959
+ "goto",
29960
+ "returning",
29961
+ "paused",
29962
+ "unknown"
29963
+ ]),
29964
+ following: boolean(),
29965
+ flash: boolean(),
29966
+ lightMode: NavigationLightModeSchema,
29967
+ lightLevel: number().min(40).max(100),
29968
+ /** Ms epoch when the slice was last updated. */
29969
+ lastChangedAt: number()
29970
+ });
29971
+ /**
29972
+ * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
29973
+ * observable). Adds `lastFetchedAt` on top of the status shape per the
29974
+ * convention.
29975
+ */
29976
+ var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: number() });
29977
+ var navigationCapability = {
29978
+ name: "navigation",
29979
+ scope: "device",
29980
+ deviceNative: true,
29981
+ mode: "singleton",
29982
+ deviceTypes: [DeviceType.Camera],
29983
+ deviceConfig: { ui: {
29984
+ kind: "widget",
29985
+ widgetId: "host/navigation-panel",
29986
+ tab: "navigation",
29987
+ topTab: true,
29988
+ label: "Navigation",
29989
+ order: 0
29990
+ } },
29991
+ methods: {
29992
+ /**
29993
+ * Momentary drive nudge (the robot moves). `protected` — mirrors
29994
+ * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
29995
+ * path) works for any authenticated user, not admin-only. The UI sends
29996
+ * these at ~1 Hz while a control is held; the provider forwards each one to
29997
+ * a single drive write WITHOUT debouncing.
29998
+ */
29999
+ move: method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
30000
+ /** Halt all motion immediately (zero drive vector). */
30001
+ stop: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
30002
+ /** Send the robot to a point on its live map. */
30003
+ goToPoint: method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
30004
+ /**
30005
+ * Enumerate the discrete controls THIS device supports (data-driven UI +
30006
+ * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
30007
+ */
30008
+ listActions: method(object({ deviceId: number() }), array(NavigationActionEntrySchema)),
30009
+ /**
30010
+ * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
30011
+ * unsupported action ids are rejected by the provider.
30012
+ */
30013
+ runAction: method(object({
30014
+ deviceId: number(),
30015
+ actionId: NavigationActionIdSchema
30016
+ }), _void(), { kind: "mutation" }),
30017
+ /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
30018
+ playSound: method(object({
30019
+ deviceId: number(),
30020
+ soundId: number().int()
30021
+ }), _void(), { kind: "mutation" }),
30022
+ /**
30023
+ * Turn the on-camera fill light on / off (the `OpenFullLight` control —
30024
+ * works anytime, no active stream required).
30025
+ */
30026
+ setLightOn: method(object({
30027
+ deviceId: number(),
30028
+ on: boolean()
30029
+ }), _void(), { kind: "mutation" }),
30030
+ /**
30031
+ * Set the fill-light mode (auto vs manual). `manual` optionally carries the
30032
+ * initial `level`. The auto/manual + level control is a CAMERA-service
30033
+ * action that generally needs an active camera stream/monitor session — the
30034
+ * UI shows the manual level slider ONLY when `mode === 'manual'`.
30035
+ */
30036
+ setLightMode: method(object({
30037
+ deviceId: number(),
30038
+ mode: NavigationLightModeSchema,
30039
+ level: number().min(40).max(100).optional()
30040
+ }), _void(), { kind: "mutation" }),
30041
+ /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
30042
+ setLightLevel: method(object({
30043
+ deviceId: number(),
30044
+ level: number().min(40).max(100)
30045
+ }), _void(), { kind: "mutation" }),
30046
+ /**
30047
+ * Per-device FEATURE-FLAG report for the general primitives — drives which
30048
+ * controls the UI shows (the per-entry flags for the dictionary come back on
30049
+ * `listActions`).
30050
+ */
30051
+ getFeatures: method(object({ deviceId: number() }), NavigationFeaturesSchema)
30052
+ },
30053
+ events: { onStatusChanged: { data: object({
30054
+ deviceId: number(),
30055
+ status: NavigationStatusSchema
30056
+ }) } },
30057
+ status: {
30058
+ schema: NavigationStatusSchema,
30059
+ kind: "push"
30060
+ },
30061
+ /**
30062
+ * Runtime-state slice mirrored by the kernel. The navigation panel watches it
30063
+ * for live mode / follow / flash changes.
30064
+ */
30065
+ runtimeState: NavigationRuntimeStateSchema,
30066
+ /**
30067
+ * Runtime-state durability: **session** — like `vacuum-control`, a restored
30068
+ * `mode: cleaning` / `following: true` is a robot that is not actually doing
30069
+ * that. The live handle re-publishes on connect.
30070
+ *
30071
+ * See `RuntimeStateDurability`. Enforced by
30072
+ * `scripts/check-runtime-state-durability.ts`.
30073
+ */
30074
+ durability: "session"
30075
+ };
29795
30076
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
29796
30077
  kind: "mutation",
29797
30078
  auth: "admin"
@@ -33067,6 +33348,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
33067
33348
  motionTrigger: motionTriggerCapability,
33068
33349
  motionZones: motionZonesCapability,
33069
33350
  nativeObjectDetection: nativeObjectDetectionCapability,
33351
+ navigation: navigationCapability,
33070
33352
  networkLink: networkLinkCapability,
33071
33353
  notifier: notifierCapability,
33072
33354
  numericSensor: numericSensorCapability,
@@ -36921,6 +37203,66 @@ Object.freeze({
36921
37203
  addonId: null,
36922
37204
  access: "create"
36923
37205
  },
37206
+ "navigation.getFeatures": {
37207
+ capName: "navigation",
37208
+ capScope: "device",
37209
+ addonId: null,
37210
+ access: "view"
37211
+ },
37212
+ "navigation.goToPoint": {
37213
+ capName: "navigation",
37214
+ capScope: "device",
37215
+ addonId: null,
37216
+ access: "create"
37217
+ },
37218
+ "navigation.listActions": {
37219
+ capName: "navigation",
37220
+ capScope: "device",
37221
+ addonId: null,
37222
+ access: "view"
37223
+ },
37224
+ "navigation.move": {
37225
+ capName: "navigation",
37226
+ capScope: "device",
37227
+ addonId: null,
37228
+ access: "create"
37229
+ },
37230
+ "navigation.playSound": {
37231
+ capName: "navigation",
37232
+ capScope: "device",
37233
+ addonId: null,
37234
+ access: "create"
37235
+ },
37236
+ "navigation.runAction": {
37237
+ capName: "navigation",
37238
+ capScope: "device",
37239
+ addonId: null,
37240
+ access: "create"
37241
+ },
37242
+ "navigation.setLightLevel": {
37243
+ capName: "navigation",
37244
+ capScope: "device",
37245
+ addonId: null,
37246
+ access: "create"
37247
+ },
37248
+ "navigation.setLightMode": {
37249
+ capName: "navigation",
37250
+ capScope: "device",
37251
+ addonId: null,
37252
+ access: "create"
37253
+ },
37254
+ "navigation.setLightOn": {
37255
+ capName: "navigation",
37256
+ capScope: "device",
37257
+ addonId: null,
37258
+ access: "create"
37259
+ },
37260
+ "navigation.stop": {
37261
+ capName: "navigation",
37262
+ capScope: "device",
37263
+ addonId: null,
37264
+ access: "create"
37265
+ },
36924
37266
  "networkAccess.getEndpoint": {
36925
37267
  capName: "network-access",
36926
37268
  capScope: "system",
@@ -41016,6 +41358,56 @@ Object.freeze({
41016
41358
  form: "single",
41017
41359
  optional: false
41018
41360
  }],
41361
+ "navigation.getFeatures": [{
41362
+ name: "deviceId",
41363
+ form: "single",
41364
+ optional: false
41365
+ }],
41366
+ "navigation.goToPoint": [{
41367
+ name: "deviceId",
41368
+ form: "single",
41369
+ optional: false
41370
+ }],
41371
+ "navigation.listActions": [{
41372
+ name: "deviceId",
41373
+ form: "single",
41374
+ optional: false
41375
+ }],
41376
+ "navigation.move": [{
41377
+ name: "deviceId",
41378
+ form: "single",
41379
+ optional: false
41380
+ }],
41381
+ "navigation.playSound": [{
41382
+ name: "deviceId",
41383
+ form: "single",
41384
+ optional: false
41385
+ }],
41386
+ "navigation.runAction": [{
41387
+ name: "deviceId",
41388
+ form: "single",
41389
+ optional: false
41390
+ }],
41391
+ "navigation.setLightLevel": [{
41392
+ name: "deviceId",
41393
+ form: "single",
41394
+ optional: false
41395
+ }],
41396
+ "navigation.setLightMode": [{
41397
+ name: "deviceId",
41398
+ form: "single",
41399
+ optional: false
41400
+ }],
41401
+ "navigation.setLightOn": [{
41402
+ name: "deviceId",
41403
+ form: "single",
41404
+ optional: false
41405
+ }],
41406
+ "navigation.stop": [{
41407
+ name: "deviceId",
41408
+ form: "single",
41409
+ optional: false
41410
+ }],
41019
41411
  "networkQuality.getDeviceStats": [{
41020
41412
  name: "deviceId",
41021
41413
  form: "single",
package/dist/addon.mjs CHANGED
@@ -29791,6 +29791,287 @@ var ptzAutotrackCapability = {
29791
29791
  */
29792
29792
  durability: "session"
29793
29793
  };
29794
+ /**
29795
+ * `navigation` — a device-scoped capability that natively expresses the FULL
29796
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
29797
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
29798
+ *
29799
+ * Why a NEW cap rather than overloading `ptz`:
29800
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
29801
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
29802
+ * The two are different physical models: PTZ is absolute-position + presets,
29803
+ * navigation is momentary drive nudges + discrete robot ACTIONS
29804
+ * (dock / spot-clean / follow-pet / go-to-point / …).
29805
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
29806
+ * the reverse:
29807
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
29808
+ * / `getOptions`), and
29809
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
29810
+ * robot camera shows up in the existing PTZ control path without every
29811
+ * PTZ provider learning about robots. The mapping lives in the adapter,
29812
+ * not here (see the addon design note):
29813
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
29814
+ * ptz.stop() → navigation.stop()
29815
+ * ptz.goHome() → navigation.runAction('goHome')
29816
+ * ptz.getPresets() → navigation.listActions() (id→preset)
29817
+ * ptz.goToPreset(id) → navigation.runAction(id)
29818
+ *
29819
+ * ## Continuous drive
29820
+ *
29821
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
29822
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
29823
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
29824
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
29825
+ * coalesce them. The UI owns the cadence.
29826
+ *
29827
+ * ## The action dictionary
29828
+ *
29829
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
29830
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
29831
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
29832
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
29833
+ * vendor-specific list. `kind: 'action'` entries are triggered with
29834
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
29835
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
29836
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
29837
+ *
29838
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
29839
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
29840
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
29841
+ * every device handle. A future nodedreame publish adds a typed
29842
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
29843
+ * provider can then swap the raw calls for the typed methods with no change to
29844
+ * THIS contract.
29845
+ */
29846
+ /**
29847
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
29848
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
29849
+ * halts it.
29850
+ *
29851
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
29852
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
29853
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
29854
+ * vector by it (drivers without proportional drive ignore it).
29855
+ *
29856
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
29857
+ * axis alone; an all-undefined nudge is a no-op.
29858
+ */
29859
+ var NavigationMoveCommandSchema = object({
29860
+ pan: number().min(-1).max(1).optional(),
29861
+ tilt: number().min(-1).max(1).optional(),
29862
+ speed: number().min(0).max(1).optional()
29863
+ });
29864
+ /**
29865
+ * The enumerated discrete actions a navigation-capable robot can perform via
29866
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
29867
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
29868
+ * `playSound` (see the `sound` dictionary entries).
29869
+ */
29870
+ var NavigationActionIdSchema = _enum([
29871
+ "goHome",
29872
+ "locate",
29873
+ "spotClean",
29874
+ "findPet",
29875
+ "personFollow",
29876
+ "stop",
29877
+ "startClean",
29878
+ "pauseClean",
29879
+ "dockWash",
29880
+ "autoEmpty",
29881
+ "flashOn",
29882
+ "flashOff"
29883
+ ]);
29884
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
29885
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
29886
+ /**
29887
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
29888
+ * native panel and the PTZ mimic render as a button.
29889
+ *
29890
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
29891
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
29892
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
29893
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
29894
+ * - `label` — operator-facing English label.
29895
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
29896
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
29897
+ * PTZ render ONLY enabled entries. Data-driven: the provider
29898
+ * flips it from config, never by editing code.
29899
+ */
29900
+ var NavigationActionEntrySchema = object({
29901
+ id: string(),
29902
+ kind: NavigationEntryKindSchema,
29903
+ label: string(),
29904
+ icon: string(),
29905
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
29906
+ soundId: number().int().optional(),
29907
+ /** Per-device feature flag — render this entry only when true. */
29908
+ enabled: boolean()
29909
+ });
29910
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
29911
+ var NavigationPointSchema = object({
29912
+ x: number(),
29913
+ y: number()
29914
+ });
29915
+ /**
29916
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
29917
+ * The cap reports which are enabled so the UI / PTZ render only the controls
29918
+ * that are turned on for THIS device. Data-driven: the provider derives these
29919
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
29920
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
29921
+ * that are not dictionary entries.
29922
+ *
29923
+ * - `move` / `stop` — the momentary drive joystick.
29924
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
29925
+ * map-coordinate plumbing is wired.
29926
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
29927
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
29928
+ * - `light` — the on/off fill-light toggle (works anytime).
29929
+ * - `lightMode` — the auto/manual selector + manual level slider (a
29930
+ * camera-service control; needs an active stream).
29931
+ */
29932
+ var NavigationFeaturesSchema = object({
29933
+ move: boolean(),
29934
+ stop: boolean(),
29935
+ goToPoint: boolean(),
29936
+ runAction: boolean(),
29937
+ playSound: boolean(),
29938
+ light: boolean(),
29939
+ lightMode: boolean()
29940
+ });
29941
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
29942
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
29943
+ /**
29944
+ * Live navigation state so the UI can reflect what the robot is doing:
29945
+ * - `mode` — coarse activity (idle / cleaning / following / …).
29946
+ * - `following` — person/pet follow is currently armed.
29947
+ * - `flash` — the on-camera fill light is on.
29948
+ * - `lightMode` — auto vs manual fill-light mode.
29949
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
29950
+ * `lightMode === 'manual'`.
29951
+ */
29952
+ var NavigationStatusSchema = object({
29953
+ mode: _enum([
29954
+ "idle",
29955
+ "cleaning",
29956
+ "spot",
29957
+ "following",
29958
+ "goto",
29959
+ "returning",
29960
+ "paused",
29961
+ "unknown"
29962
+ ]),
29963
+ following: boolean(),
29964
+ flash: boolean(),
29965
+ lightMode: NavigationLightModeSchema,
29966
+ lightLevel: number().min(40).max(100),
29967
+ /** Ms epoch when the slice was last updated. */
29968
+ lastChangedAt: number()
29969
+ });
29970
+ /**
29971
+ * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
29972
+ * observable). Adds `lastFetchedAt` on top of the status shape per the
29973
+ * convention.
29974
+ */
29975
+ var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: number() });
29976
+ var navigationCapability = {
29977
+ name: "navigation",
29978
+ scope: "device",
29979
+ deviceNative: true,
29980
+ mode: "singleton",
29981
+ deviceTypes: [DeviceType.Camera],
29982
+ deviceConfig: { ui: {
29983
+ kind: "widget",
29984
+ widgetId: "host/navigation-panel",
29985
+ tab: "navigation",
29986
+ topTab: true,
29987
+ label: "Navigation",
29988
+ order: 0
29989
+ } },
29990
+ methods: {
29991
+ /**
29992
+ * Momentary drive nudge (the robot moves). `protected` — mirrors
29993
+ * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
29994
+ * path) works for any authenticated user, not admin-only. The UI sends
29995
+ * these at ~1 Hz while a control is held; the provider forwards each one to
29996
+ * a single drive write WITHOUT debouncing.
29997
+ */
29998
+ move: method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
29999
+ /** Halt all motion immediately (zero drive vector). */
30000
+ stop: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
30001
+ /** Send the robot to a point on its live map. */
30002
+ goToPoint: method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
30003
+ /**
30004
+ * Enumerate the discrete controls THIS device supports (data-driven UI +
30005
+ * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
30006
+ */
30007
+ listActions: method(object({ deviceId: number() }), array(NavigationActionEntrySchema)),
30008
+ /**
30009
+ * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
30010
+ * unsupported action ids are rejected by the provider.
30011
+ */
30012
+ runAction: method(object({
30013
+ deviceId: number(),
30014
+ actionId: NavigationActionIdSchema
30015
+ }), _void(), { kind: "mutation" }),
30016
+ /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
30017
+ playSound: method(object({
30018
+ deviceId: number(),
30019
+ soundId: number().int()
30020
+ }), _void(), { kind: "mutation" }),
30021
+ /**
30022
+ * Turn the on-camera fill light on / off (the `OpenFullLight` control —
30023
+ * works anytime, no active stream required).
30024
+ */
30025
+ setLightOn: method(object({
30026
+ deviceId: number(),
30027
+ on: boolean()
30028
+ }), _void(), { kind: "mutation" }),
30029
+ /**
30030
+ * Set the fill-light mode (auto vs manual). `manual` optionally carries the
30031
+ * initial `level`. The auto/manual + level control is a CAMERA-service
30032
+ * action that generally needs an active camera stream/monitor session — the
30033
+ * UI shows the manual level slider ONLY when `mode === 'manual'`.
30034
+ */
30035
+ setLightMode: method(object({
30036
+ deviceId: number(),
30037
+ mode: NavigationLightModeSchema,
30038
+ level: number().min(40).max(100).optional()
30039
+ }), _void(), { kind: "mutation" }),
30040
+ /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
30041
+ setLightLevel: method(object({
30042
+ deviceId: number(),
30043
+ level: number().min(40).max(100)
30044
+ }), _void(), { kind: "mutation" }),
30045
+ /**
30046
+ * Per-device FEATURE-FLAG report for the general primitives — drives which
30047
+ * controls the UI shows (the per-entry flags for the dictionary come back on
30048
+ * `listActions`).
30049
+ */
30050
+ getFeatures: method(object({ deviceId: number() }), NavigationFeaturesSchema)
30051
+ },
30052
+ events: { onStatusChanged: { data: object({
30053
+ deviceId: number(),
30054
+ status: NavigationStatusSchema
30055
+ }) } },
30056
+ status: {
30057
+ schema: NavigationStatusSchema,
30058
+ kind: "push"
30059
+ },
30060
+ /**
30061
+ * Runtime-state slice mirrored by the kernel. The navigation panel watches it
30062
+ * for live mode / follow / flash changes.
30063
+ */
30064
+ runtimeState: NavigationRuntimeStateSchema,
30065
+ /**
30066
+ * Runtime-state durability: **session** — like `vacuum-control`, a restored
30067
+ * `mode: cleaning` / `following: true` is a robot that is not actually doing
30068
+ * that. The live handle re-publishes on connect.
30069
+ *
30070
+ * See `RuntimeStateDurability`. Enforced by
30071
+ * `scripts/check-runtime-state-durability.ts`.
30072
+ */
30073
+ durability: "session"
30074
+ };
29794
30075
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
29795
30076
  kind: "mutation",
29796
30077
  auth: "admin"
@@ -33066,6 +33347,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
33066
33347
  motionTrigger: motionTriggerCapability,
33067
33348
  motionZones: motionZonesCapability,
33068
33349
  nativeObjectDetection: nativeObjectDetectionCapability,
33350
+ navigation: navigationCapability,
33069
33351
  networkLink: networkLinkCapability,
33070
33352
  notifier: notifierCapability,
33071
33353
  numericSensor: numericSensorCapability,
@@ -36920,6 +37202,66 @@ Object.freeze({
36920
37202
  addonId: null,
36921
37203
  access: "create"
36922
37204
  },
37205
+ "navigation.getFeatures": {
37206
+ capName: "navigation",
37207
+ capScope: "device",
37208
+ addonId: null,
37209
+ access: "view"
37210
+ },
37211
+ "navigation.goToPoint": {
37212
+ capName: "navigation",
37213
+ capScope: "device",
37214
+ addonId: null,
37215
+ access: "create"
37216
+ },
37217
+ "navigation.listActions": {
37218
+ capName: "navigation",
37219
+ capScope: "device",
37220
+ addonId: null,
37221
+ access: "view"
37222
+ },
37223
+ "navigation.move": {
37224
+ capName: "navigation",
37225
+ capScope: "device",
37226
+ addonId: null,
37227
+ access: "create"
37228
+ },
37229
+ "navigation.playSound": {
37230
+ capName: "navigation",
37231
+ capScope: "device",
37232
+ addonId: null,
37233
+ access: "create"
37234
+ },
37235
+ "navigation.runAction": {
37236
+ capName: "navigation",
37237
+ capScope: "device",
37238
+ addonId: null,
37239
+ access: "create"
37240
+ },
37241
+ "navigation.setLightLevel": {
37242
+ capName: "navigation",
37243
+ capScope: "device",
37244
+ addonId: null,
37245
+ access: "create"
37246
+ },
37247
+ "navigation.setLightMode": {
37248
+ capName: "navigation",
37249
+ capScope: "device",
37250
+ addonId: null,
37251
+ access: "create"
37252
+ },
37253
+ "navigation.setLightOn": {
37254
+ capName: "navigation",
37255
+ capScope: "device",
37256
+ addonId: null,
37257
+ access: "create"
37258
+ },
37259
+ "navigation.stop": {
37260
+ capName: "navigation",
37261
+ capScope: "device",
37262
+ addonId: null,
37263
+ access: "create"
37264
+ },
36923
37265
  "networkAccess.getEndpoint": {
36924
37266
  capName: "network-access",
36925
37267
  capScope: "system",
@@ -41015,6 +41357,56 @@ Object.freeze({
41015
41357
  form: "single",
41016
41358
  optional: false
41017
41359
  }],
41360
+ "navigation.getFeatures": [{
41361
+ name: "deviceId",
41362
+ form: "single",
41363
+ optional: false
41364
+ }],
41365
+ "navigation.goToPoint": [{
41366
+ name: "deviceId",
41367
+ form: "single",
41368
+ optional: false
41369
+ }],
41370
+ "navigation.listActions": [{
41371
+ name: "deviceId",
41372
+ form: "single",
41373
+ optional: false
41374
+ }],
41375
+ "navigation.move": [{
41376
+ name: "deviceId",
41377
+ form: "single",
41378
+ optional: false
41379
+ }],
41380
+ "navigation.playSound": [{
41381
+ name: "deviceId",
41382
+ form: "single",
41383
+ optional: false
41384
+ }],
41385
+ "navigation.runAction": [{
41386
+ name: "deviceId",
41387
+ form: "single",
41388
+ optional: false
41389
+ }],
41390
+ "navigation.setLightLevel": [{
41391
+ name: "deviceId",
41392
+ form: "single",
41393
+ optional: false
41394
+ }],
41395
+ "navigation.setLightMode": [{
41396
+ name: "deviceId",
41397
+ form: "single",
41398
+ optional: false
41399
+ }],
41400
+ "navigation.setLightOn": [{
41401
+ name: "deviceId",
41402
+ form: "single",
41403
+ optional: false
41404
+ }],
41405
+ "navigation.stop": [{
41406
+ name: "deviceId",
41407
+ form: "single",
41408
+ optional: false
41409
+ }],
41018
41410
  "networkQuality.getDeviceStats": [{
41019
41411
  name: "deviceId",
41020
41412
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-tuya",
3
- "version": "0.2.70",
3
+ "version": "0.2.71",
4
4
  "description": "Tuya / Smart Life device-provider addon for CamStack — account-onboarded (Tuya IoT cloud fetch of device localKeys) + LOCAL DP control via the @apocaliss92/nodetuya encrypted-LAN client, exposing switch / water-heater-family kettle entities",
5
5
  "keywords": [
6
6
  "camstack",