@camstack/addon-provider-petkit 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
@@ -1101,7 +1101,7 @@ var Nodepetkit = class {
1101
1101
  }
1102
1102
  };
1103
1103
  //#endregion
1104
- //#region ../types/dist/event-category-BZL-fdNj.mjs
1104
+ //#region ../types/dist/event-category-zAv7pMUz.mjs
1105
1105
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
1106
1106
  EventCategory["SystemBoot"] = "system.boot";
1107
1107
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -1473,6 +1473,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
1473
1473
  */
1474
1474
  EventCategory["BatteryOnStatusChanged"] = "battery.onStatusChanged";
1475
1475
  /**
1476
+ * Cap event fired by every device that registers the `network-link`
1477
+ * capability. Mirrors the cap definition's `onStatusChanged`. Carries
1478
+ * `{ deviceId, status: NetworkLinkStatus }` — a link switch or a signal
1479
+ * reading that moved.
1480
+ */
1481
+ EventCategory["NetworkLinkOnStatusChanged"] = "network-link.onStatusChanged";
1482
+ /**
1476
1483
  * Emitted by the battery cap provider WHEN `wakeForStream` enters the
1477
1484
  * "wake in progress" window — between the Baichuan wake-up issue and
1478
1485
  * the camera's first dialed-back RTP packet. The stream-broker
@@ -24868,6 +24875,88 @@ onStatusChanged: { data: object({
24868
24875
  volatileStateFields: ["lastUpdated"]
24869
24876
  };
24870
24877
  /**
24878
+ * Network-link snapshot. Same shape for every provider (a Reolink wifi
24879
+ * camera, a Home Assistant device with a signal-strength sensor, a Tapo
24880
+ * plug): one slice under `device.runtimeState['network-link']`, one badge,
24881
+ * one Home Assistant projection.
24882
+ */
24883
+ var NetworkLinkStatusSchema = object({
24884
+ /** The link the device is on. `'unknown'` = not read yet, not "no link". */
24885
+ type: _enum([
24886
+ "wifi",
24887
+ "ethernet",
24888
+ "cellular",
24889
+ "unknown"
24890
+ ]),
24891
+ /**
24892
+ * Link quality, 0..100 inclusive, normalised by the provider from whatever
24893
+ * the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
24894
+ * KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
24895
+ * one whose reading has not landed must not be drawn at 0 %. Consumers
24896
+ * SKIP a null rather than coerce it.
24897
+ */
24898
+ signalPercent: number().min(0).max(100).nullable(),
24899
+ /** Raw received signal strength in dBm, when the firmware reports one. */
24900
+ rssiDbm: number().optional(),
24901
+ /** Network name of a wireless link, when the firmware reports it. */
24902
+ ssid: string().optional(),
24903
+ /** Ms epoch of the last observation. Lets consumers reason about freshness. */
24904
+ lastUpdated: number()
24905
+ });
24906
+ var networkLinkCapability = {
24907
+ name: "network-link",
24908
+ scope: "device",
24909
+ deviceNative: true,
24910
+ mode: "singleton",
24911
+ deviceTypes: [
24912
+ DeviceType.Camera,
24913
+ DeviceType.Sensor,
24914
+ DeviceType.Button,
24915
+ DeviceType.Switch,
24916
+ DeviceType.Light,
24917
+ DeviceType.Lock,
24918
+ DeviceType.Siren
24919
+ ],
24920
+ methods: {},
24921
+ events: {
24922
+ /**
24923
+ * Emitted whenever the cached status changes (a link switch, a signal
24924
+ * reading that moved). Mirrored on the parent chain by the
24925
+ * DeviceEventPropagator like `battery.onStatusChanged`.
24926
+ */
24927
+ onStatusChanged: { data: object({
24928
+ deviceId: number(),
24929
+ status: NetworkLinkStatusSchema
24930
+ }) } },
24931
+ status: {
24932
+ schema: NetworkLinkStatusSchema,
24933
+ kind: "push",
24934
+ empty: {
24935
+ type: "unknown",
24936
+ signalPercent: null,
24937
+ lastUpdated: 0
24938
+ }
24939
+ },
24940
+ /**
24941
+ * Runtime-state slice — every provider stores the same shape under
24942
+ * `device.runtimeState['network-link']`, read once by the badge and the
24943
+ * Home Assistant projector regardless of the driver.
24944
+ */
24945
+ runtimeState: NetworkLinkStatusSchema,
24946
+ /**
24947
+ * Runtime-state durability: **restored** — a link reading is slow to
24948
+ * change and a sleeping battery camera may not report for hours; the
24949
+ * restored slice is what the badge shows until the next read.
24950
+ *
24951
+ * See `RuntimeStateDurability`. Enforced by
24952
+ * `scripts/check-runtime-state-durability.ts`.
24953
+ */
24954
+ durability: "restored",
24955
+ /** Clock fields: written, but excluded from the compare that decides
24956
+ * whether persisting is worth a SQLite commit. */
24957
+ volatileStateFields: ["lastUpdated"]
24958
+ };
24959
+ /**
24871
24960
  * Generic boolean sensor — last-resort fallback when no domain-
24872
24961
  * specific binary cap fits (Home Assistant `binary_sensor` without a
24873
24962
  * known `device_class`, or a domain we haven't typed yet). Pure
@@ -29985,6 +30074,287 @@ var ptzAutotrackCapability = {
29985
30074
  */
