@camstack/addon-provider-tuya 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
@@ -819,7 +819,7 @@ async function discoverDevices(options = {}, binder = createDgramBinder()) {
819
819
  return [...byId.values()];
820
820
  }
821
821
  //#endregion
822
- //#region ../types/dist/event-category-BZL-fdNj.mjs
822
+ //#region ../types/dist/event-category-zAv7pMUz.mjs
823
823
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
824
824
  EventCategory["SystemBoot"] = "system.boot";
825
825
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -1191,6 +1191,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
1191
1191
  */
1192
1192
  EventCategory["BatteryOnStatusChanged"] = "battery.onStatusChanged";
1193
1193
  /**
1194
+ * Cap event fired by every device that registers the `network-link`
1195
+ * capability. Mirrors the cap definition's `onStatusChanged`. Carries
1196
+ * `{ deviceId, status: NetworkLinkStatus }` — a link switch or a signal
1197
+ * reading that moved.
1198
+ */
1199
+ EventCategory["NetworkLinkOnStatusChanged"] = "network-link.onStatusChanged";
1200
+ /**
1194
1201
  * Emitted by the battery cap provider WHEN `wakeForStream` enters the
1195
1202
  * "wake in progress" window — between the Baichuan wake-up issue and
1196
1203
  * the camera's first dialed-back RTP packet. The stream-broker
@@ -24586,6 +24593,88 @@ onStatusChanged: { data: object({
24586
24593
  volatileStateFields: ["lastUpdated"]
24587
24594
  };
24588
24595
  /**
24596
+ * Network-link snapshot. Same shape for every provider (a Reolink wifi
24597
+ * camera, a Home Assistant device with a signal-strength sensor, a Tapo
24598
+ * plug): one slice under `device.runtimeState['network-link']`, one badge,
24599
+ * one Home Assistant projection.
24600
+ */
24601
+ var NetworkLinkStatusSchema = object({
24602
+ /** The link the device is on. `'unknown'` = not read yet, not "no link". */
24603
+ type: _enum([
24604
+ "wifi",
24605
+ "ethernet",
24606
+ "cellular",
24607
+ "unknown"
24608
+ ]),
24609
+ /**
24610
+ * Link quality, 0..100 inclusive, normalised by the provider from whatever
24611
+ * the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
24612
+ * KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
24613
+ * one whose reading has not landed must not be drawn at 0 %. Consumers
24614
+ * SKIP a null rather than coerce it.
24615
+ */
24616
+ signalPercent: number().min(0).max(100).nullable(),
24617
+ /** Raw received signal strength in dBm, when the firmware reports one. */
24618
+ rssiDbm: number().optional(),
24619
+ /** Network name of a wireless link, when the firmware reports it. */
24620
+ ssid: string().optional(),
24621
+ /** Ms epoch of the last observation. Lets consumers reason about freshness. */
24622
+ lastUpdated: number()
24623
+ });
24624
+ var networkLinkCapability = {
24625
+ name: "network-link",
24626
+ scope: "device",
24627
+ deviceNative: true,
24628
+ mode: "singleton",
24629
+ deviceTypes: [
24630
+ DeviceType.Camera,
24631
+ DeviceType.Sensor,
24632
+ DeviceType.Button,
24633
+ DeviceType.Switch,
24634
+ DeviceType.Light,
24635
+ DeviceType.Lock,
24636
+ DeviceType.Siren
24637
+ ],
24638
+ methods: {},
24639
+ events: {
24640
+ /**
24641
+ * Emitted whenever the cached status changes (a link switch, a signal
24642
+ * reading that moved). Mirrored on the parent chain by the
24643
+ * DeviceEventPropagator like `battery.onStatusChanged`.
24644
+ */
24645
+ onStatusChanged: { data: object({
24646
+ deviceId: number(),
24647
+ status: NetworkLinkStatusSchema
24648
+ }) } },
24649
+ status: {
24650
+ schema: NetworkLinkStatusSchema,
24651
+ kind: "push",
24652
+ empty: {
24653
+ type: "unknown",
24654
+ signalPercent: null,
24655
+ lastUpdated: 0
24656
+ }
24657
+ },
24658
+ /**
24659
+ * Runtime-state slice — every provider stores the same shape under
24660
+ * `device.runtimeState['network-link']`, read once by the badge and the
24661
+ * Home Assistant projector regardless of the driver.
24662
+ */
24663
+ runtimeState: NetworkLinkStatusSchema,
24664
+ /**
24665
+ * Runtime-state durability: **restored** — a link reading is slow to
24666
+ * change and a sleeping battery camera may not report for hours; the
24667
+ * restored slice is what the badge shows until the next read.
24668
+ *
24669
+ * See `RuntimeStateDurability`. Enforced by
24670
+ * `scripts/check-runtime-state-durability.ts`.
24671
+ */
24672
+ durability: "restored",
24673
+ /** Clock fields: written, but excluded from the compare that decides
24674
+ * whether persisting is worth a SQLite commit. */
24675
+ volatileStateFields: ["lastUpdated"]
24676
+ };
24677
+ /**
24589
24678
  * Generic boolean sensor — last-resort fallback when no domain-
24590
24679
  * specific binary cap fits (Home Assistant `binary_sensor` without a
24591
24680
  * known `device_class`, or a domain we haven't typed yet). Pure
@@ -29703,6 +29792,287 @@ var ptzAutotrackCapability = {
29703
29792
  */