29986
30075
  durability: "session"
29987
30076
  };
30077
+ /**
30078
+ * `navigation` — a device-scoped capability that natively expresses the FULL
30079
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
30080
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
30081
+ *
30082
+ * Why a NEW cap rather than overloading `ptz`:
30083
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
30084
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
30085
+ * The two are different physical models: PTZ is absolute-position + presets,
30086
+ * navigation is momentary drive nudges + discrete robot ACTIONS
30087
+ * (dock / spot-clean / follow-pet / go-to-point / …).
30088
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
30089
+ * the reverse:
30090
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
30091
+ * / `getOptions`), and
30092
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
30093
+ * robot camera shows up in the existing PTZ control path without every
30094
+ * PTZ provider learning about robots. The mapping lives in the adapter,
30095
+ * not here (see the addon design note):
30096
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
30097
+ * ptz.stop() → navigation.stop()
30098
+ * ptz.goHome() → navigation.runAction('goHome')
30099
+ * ptz.getPresets() → navigation.listActions() (id→preset)
30100
+ * ptz.goToPreset(id) → navigation.runAction(id)
30101
+ *
30102
+ * ## Continuous drive
30103
+ *
30104
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
30105
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
30106
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
30107
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
30108
+ * coalesce them. The UI owns the cadence.
30109
+ *
30110
+ * ## The action dictionary
30111
+ *
30112
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
30113
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
30114
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
30115
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
30116
+ * vendor-specific list. `kind: 'action'` entries are triggered with
30117
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
30118
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
30119
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
30120
+ *
30121
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
30122
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
30123
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
30124
+ * every device handle. A future nodedreame publish adds a typed
30125
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
30126
+ * provider can then swap the raw calls for the typed methods with no change to
30127
+ * THIS contract.
30128
+ */
30129
+ /**
30130
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
30131
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
30132
+ * halts it.
30133
+ *
30134
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
30135
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
30136
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
30137
+ * vector by it (drivers without proportional drive ignore it).
30138
+ *
30139
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
30140
+ * axis alone; an all-undefined nudge is a no-op.
30141
+ */
30142
+ var NavigationMoveCommandSchema = object({
30143
+ pan: number().min(-1).max(1).optional(),
30144
+ tilt: number().min(-1).max(1).optional(),
30145
+ speed: number().min(0).max(1).optional()
30146
+ });
30147
+ /**
30148
+ * The enumerated discrete actions a navigation-capable robot can perform via
30149
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
30150
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
30151
+ * `playSound` (see the `sound` dictionary entries).
30152
+ */
30153
+ var NavigationActionIdSchema = _enum([
30154
+ "goHome",
30155
+ "locate",
30156
+ "spotClean",
30157
+ "findPet",
30158
+ "personFollow",
30159
+ "stop",
30160
+ "startClean",
30161
+ "pauseClean",
30162
+ "dockWash",
30163
+ "autoEmpty",
30164
+ "flashOn",
30165
+ "flashOff"
30166
+ ]);
30167
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
30168
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
30169
+ /**
30170
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
30171
+ * native panel and the PTZ mimic render as a button.
30172
+ *
30173
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
30174
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
30175
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
30176
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
30177
+ * - `label` — operator-facing English label.
30178
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
30179
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
30180
+ * PTZ render ONLY enabled entries. Data-driven: the provider
30181
+ * flips it from config, never by editing code.
30182
+ */
30183
+ var NavigationActionEntrySchema = object({
30184
+ id: string(),
30185
+ kind: NavigationEntryKindSchema,
30186
+ label: string(),
30187
+ icon: string(),
30188
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
30189
+ soundId: number().int().optional(),
30190
+ /** Per-device feature flag — render this entry only when true. */
30191
+ enabled: boolean()
30192
+ });
30193
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
30194
+ var NavigationPointSchema = object({
30195
+ x: number(),
30196
+ y: number()
30197
+ });
30198
+ /**
30199
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
30200
+ * The cap reports which are enabled so the UI / PTZ render only the controls
30201
+ * that are turned on for THIS device. Data-driven: the provider derives these
30202
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
30203
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
30204
+ * that are not dictionary entries.
30205
+ *
30206
+ * - `move` / `stop` — the momentary drive joystick.
30207
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
30208
+ * map-coordinate plumbing is wired.
30209
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
30210
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
30211
+ * - `light` — the on/off fill-light toggle (works anytime).
30212
+ * - `lightMode` — the auto/manual selector + manual level slider (a
30213
+ * camera-service control; needs an active stream).
30214
+ */
30215
+ var NavigationFeaturesSchema = object({
30216
+ move: boolean(),
30217
+ stop: boolean(),
30218
+ goToPoint: boolean(),
30219
+ runAction: boolean(),
30220
+ playSound: boolean(),
30221
+ light: boolean(),
30222
+ lightMode: boolean()
30223
+ });
30224
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
30225
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
30226
+ /**
30227
+ * Live navigation state so the UI can reflect what the robot is doing:
30228
+ * - `mode` — coarse activity (idle / cleaning / following / …).
30229
+ * - `following` — person/pet follow is currently armed.
30230
+ * - `flash` — the on-camera fill light is on.
30231
+ * - `lightMode` — auto vs manual fill-light mode.
30232
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
30233
+ * `lightMode === 'manual'`.
30234
+ */
30235
+ var NavigationStatusSchema = object({
30236
+ mode: _enum([
30237
+ "idle",
30238
+ "cleaning",
30239
+ "spot",
30240
+ "following",
30241
+ "goto",
30242
+ "returning",
30243
+ "paused",
30244
+ "unknown"
30245
+ ]),
30246
+ following: boolean(),
30247
+ flash: boolean(),
30248
+ lightMode: NavigationLightModeSchema,
30249
+ lightLevel: number().min(40).max(100),
30250
+ /** Ms epoch when the slice was last updated. */
30251
+ lastChangedAt: number()
30252
+ });
30253
+ /**
30254
+ * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
30255
+ * observable). Adds `lastFetchedAt` on top of the status shape per the
30256
+ * convention.
30257
+ */
30258
+ var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: number() });
30259
+ var navigationCapability = {
30260
+ name: "navigation",
30261
+ scope: "device",
30262
+ deviceNative: true,
30263
+ mode: "singleton",
30264
+ deviceTypes: [DeviceType.Camera],
30265
+ deviceConfig: { ui: {
30266
+ kind: "widget",
30267
+ widgetId: "host/navigation-panel",
30268
+ tab: "navigation",
30269
+ topTab: true,
30270
+ label: "Navigation",
30271
+ order: 0
30272
+ } },
30273
+ methods: {
30274
+ /**
30275
+ * Momentary drive nudge (the robot moves). `protected` — mirrors
30276
+ * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
30277
+ * path) works for any authenticated user, not admin-only. The UI sends
30278
+ * these at ~1 Hz while a control is held; the provider forwards each one to
30279
+ * a single drive write WITHOUT debouncing.
30280
+ */
30281
+ move: method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
30282
+ /** Halt all motion immediately (zero drive vector). */
30283
+ stop: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
30284
+ /** Send the robot to a point on its live map. */
30285
+ goToPoint: method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
30286
+ /**
30287
+ * Enumerate the discrete controls THIS device supports (data-driven UI +
30288
+ * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
30289
+ */
30290
+ listActions: method(object({ deviceId: number() }), array(NavigationActionEntrySchema)),
30291
+ /**
30292
+ * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
30293
+ * unsupported action ids are rejected by the provider.
30294
+ */
30295
+ runAction: method(object({
30296
+ deviceId: number(),
30297
+ actionId: NavigationActionIdSchema
30298
+ }), _void(), { kind: "mutation" }),
30299
+ /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
30300
+ playSound: method(object({
30301
+ deviceId: number(),
30302
+ soundId: number().int()
30303
+ }), _void(), { kind: "mutation" }),
30304
+ /**
30305
+ * Turn the on-camera fill light on / off (the `OpenFullLight` control —
30306
+ * works anytime, no active stream required).
30307
+ */
30308
+ setLightOn: method(object({
30309
+ deviceId: number(),
30310
+ on: boolean()
30311
+ }), _void(), { kind: "mutation" }),
30312
+ /**
30313
+ * Set the fill-light mode (auto vs manual). `manual` optionally carries the
30314
+ * initial `level`. The auto/manual + level control is a CAMERA-service
30315
+ * action that generally needs an active camera stream/monitor session — the
30316
+ * UI shows the manual level slider ONLY when `mode === 'manual'`.
30317
+ */
30318
+ setLightMode: method(object({
30319
+ deviceId: number(),
30320
+ mode: NavigationLightModeSchema,
30321
+ level: number().min(40).max(100).optional()
30322
+ }), _void(), { kind: "mutation" }),
30323
+ /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
30324
+ setLightLevel: method(object({
30325
+ deviceId: number(),
30326
+ level: number().min(40).max(100)
30327
+ }), _void(), { kind: "mutation" }),
30328
+ /**
30329
+ * Per-device FEATURE-FLAG report for the general primitives — drives which
30330
+ * controls the UI shows (the per-entry flags for the dictionary come back on
30331
+ * `listActions`).
30332
+ */
30333
+ getFeatures: method(object({ deviceId: number() }), NavigationFeaturesSchema)
30334
+ },
30335
+ events: { onStatusChanged: { data: object({
30336
+ deviceId: number(),
30337
+ status: NavigationStatusSchema
30338
+ }) } },
30339
+ status: {
30340
+ schema: NavigationStatusSchema,
30341
+ kind: "push"
30342
+ },
30343
+ /**
30344
+ * Runtime-state slice mirrored by the kernel. The navigation panel watches it
30345
+ * for live mode / follow / flash changes.
30346
+ */
30347
+ runtimeState: NavigationRuntimeStateSchema,
30348
+ /**
30349
+ * Runtime-state durability: **session** — like `vacuum-control`, a restored
30350
+ * `mode: cleaning` / `following: true` is a robot that is not actually doing
30351
+ * that. The live handle re-publishes on connect.
30352
+ *
30353
+ * See `RuntimeStateDurability`. Enforced by
30354
+ * `scripts/check-runtime-state-durability.ts`.
30355
+ */
30356
+ durability: "session"
30357
+ };
29988
30358
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
29989
30359
  kind: "mutation",
29990
30360
  auth: "admin"
@@ -33260,6 +33630,8 @@ var DEVICE_LOCAL_STATE_CAPS = {
33260
33630
  motionTrigger: motionTriggerCapability,
33261
33631
  motionZones: motionZonesCapability,
33262
33632
  nativeObjectDetection: nativeObjectDetectionCapability,
33633
+ navigation: navigationCapability,
33634
+ networkLink: networkLinkCapability,
33263
33635
  notifier: notifierCapability,
33264
33636
  numericSensor: numericSensorCapability,
33265
33637
  petFeeder: petFeederCapability,
@@ -37113,6 +37485,66 @@ Object.freeze({
37113
37485
  addonId: null,
37114
37486
  access: "create"
37115
37487
  },
37488
+ "navigation.getFeatures": {
37489
+ capName: "navigation",
37490
+ capScope: "device",
37491
+ addonId: null,
37492
+ access: "view"
37493
+ },
37494
+ "navigation.goToPoint": {
37495
+ capName: "navigation",
37496
+ capScope: "device",
37497
+ addonId: null,
37498
+ access: "create"
37499
+ },
37500
+ "navigation.listActions": {
37501
+ capName: "navigation",
37502
+ capScope: "device",
37503
+ addonId: null,
37504
+ access: "view"
37505
+ },
37506
+ "navigation.move": {
37507
+ capName: "navigation",
37508
+ capScope: "device",
37509
+ addonId: null,
37510
+ access: "create"
37511
+ },
37512
+ "navigation.playSound": {
37513
+ capName: "navigation",
37514
+ capScope: "device",
37515
+ addonId: null,
37516
+ access: "create"
37517
+ },
37518
+ "navigation.runAction": {
37519
+ capName: "navigation",
37520
+ capScope: "device",
37521
+ addonId: null,
37522
+ access: "create"
37523
+ },
37524
+ "navigation.setLightLevel": {
37525
+ capName: "navigation",
37526
+ capScope: "device",
37527
+ addonId: null,
37528
+ access: "create"
37529
+ },
37530
+ "navigation.setLightMode": {
37531
+ capName: "navigation",
37532
+ capScope: "device",
37533
+ addonId: null,
37534
+ access: "create"
37535
+ },
37536
+ "navigation.setLightOn": {
37537
+ capName: "navigation",
37538
+ capScope: "device",
37539
+ addonId: null,
37540
+ access: "create"
37541
+ },
37542
+ "navigation.stop": {
37543
+ capName: "navigation",
37544
+ capScope: "device",
37545
+ addonId: null,
37546
+ access: "create"
37547
+ },
37116
37548
  "networkAccess.getEndpoint": {
37117
37549
  capName: "network-access",
37118
37550
  capScope: "system",
@@ -41208,6 +41640,56 @@ Object.freeze({
41208
41640
  form: "single",
41209
41641
  optional: false
41210
41642
  }],
41643
+ "navigation.getFeatures": [{
41644
+ name: "deviceId",
41645
+ form: "single",
41646
+ optional: false
41647
+ }],
41648
+ "navigation.goToPoint": [{
41649
+ name: "deviceId",
41650
+ form: "single",
41651
+ optional: false
41652
+ }],
41653
+ "navigation.listActions": [{
41654
+ name: "deviceId",
41655
+ form: "single",
41656
+ optional: false
41657
+ }],
41658
+ "navigation.move": [{
41659
+ name: "deviceId",
41660
+ form: "single",
41661
+ optional: false
41662
+ }],
41663
+ "navigation.playSound": [{
41664
+ name: "deviceId",
41665
+ form: "single",
41666
+ optional: false
41667
+ }],
41668
+ "navigation.runAction": [{
41669
+ name: "deviceId",
41670
+ form: "single",
41671
+ optional: false
41672
+ }],
41673
+ "navigation.setLightLevel": [{
41674
+ name: "deviceId",
41675
+ form: "single",
41676
+ optional: false
41677
+ }],
41678
+ "navigation.setLightMode": [{
41679
+ name: "deviceId",
41680
+ form: "single",
41681
+ optional: false
41682
+ }],
41683
+ "navigation.setLightOn": [{
41684
+ name: "deviceId",
41685
+ form: "single",
41686
+ optional: false
41687
+ }],
41688
+ "navigation.stop": [{
41689
+ name: "deviceId",
41690
+ form: "single",
41691
+ optional: false
41692
+ }],
41211
41693
  "networkQuality.getDeviceStats": [{
41212
41694
  name: "deviceId",
41213
41695
  form: "single",
package/dist/addon.mjs CHANGED
@@ -1100,7 +1100,7 @@ var Nodepetkit = class {
1100
1100
  }
1101
1101
  };
1102
1102
  //#endregion
1103
- //#region ../types/dist/event-category-BZL-fdNj.mjs
1103
+ //#region ../types/dist/event-category-zAv7pMUz.mjs
1104
1104
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
1105
1105
  EventCategory["SystemBoot"] = "system.boot";
1106
1106
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -1472,6 +1472,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
1472
1472
  */
1473
1473
  EventCategory["BatteryOnStatusChanged"] = "battery.onStatusChanged";
1474
1474
  /**
1475
+ * Cap event fired by every device that registers the `network-link`
1476
+ * capability. Mirrors the cap definition's `onStatusChanged`. Carries
1477
+ * `{ deviceId, status: NetworkLinkStatus }` — a link switch or a signal
1478
+ * reading that moved.
1479
+ */
1480
+ EventCategory["NetworkLinkOnStatusChanged"] = "network-link.onStatusChanged";
1481
+ /**
1475
1482
  * Emitted by the battery cap provider WHEN `wakeForStream` enters the
1476
1483
  * "wake in progress" window — between the Baichuan wake-up issue and
1477
1484
  * the camera's first dialed-back RTP packet. The stream-broker
@@ -24867,6 +24874,88 @@ onStatusChanged: { data: object({
24867
24874
  volatileStateFields: ["lastUpdated"]
24868
24875
  };
24869
24876
  /**
24877
+ * Network-link snapshot. Same shape for every provider (a Reolink wifi
24878
+ * camera, a Home Assistant device with a signal-strength sensor, a Tapo
24879
+ * plug): one slice under `device.runtimeState['network-link']`, one badge,
24880
+ * one Home Assistant projection.
24881
+ */
24882
+ var NetworkLinkStatusSchema = object({
24883
+ /** The link the device is on. `'unknown'` = not read yet, not "no link". */
24884
+ type: _enum([
24885
+ "wifi",
24886
+ "ethernet",
24887
+ "cellular",
24888
+ "unknown"
24889
+ ]),
24890
+ /**
24891
+ * Link quality, 0..100 inclusive, normalised by the provider from whatever
24892
+ * the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
24893
+ * KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
24894
+ * one whose reading has not landed must not be drawn at 0 %. Consumers
24895
+ * SKIP a null rather than coerce it.
24896
+ */
24897
+ signalPercent: number().min(0).max(100).nullable(),
24898
+ /** Raw received signal strength in dBm, when the firmware reports one. */
24899
+ rssiDbm: number().optional(),
24900
+ /** Network name of a wireless link, when the firmware reports it. */
24901
+ ssid: string().optional(),
24902
+ /** Ms epoch of the last observation. Lets consumers reason about freshness. */
24903
+ lastUpdated: number()
24904
+ });
24905
+ var networkLinkCapability = {
24906
+ name: "network-link",
24907
+ scope: "device",
24908
+ deviceNative: true,
24909
+ mode: "singleton",
24910
+ deviceTypes: [
24911
+ DeviceType.Camera,
24912
+ DeviceType.Sensor,
24913
+ DeviceType.Button,
24914
+ DeviceType.Switch,
24915
+ DeviceType.Light,
24916
+ DeviceType.Lock,
24917
+ DeviceType.Siren
24918
+ ],
24919
+ methods: {},
24920
+ events: {
24921
+ /**
24922
+ * Emitted whenever the cached status changes (a link switch, a signal
24923
+ * reading that moved). Mirrored on the parent chain by the
24924
+ * DeviceEventPropagator like `battery.onStatusChanged`.
24925
+ */
24926
+ onStatusChanged: { data: object({
24927
+ deviceId: number(),
24928
+ status: NetworkLinkStatusSchema
24929
+ }) } },
24930
+ status: {
24931
+ schema: NetworkLinkStatusSchema,
24932
+ kind: "push",
24933
+ empty: {
24934
+ type: "unknown",
24935
+ signalPercent: null,
24936
+ lastUpdated: 0
24937
+ }
24938
+ },
24939
+ /**
24940
+ * Runtime-state slice — every provider stores the same shape under
24941
+ * `device.runtimeState['network-link']`, read once by the badge and the
24942
+ * Home Assistant projector regardless of the driver.
24943
+ */
24944
+ runtimeState: NetworkLinkStatusSchema,
24945
+ /**
24946
+ * Runtime-state durability: **restored** — a link reading is slow to
24947
+ * change and a sleeping battery camera may not report for hours; the
24948
+ * restored slice is what the badge shows until the next read.
24949
+ *
24950
+ * See `RuntimeStateDurability`. Enforced by
24951
+ * `scripts/check-runtime-state-durability.ts`.
24952
+ */
24953
+ durability: "restored",
24954
+ /** Clock fields: written, but excluded from the compare that decides
24955
+ * whether persisting is worth a SQLite commit. */
24956
+ volatileStateFields: ["lastUpdated"]
24957
+ };
24958
+ /**
24870
24959
  * Generic boolean sensor — last-resort fallback when no domain-
24871
24960
  * specific binary cap fits (Home Assistant `binary_sensor` without a
24872
24961
  * known `device_class`, or a domain we haven't typed yet). Pure
@@ -29984,6 +30073,287 @@ var ptzAutotrackCapability = {
29984
30073
  */
29985
30074
  durability: "session"
29986
30075
  };
30076
+ /**
30077
+ * `navigation` — a device-scoped capability that natively expresses the FULL
30078
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
30079
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
30080
+ *
30081
+ * Why a NEW cap rather than overloading `ptz`:
30082
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
30083
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
30084
+ * The two are different physical models: PTZ is absolute-position + presets,
30085
+ * navigation is momentary drive nudges + discrete robot ACTIONS
30086
+ * (dock / spot-clean / follow-pet / go-to-point / …).
30087
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
30088
+ * the reverse:
30089
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
30090
+ * / `getOptions`), and
30091
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
30092
+ * robot camera shows up in the existing PTZ control path without every
30093
+ * PTZ provider learning about robots. The mapping lives in the adapter,
30094
+ * not here (see the addon design note):
30095
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
30096
+ * ptz.stop() → navigation.stop()
30097
+ * ptz.goHome() → navigation.runAction('goHome')
30098
+ * ptz.getPresets() → navigation.listActions() (id→preset)
30099
+ * ptz.goToPreset(id) → navigation.runAction(id)
30100
+ *
30101
+ * ## Continuous drive
30102
+ *
30103
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
30104
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
30105
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
30106
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
30107
+ * coalesce them. The UI owns the cadence.
30108
+ *
30109
+ * ## The action dictionary
30110
+ *
30111
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
30112
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
30113
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
30114
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
30115
+ * vendor-specific list. `kind: 'action'` entries are triggered with
30116
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
30117
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
30118
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
30119
+ *
30120
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
30121
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
30122
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
30123
+ * every device handle. A future nodedreame publish adds a typed
30124
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
30125
+ * provider can then swap the raw calls for the typed methods with no change to
30126
+ * THIS contract.
30127
+ */
30128
+ /**
30129
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
30130
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
30131
+ * halts it.
30132
+ *
30133
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
30134
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
30135
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
30136
+ * vector by it (drivers without proportional drive ignore it).
30137
+ *
30138
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
30139
+ * axis alone; an all-undefined nudge is a no-op.
30140
+ */
30141
+ var NavigationMoveCommandSchema = object({
30142
+ pan: number().min(-1).max(1).optional(),
30143
+ tilt: number().min(-1).max(1).optional(),
30144
+ speed: number().min(0).max(1).optional()
30145
+ });
30146
+ /**
30147
+ * The enumerated discrete actions a navigation-capable robot can perform via
30148
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
30149
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
30150
+ * `playSound` (see the `sound` dictionary entries).
30151
+ */
30152
+ var NavigationActionIdSchema = _enum([
30153
+ "goHome",
30154
+ "locate",
30155
+ "spotClean",
30156
+ "findPet",
30157
+ "personFollow",
30158
+ "stop",
30159
+ "startClean",
30160
+ "pauseClean",
30161
+ "dockWash",
30162
+ "autoEmpty",
30163
+ "flashOn",
30164
+ "flashOff"
30165
+ ]);
30166
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
30167
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
30168
+ /**
30169
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
30170
+ * native panel and the PTZ mimic render as a button.
30171
+ *
30172
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
30173
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
30174
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
30175
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
30176
+ * - `label` — operator-facing English label.
30177
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
30178
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
30179
+ * PTZ render ONLY enabled entries. Data-driven: the provider
30180
+ * flips it from config, never by editing code.
30181
+ */
30182
+ var NavigationActionEntrySchema = object({
30183
+ id: string(),
30184
+ kind: NavigationEntryKindSchema,
30185
+ label: string(),
30186
+ icon: string(),
30187
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
30188
+ soundId: number().int().optional(),
30189
+ /** Per-device feature flag — render this entry only when true. */
30190
+ enabled: boolean()
30191
+ });
30192
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
30193
+ var NavigationPointSchema = object({
30194
+ x: number(),
30195
+ y: number()
30196
+ });
30197
+ /**
30198
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
30199
+ * The cap reports which are enabled so the UI / PTZ render only the controls
30200
+ * that are turned on for THIS device. Data-driven: the provider derives these
30201
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
30202
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
30203
+ * that are not dictionary entries.
30204
+ *
30205
+ * - `move` / `stop` — the momentary drive joystick.
30206
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
30207
+ * map-coordinate plumbing is wired.
30208
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
30209
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
30210
+ * - `light` — the on/off fill-light toggle (works anytime).
30211
+ * - `lightMode` — the auto/manual selector + manual level slider (a
30212
+ * camera-service control; needs an active stream).
30213
+ */
30214
+ var NavigationFeaturesSchema = object({
30215
+ move: boolean(),
30216
+ stop: boolean(),
30217
+ goToPoint: boolean(),
30218
+ runAction: boolean(),
30219
+ playSound: boolean(),
30220
+ light: boolean(),
30221
+ lightMode: boolean()
30222
+ });
30223
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
30224
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
30225
+ /**
30226
+ * Live navigation state so the UI can reflect what the robot is doing:
30227
+ * - `mode` — coarse activity (idle / cleaning / following / …).
30228
+ * - `following` — person/pet follow is currently armed.
30229
+ * - `flash` — the on-camera fill light is on.
30230
+ * - `lightMode` — auto vs manual fill-light mode.
30231
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
30232
+ * `lightMode === 'manual'`.
30233
+ */
30234
+ var NavigationStatusSchema = object({
30235
+ mode: _enum([
30236
+ "idle",
30237
+ "cleaning",
30238
+ "spot",
30239
+ "following",
30240
+ "goto",
30241
+ "returning",
30242
+ "paused",
30243
+ "unknown"
30244
+ ]),
30245
+ following: boolean(),
30246
+ flash: boolean(),
30247
+ lightMode: NavigationLightModeSchema,
30248
+ lightLevel: number().min(40).max(100),
30249
+ /** Ms epoch when the slice was last updated. */
30250
+ lastChangedAt: number()
30251
+ });
30252
+ /**
30253
+ * Runtime-state slice owned by this cap (kernel-managed: validated, mirrored,
30254
+ * observable). Adds `lastFetchedAt` on top of the status shape per the
30255
+ * convention.
30256
+ */
30257
+ var NavigationRuntimeStateSchema = NavigationStatusSchema.extend({ lastFetchedAt: number() });
30258
+ var navigationCapability = {
30259
+ name: "navigation",
30260
+ scope: "device",
30261
+ deviceNative: true,
30262
+ mode: "singleton",
30263
+ deviceTypes: [DeviceType.Camera],
30264
+ deviceConfig: { ui: {
30265
+ kind: "widget",
30266
+ widgetId: "host/navigation-panel",
30267
+ tab: "navigation",
30268
+ topTab: true,
30269
+ label: "Navigation",
30270
+ order: 0
30271
+ } },
30272
+ methods: {
30273
+ /**
30274
+ * Momentary drive nudge (the robot moves). `protected` — mirrors
30275
+ * `ptz.continuousMove` so the Viewer navigation panel (and the PTZ-mimic
30276
+ * path) works for any authenticated user, not admin-only. The UI sends
30277
+ * these at ~1 Hz while a control is held; the provider forwards each one to
30278
+ * a single drive write WITHOUT debouncing.
30279
+ */
30280
+ move: method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
30281
+ /** Halt all motion immediately (zero drive vector). */
30282
+ stop: method(object({ deviceId: number() }), _void(), { kind: "mutation" }),
30283
+ /** Send the robot to a point on its live map. */
30284
+ goToPoint: method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }),
30285
+ /**
30286
+ * Enumerate the discrete controls THIS device supports (data-driven UI +
30287
+ * PTZ mimic). Camera-probed subset of {@link NAVIGATION_ACTION_CATALOG}.
30288
+ */
30289
+ listActions: method(object({ deviceId: number() }), array(NavigationActionEntrySchema)),
30290
+ /**
30291
+ * Run one discrete action (a `kind:'action'` dictionary entry). Invalid /
30292
+ * unsupported action ids are rejected by the provider.
30293
+ */
30294
+ runAction: method(object({
30295
+ deviceId: number(),
30296
+ actionId: NavigationActionIdSchema
30297
+ }), _void(), { kind: "mutation" }),
30298
+ /** Play a sound by its wire id (the `soundId` of a `kind:'sound'` entry). */
30299
+ playSound: method(object({
30300
+ deviceId: number(),
30301
+ soundId: number().int()
30302
+ }), _void(), { kind: "mutation" }),
30303
+ /**
30304
+ * Turn the on-camera fill light on / off (the `OpenFullLight` control —
30305
+ * works anytime, no active stream required).
30306
+ */
30307
+ setLightOn: method(object({
30308
+ deviceId: number(),
30309
+ on: boolean()
30310
+ }), _void(), { kind: "mutation" }),
30311
+ /**
30312
+ * Set the fill-light mode (auto vs manual). `manual` optionally carries the
30313
+ * initial `level`. The auto/manual + level control is a CAMERA-service
30314
+ * action that generally needs an active camera stream/monitor session — the
30315
+ * UI shows the manual level slider ONLY when `mode === 'manual'`.
30316
+ */
30317
+ setLightMode: method(object({
30318
+ deviceId: number(),
30319
+ mode: NavigationLightModeSchema,
30320
+ level: number().min(40).max(100).optional()
30321
+ }), _void(), { kind: "mutation" }),
30322
+ /** Set the MANUAL fill-light level (40..100). Implies `manual` mode. */
30323
+ setLightLevel: method(object({
30324
+ deviceId: number(),
30325
+ level: number().min(40).max(100)
30326
+ }), _void(), { kind: "mutation" }),
30327
+ /**
30328
+ * Per-device FEATURE-FLAG report for the general primitives — drives which
30329
+ * controls the UI shows (the per-entry flags for the dictionary come back on
30330
+ * `listActions`).
30331
+ */
30332
+ getFeatures: method(object({ deviceId: number() }), NavigationFeaturesSchema)
30333
+ },
30334
+ events: { onStatusChanged: { data: object({
30335
+ deviceId: number(),
30336
+ status: NavigationStatusSchema
30337
+ }) } },
30338
+ status: {
30339
+ schema: NavigationStatusSchema,
30340
+ kind: "push"
30341
+ },
30342
+ /**
30343
+ * Runtime-state slice mirrored by the kernel. The navigation panel watches it
30344
+ * for live mode / follow / flash changes.
30345
+ */
30346
+ runtimeState: NavigationRuntimeStateSchema,
30347
+ /**
30348
+ * Runtime-state durability: **session** — like `vacuum-control`, a restored
30349
+ * `mode: cleaning` / `following: true` is a robot that is not actually doing
30350
+ * that. The live handle re-publishes on connect.
30351
+ *
30352
+ * See `RuntimeStateDurability`. Enforced by
30353
+ * `scripts/check-runtime-state-durability.ts`.
30354
+ */
30355
+ durability: "session"
30356
+ };
29987
30357
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
29988
30358
  kind: "mutation",
29989
30359
  auth: "admin"
@@ -33259,6 +33629,8 @@ var DEVICE_LOCAL_STATE_CAPS = {
33259
33629
  motionTrigger: motionTriggerCapability,
33260
33630
  motionZones: motionZonesCapability,
33261
33631
  nativeObjectDetection: nativeObjectDetectionCapability,
33632
+ navigation: navigationCapability,
33633
+ networkLink: networkLinkCapability,
33262
33634
  notifier: notifierCapability,
33263
33635
  numericSensor: numericSensorCapability,
33264
33636
  petFeeder: petFeederCapability,
@@ -37112,6 +37484,66 @@ Object.freeze({
37112
37484
  addonId: null,
37113
37485
  access: "create"
37114
37486
  },
37487
+ "navigation.getFeatures": {
37488
+ capName: "navigation",
37489
+ capScope: "device",
37490
+ addonId: null,
37491
+ access: "view"
37492
+ },
37493
+ "navigation.goToPoint": {
37494
+ capName: "navigation",
37495
+ capScope: "device",
37496
+ addonId: null,
37497
+ access: "create"
37498
+ },
37499
+ "navigation.listActions": {
37500
+ capName: "navigation",
37501
+ capScope: "device",
37502
+ addonId: null,
37503
+ access: "view"
37504
+ },
37505
+ "navigation.move": {
37506
+ capName: "navigation",
37507
+ capScope: "device",
37508
+ addonId: null,
37509
+ access: "create"
37510
+ },
37511
+ "navigation.playSound": {
37512
+ capName: "navigation",
37513
+ capScope: "device",
37514
+ addonId: null,
37515
+ access: "create"
37516
+ },
37517
+ "navigation.runAction": {
37518
+ capName: "navigation",
37519
+ capScope: "device",
37520
+ addonId: null,
37521
+ access: "create"
37522
+ },
37523
+ "navigation.setLightLevel": {
37524
+ capName: "navigation",
37525
+ capScope: "device",
37526
+ addonId: null,
37527
+ access: "create"
37528
+ },
37529
+ "navigation.setLightMode": {
37530
+ capName: "navigation",
37531
+ capScope: "device",
37532
+ addonId: null,
37533
+ access: "create"
37534
+ },
37535
+ "navigation.setLightOn": {
37536
+ capName: "navigation",
37537
+ capScope: "device",
37538
+ addonId: null,
37539
+ access: "create"
37540
+ },
37541
+ "navigation.stop": {
37542
+ capName: "navigation",
37543
+ capScope: "device",
37544
+ addonId: null,
37545
+ access: "create"
37546
+ },
37115
37547
  "networkAccess.getEndpoint": {
37116
37548
  capName: "network-access",
37117
37549
  capScope: "system",
@@ -41207,6 +41639,56 @@ Object.freeze({
41207
41639
  form: "single",
41208
41640
  optional: false
41209
41641
  }],
41642
+ "navigation.getFeatures": [{
41643
+ name: "deviceId",
41644
+ form: "single",
41645
+ optional: false
41646
+ }],
41647
+ "navigation.goToPoint": [{
41648
+ name: "deviceId",
41649
+ form: "single",
41650
+ optional: false
41651
+ }],
41652
+ "navigation.listActions": [{
41653
+ name: "deviceId",
41654
+ form: "single",
41655
+ optional: false
41656
+ }],
41657
+ "navigation.move": [{
41658
+ name: "deviceId",
41659
+ form: "single",
41660
+ optional: false
41661
+ }],
41662
+ "navigation.playSound": [{
41663
+ name: "deviceId",
41664
+ form: "single",
41665
+ optional: false
41666
+ }],
41667
+ "navigation.runAction": [{
41668
+ name: "deviceId",
41669
+ form: "single",
41670
+ optional: false
41671
+ }],
41672
+ "navigation.setLightLevel": [{
41673
+ name: "deviceId",
41674
+ form: "single",
41675
+ optional: false
41676
+ }],
41677
+ "navigation.setLightMode": [{
41678
+ name: "deviceId",
41679
+ form: "single",
41680
+ optional: false
41681
+ }],
41682
+ "navigation.setLightOn": [{
41683
+ name: "deviceId",
41684
+ form: "single",
41685
+ optional: false
41686
+ }],
41687
+ "navigation.stop": [{
41688
+ name: "deviceId",
41689
+ form: "single",
41690
+ optional: false
41691
+ }],
41210
41692
  "networkQuality.getDeviceStats": [{
41211
41693
  name: "deviceId",
41212
41694
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-petkit",
3
- "version": "0.2.69",
3
+ "version": "0.2.71",
4
4
  "description": "PetKit smart-feeder device-provider addon for CamStack — wraps the @apocaliss92/nodepetkit PetKit cloud client",
5
5
  "keywords": [
6
6
  "camstack",