29704
29793
  durability: "session"
29705
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
+ };
29706
30076
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
29707
30077
  kind: "mutation",
29708
30078
  auth: "admin"
@@ -32978,6 +33348,8 @@ var DEVICE_LOCAL_STATE_CAPS = {
32978
33348
  motionTrigger: motionTriggerCapability,
32979
33349
  motionZones: motionZonesCapability,
32980
33350
  nativeObjectDetection: nativeObjectDetectionCapability,
33351
+ navigation: navigationCapability,
33352
+ networkLink: networkLinkCapability,
32981
33353
  notifier: notifierCapability,
32982
33354
  numericSensor: numericSensorCapability,
32983
33355
  petFeeder: petFeederCapability,
@@ -36831,6 +37203,66 @@ Object.freeze({
36831
37203
  addonId: null,
36832
37204
  access: "create"
36833
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
+ },
36834
37266
  "networkAccess.getEndpoint": {
36835
37267
  capName: "network-access",
36836
37268
  capScope: "system",
@@ -40926,6 +41358,56 @@ Object.freeze({
40926
41358
  form: "single",
40927
41359
  optional: false
40928
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
+ }],
40929
41411
  "networkQuality.getDeviceStats": [{
40930
41412
  name: "deviceId",
40931
41413
  form: "single",
package/dist/addon.mjs CHANGED
@@ -818,7 +818,7 @@ async function discoverDevices(options = {}, binder = createDgramBinder()) {
818
818
  return [...byId.values()];
819
819
  }
820
820
  //#endregion
821
- //#region ../types/dist/event-category-BZL-fdNj.mjs
821
+ //#region ../types/dist/event-category-zAv7pMUz.mjs
822
822
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
823
823
  EventCategory["SystemBoot"] = "system.boot";
824
824
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -1190,6 +1190,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
1190
1190
  */
1191
1191
  EventCategory["BatteryOnStatusChanged"] = "battery.onStatusChanged";
1192
1192
  /**
1193
+ * Cap event fired by every device that registers the `network-link`
1194
+ * capability. Mirrors the cap definition's `onStatusChanged`. Carries
1195
+ * `{ deviceId, status: NetworkLinkStatus }` — a link switch or a signal
1196
+ * reading that moved.
1197
+ */
1198
+ EventCategory["NetworkLinkOnStatusChanged"] = "network-link.onStatusChanged";
1199
+ /**
1193
1200
  * Emitted by the battery cap provider WHEN `wakeForStream` enters the
1194
1201
  * "wake in progress" window — between the Baichuan wake-up issue and
1195
1202
  * the camera's first dialed-back RTP packet. The stream-broker
@@ -24585,6 +24592,88 @@ onStatusChanged: { data: object({
24585
24592
  volatileStateFields: ["lastUpdated"]
24586
24593
  };
24587
24594
  /**
24595
+ * Network-link snapshot. Same shape for every provider (a Reolink wifi
24596
+ * camera, a Home Assistant device with a signal-strength sensor, a Tapo
24597
+ * plug): one slice under `device.runtimeState['network-link']`, one badge,
24598
+ * one Home Assistant projection.
24599
+ */
24600
+ var NetworkLinkStatusSchema = object({
24601
+ /** The link the device is on. `'unknown'` = not read yet, not "no link". */
24602
+ type: _enum([
24603
+ "wifi",
24604
+ "ethernet",
24605
+ "cellular",
24606
+ "unknown"
24607
+ ]),
24608
+ /**
24609
+ * Link quality, 0..100 inclusive, normalised by the provider from whatever
24610
+ * the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
24611
+ * KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
24612
+ * one whose reading has not landed must not be drawn at 0 %. Consumers
24613
+ * SKIP a null rather than coerce it.
24614
+ */
24615
+ signalPercent: number().min(0).max(100).nullable(),
24616
+ /** Raw received signal strength in dBm, when the firmware reports one. */
24617
+ rssiDbm: number().optional(),
24618
+ /** Network name of a wireless link, when the firmware reports it. */
24619
+ ssid: string().optional(),
24620
+ /** Ms epoch of the last observation. Lets consumers reason about freshness. */
24621
+ lastUpdated: number()
24622
+ });
24623
+ var networkLinkCapability = {
24624
+ name: "network-link",
24625
+ scope: "device",
24626
+ deviceNative: true,
24627
+ mode: "singleton",
24628
+ deviceTypes: [
24629
+ DeviceType.Camera,
24630
+ DeviceType.Sensor,
24631
+ DeviceType.Button,
24632
+ DeviceType.Switch,
24633
+ DeviceType.Light,
24634
+ DeviceType.Lock,
24635
+ DeviceType.Siren
24636
+ ],
24637
+ methods: {},
24638
+ events: {
24639
+ /**
24640
+ * Emitted whenever the cached status changes (a link switch, a signal
24641
+ * reading that moved). Mirrored on the parent chain by the
24642
+ * DeviceEventPropagator like `battery.onStatusChanged`.
24643
+ */
24644
+ onStatusChanged: { data: object({
24645
+ deviceId: number(),
24646
+ status: NetworkLinkStatusSchema
24647
+ }) } },
24648
+ status: {
24649
+ schema: NetworkLinkStatusSchema,
24650
+ kind: "push",
24651
+ empty: {
24652
+ type: "unknown",
24653
+ signalPercent: null,
24654
+ lastUpdated: 0
24655
+ }
24656
+ },
24657
+ /**
24658
+ * Runtime-state slice — every provider stores the same shape under
24659
+ * `device.runtimeState['network-link']`, read once by the badge and the
24660
+ * Home Assistant projector regardless of the driver.
24661
+ */
24662
+ runtimeState: NetworkLinkStatusSchema,
24663
+ /**
24664
+ * Runtime-state durability: **restored** — a link reading is slow to
24665
+ * change and a sleeping battery camera may not report for hours; the
24666
+ * restored slice is what the badge shows until the next read.
24667
+ *
24668
+ * See `RuntimeStateDurability`. Enforced by
24669
+ * `scripts/check-runtime-state-durability.ts`.
24670
+ */
24671
+ durability: "restored",
24672
+ /** Clock fields: written, but excluded from the compare that decides
24673
+ * whether persisting is worth a SQLite commit. */
24674
+ volatileStateFields: ["lastUpdated"]
24675
+ };
24676
+ /**
24588
24677
  * Generic boolean sensor — last-resort fallback when no domain-
24589
24678
  * specific binary cap fits (Home Assistant `binary_sensor` without a
24590
24679
  * known `device_class`, or a domain we haven't typed yet). Pure
@@ -29702,6 +29791,287 @@ var ptzAutotrackCapability = {
29702
29791
  */
29703
29792
  durability: "session"
29704
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
+ };
29705
30075
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
29706
30076
  kind: "mutation",
29707
30077
  auth: "admin"
@@ -32977,6 +33347,8 @@ var DEVICE_LOCAL_STATE_CAPS = {
32977
33347
  motionTrigger: motionTriggerCapability,
32978
33348
  motionZones: motionZonesCapability,
32979
33349
  nativeObjectDetection: nativeObjectDetectionCapability,
33350
+ navigation: navigationCapability,
33351
+ networkLink: networkLinkCapability,
32980
33352
  notifier: notifierCapability,
32981
33353
  numericSensor: numericSensorCapability,
32982
33354
  petFeeder: petFeederCapability,
@@ -36830,6 +37202,66 @@ Object.freeze({
36830
37202
  addonId: null,
36831
37203
  access: "create"
36832
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
+ },
36833
37265
  "networkAccess.getEndpoint": {
36834
37266
  capName: "network-access",
36835
37267
  capScope: "system",
@@ -40925,6 +41357,56 @@ Object.freeze({
40925
41357
  form: "single",
40926
41358
  optional: false
40927
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
+ }],
40928
41410
  "networkQuality.getDeviceStats": [{
40929
41411
  name: "deviceId",
40930
41412
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-tuya",
3
- "version": "0.2.69",
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",