@mega-yfue/eufy-sdk 0.0.4 → 0.1.0-beta.0

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.
Files changed (143) hide show
  1. package/README.md +31 -12
  2. package/dist/client/device-registry.d.ts +343 -0
  3. package/dist/client/eufy-mega.d.ts +872 -0
  4. package/dist/client/index.d.ts +1 -6
  5. package/dist/client/map-channels.d.ts +22 -0
  6. package/dist/client/types.d.ts +383 -0
  7. package/dist/core/contracts.d.ts +898 -0
  8. package/dist/core/crypto.d.ts +98 -0
  9. package/dist/core/index.d.ts +9 -7
  10. package/dist/core/logger.d.ts +53 -0
  11. package/dist/core/lz4-block.d.ts +35 -0
  12. package/dist/core/raw-dp-hex.d.ts +32 -0
  13. package/dist/core/raw-dp-writer.d.ts +83 -0
  14. package/dist/core/store.d.ts +43 -0
  15. package/dist/core/types.d.ts +169 -0
  16. package/dist/core/util.d.ts +78 -0
  17. package/dist/index.d.ts +4 -7
  18. package/dist/index.js +25526 -14
  19. package/dist/index.js.map +7 -1
  20. package/dist/model/capabilities/access.d.ts +127 -0
  21. package/dist/model/capabilities/arming.d.ts +201 -0
  22. package/dist/model/capabilities/audio.d.ts +154 -0
  23. package/dist/model/capabilities/battery.d.ts +363 -0
  24. package/dist/model/capabilities/camera.d.ts +564 -0
  25. package/dist/model/capabilities/co.d.ts +40 -0
  26. package/dist/model/capabilities/contact.d.ts +133 -0
  27. package/dist/model/capabilities/doorbell.d.ts +345 -0
  28. package/dist/model/capabilities/dp-catalog.d.ts +38 -0
  29. package/dist/model/capabilities/index.d.ts +561 -0
  30. package/dist/model/capabilities/info.d.ts +28 -0
  31. package/dist/model/capabilities/keypad.d.ts +61 -0
  32. package/dist/model/capabilities/leak.d.ts +43 -0
  33. package/dist/model/capabilities/light.d.ts +174 -0
  34. package/dist/model/capabilities/locate.d.ts +63 -0
  35. package/dist/model/capabilities/lock.d.ts +242 -0
  36. package/dist/model/capabilities/manifest.d.ts +107 -0
  37. package/dist/model/capabilities/members.d.ts +647 -0
  38. package/dist/model/capabilities/motion.d.ts +377 -0
  39. package/dist/model/capabilities/person-detection.d.ts +8 -0
  40. package/dist/model/capabilities/ptz.d.ts +289 -0
  41. package/dist/model/capabilities/rtsp.d.ts +221 -0
  42. package/dist/model/capabilities/siren.d.ts +218 -0
  43. package/dist/model/capabilities/smart-light.d.ts +172 -0
  44. package/dist/model/capabilities/smoke.d.ts +40 -0
  45. package/dist/model/capabilities/snapshot.d.ts +6 -0
  46. package/dist/model/capabilities/storage.d.ts +11 -0
  47. package/dist/model/capabilities/suction.d.ts +104 -0
  48. package/dist/model/capabilities/types.d.ts +484 -0
  49. package/dist/model/capabilities/vacuum-clean.d.ts +1946 -0
  50. package/dist/model/capabilities/vacuum-dock.d.ts +208 -0
  51. package/dist/model/capabilities/video.d.ts +6 -0
  52. package/dist/model/classify.d.ts +78 -0
  53. package/dist/model/clean-record-detail.d.ts +65 -0
  54. package/dist/model/clean-records.d.ts +69 -0
  55. package/dist/model/device-family.d.ts +73 -0
  56. package/dist/model/device-types.d.ts +123 -0
  57. package/dist/model/device.d.ts +265 -0
  58. package/dist/model/index.d.ts +29 -4
  59. package/dist/model/infer.d.ts +23 -0
  60. package/dist/model/inspect.d.ts +61 -0
  61. package/dist/model/life-params.d.ts +21 -0
  62. package/dist/model/map-pixels.d.ts +70 -0
  63. package/dist/model/param-dictionary.d.ts +28 -0
  64. package/dist/model/param-namespace.d.ts +21 -0
  65. package/dist/model/proto-read.d.ts +53 -0
  66. package/dist/model/push-events.d.ts +147 -0
  67. package/dist/model/registry.d.ts +54 -0
  68. package/dist/model/types.d.ts +301 -0
  69. package/dist/model/vacuum-map-store.d.ts +92 -0
  70. package/dist/model/vacuum-map.d.ts +286 -0
  71. package/dist/model/vacuum-scenes.d.ts +76 -0
  72. package/dist/model/vacuum-schedules.d.ts +85 -0
  73. package/dist/transport/dp-preset.d.ts +102 -0
  74. package/dist/transport/ff09.d.ts +444 -0
  75. package/dist/transport/ffmpeg.d.ts +86 -0
  76. package/dist/transport/http/decodeImageV1.d.ts +20 -0
  77. package/dist/transport/http/decodeImageV2.d.ts +19 -0
  78. package/dist/transport/http/index.d.ts +5 -0
  79. package/dist/transport/http/light-catalog.d.ts +62 -0
  80. package/dist/transport/http/media-download.d.ts +14 -0
  81. package/dist/transport/http/mega-client.d.ts +514 -0
  82. package/dist/transport/http/phone-model.d.ts +21 -0
  83. package/dist/transport/index.d.ts +10 -7
  84. package/dist/transport/mqtt/app-client-id.d.ts +16 -0
  85. package/dist/transport/mqtt/availability.d.ts +14 -0
  86. package/dist/transport/mqtt/bare-ip-tls.d.ts +46 -0
  87. package/dist/transport/mqtt/biz-stream.d.ts +98 -0
  88. package/dist/transport/mqtt/broker-discovery.d.ts +55 -0
  89. package/dist/transport/mqtt/clean-codec.d.ts +14 -0
  90. package/dist/transport/mqtt/command-router.d.ts +285 -0
  91. package/dist/transport/mqtt/dp-codec.d.ts +58 -0
  92. package/dist/transport/mqtt/dp-color.d.ts +14 -0
  93. package/dist/transport/mqtt/engine.d.ts +16 -0
  94. package/dist/transport/mqtt/index.d.ts +5 -0
  95. package/dist/transport/mqtt/secure-mqtt.d.ts +107 -0
  96. package/dist/transport/mqtt/topics.d.ts +80 -0
  97. package/dist/transport/p2p/adts.d.ts +91 -0
  98. package/dist/transport/p2p/annexb.d.ts +124 -0
  99. package/dist/transport/p2p/codec.d.ts +166 -0
  100. package/dist/transport/p2p/command-router.d.ts +655 -0
  101. package/dist/transport/p2p/commands.d.ts +550 -0
  102. package/dist/transport/p2p/envelope.d.ts +46 -0
  103. package/dist/transport/p2p/fmp4.d.ts +89 -0
  104. package/dist/transport/p2p/fragment-recording.d.ts +33 -0
  105. package/dist/transport/p2p/index.d.ts +13 -0
  106. package/dist/transport/p2p/lan-ip.d.ts +25 -0
  107. package/dist/transport/p2p/live-stream.d.ts +223 -0
  108. package/dist/transport/p2p/live-trace.d.ts +123 -0
  109. package/dist/transport/p2p/media.d.ts +105 -0
  110. package/dist/transport/p2p/p2p-session.d.ts +620 -0
  111. package/dist/transport/p2p/readable-egress.d.ts +27 -0
  112. package/dist/transport/p2p/session-manager.d.ts +154 -0
  113. package/dist/transport/p2p/shared-live-source.d.ts +431 -0
  114. package/dist/transport/p2p/talkback.d.ts +187 -0
  115. package/dist/transport/p2p/video.d.ts +150 -0
  116. package/dist/transport/p2p/write-commands.d.ts +21 -0
  117. package/dist/transport/protobuf.d.ts +5 -0
  118. package/dist/transport/push/fcm.d.ts +23 -0
  119. package/dist/transport/push/index.d.ts +6 -0
  120. package/dist/transport/push/message-tags.d.ts +26 -0
  121. package/dist/transport/push/parser.d.ts +27 -0
  122. package/dist/transport/push/proto.d.ts +11 -0
  123. package/dist/transport/push/push-client.d.ts +64 -0
  124. package/dist/transport/push/store.d.ts +23 -0
  125. package/dist/transport/push/types.d.ts +180 -0
  126. package/dist/transport/raw-dp.d.ts +6 -0
  127. package/dist/transport/stored-image-cache.d.ts +23 -0
  128. package/dist/transport/tuya/account.d.ts +44 -0
  129. package/dist/transport/tuya/client.d.ts +89 -0
  130. package/dist/transport/tuya/command-router.d.ts +79 -0
  131. package/dist/transport/tuya/dp-codec.d.ts +56 -0
  132. package/dist/transport/tuya/index.d.ts +28 -0
  133. package/dist/transport/tuya/request.d.ts +157 -0
  134. package/dist/transport/tuya/sign.d.ts +64 -0
  135. package/package.json +12 -13
  136. package/dist/client/index.js +0 -2
  137. package/dist/client/index.js.map +0 -1
  138. package/dist/core/index.js +0 -2
  139. package/dist/core/index.js.map +0 -1
  140. package/dist/model/index.js +0 -2
  141. package/dist/model/index.js.map +0 -1
  142. package/dist/transport/index.js +0 -2
  143. package/dist/transport/index.js.map +0 -1
@@ -0,0 +1,43 @@
1
+ import { type Surface } from "./members.js";
2
+ import type { CapabilityModule } from "./types.js";
3
+ /**
4
+ * Every `leak` feature, declared once — the property schema and the evidence-gated getters derive from here.
5
+ *
6
+ * Exported but NOT published: each entry states its wire id and the evidence it was confirmed on,
7
+ * which the reference site does not carry.
8
+ * @internal
9
+ */
10
+ export declare const LEAK_MEMBERS: {
11
+ /**
12
+ * The alarm flag, modelled as state even though the sensor announces a leak as a PUSH EVENT rather
13
+ * than by holding a param — so 1560 is a `guessed` placeholder and the evidence gate will normally
14
+ * leave this getter uninstalled. The push event is the reliable channel for a leak; this read is
15
+ * here so the flag has a home once a capture pins a real id.
16
+ */
17
+ readonly leakDetected: {
18
+ readonly param: 1560;
19
+ readonly type: "bool";
20
+ readonly kind: "boolean";
21
+ readonly provenance: "guessed";
22
+ readonly description: string;
23
+ };
24
+ /**
25
+ * Unix seconds at which the sensor last checked in — param 1551, the same last-seen id the other
26
+ * sensor capabilities read. A `timestamp` kind takes no `unit`: the number is an instant, not a
27
+ * duration, and declaring `unit: "s"` beside it fails the value-kind spec.
28
+ */
29
+ readonly lastSeen: {
30
+ readonly param: 1551;
31
+ readonly type: "number";
32
+ readonly kind: "timestamp";
33
+ readonly provenance: "verified";
34
+ readonly description: "Last-seen unix timestamp, seconds (verified: param 1551).";
35
+ };
36
+ };
37
+ /** Bound leak-sensor reads — the object returned by `dev.leak()`. Read-only. */
38
+ export type LeakActions = Surface<typeof LEAK_MEMBERS>;
39
+ /**
40
+ * `leak` — water/leak (and freeze) sensor. Alarm flag is a placeholder — the T8920 Water & Freeze
41
+ * sensor reports leak via a push event, not a stable param id.
42
+ */
43
+ export declare const LEAK: CapabilityModule;
@@ -0,0 +1,174 @@
1
+ import type { CapabilityModule, CommandContext } from "./types.js";
2
+ import { type Surface } from "./members.js";
3
+ import type { Command } from "../../core/contracts.js";
4
+ /**
5
+ * The P2P **feature-command ids** this light capability drives. Capability-owned wire vocabulary —
6
+ * the transport forwards `cmd.param` opaquely and never names them (full id→name catalog in the
7
+ * generated `transport/p2p/commands.ts`).
8
+ */
9
+ export declare const LIGHT_CMD: {
10
+ /** Spotlight momentary on/off; `{time,type,value}` under 1700. ✅ Verified via P2P state readback (3/3). */
11
+ readonly FLOODLIGHT_SWITCH: 1400;
12
+ /**
13
+ * Spotlight brightness 1..100. ✅ SOLVED & verified live (T8425 + T8124): direct-binary command,
14
+ * 136-byte struct `[u32 ch][u32 value][account_id pad→128]` (value = raw 1..100), NOT a JSON wrapper.
15
+ */
16
+ readonly SPOTLIGHT_BRIGHTNESS: 1401;
17
+ /** Spotlight master enable — distinct from the on/off switch. ✅ Verified live T8124 (same 136-byte direct-binary struct as brightness). */
18
+ readonly SPOTLIGHT_ENABLE: 1403;
19
+ /** Spotlight color temperature 0=warm..100=cool. ✅ Verified live T8124 (only tunable-white spotlights respond; T8425 is white-only). */
20
+ readonly SPOTLIGHT_COLOR_TEMP: 1410;
21
+ /**
22
+ * Auto-spotlight = "motion-activated light" — light up when motion fires. ✅ Wire reversed from a live
23
+ * OUTBOUND P2P capture + replay-verified on T8425 (ch3, 2026-07-13, ON+OFF): the real command
24
+ * is **cmd 1422**, carried in a `1350` SET_PAYLOAD envelope on the DEVICE channel (mChannel = device
25
+ * channel, mValue3 0), with a COMPOSITE payload — NOT a bare bool:
26
+ * `{brightness:0..100, enable:0|1, latitude:"", longtitude:"" (sic), mode:1, schedule:[], sunset2rise:0, time}`
27
+ * `enable` is the on/off; `time` = auto-off seconds; `mode`/`schedule`/`sunset2rise` are the scheduling
28
+ * knobs. See {@link AutoSpotlightOptions}.
29
+ * NB: the app-JS constant `APP_CMD_SET_LIGHT_CTRL_PIR_SWITCH` (1408) is a RED HERRING for this device —
30
+ * the T8425 never emits 1408; three 1408 wire attempts all no-op'd. (No `1408` constant is defined — it's
31
+ * dead; do not add one.)
32
+ */
33
+ readonly MOTION_ACTIVATE_LIGHT: 1422;
34
+ };
35
+ /**
36
+ * Tunables for the "motion-activated light" (auto-spotlight): the spotlight lights up when the camera
37
+ * detects motion. The device takes the WHOLE config on every write — there's no isolated on/off — so
38
+ * these carry the scheduling knobs alongside the enable flag. Defaults mirror the app's; the current
39
+ * values can't be read back, so any field left unset is written as its default rather than preserved.
40
+ */
41
+ export type AutoSpotlightOptions = {
42
+ /** Spotlight brightness 1..100 when it triggers (default 50). */
43
+ brightness?: number;
44
+ /** Auto-off timer in seconds after the light triggers (default 30). */
45
+ time?: number;
46
+ /** Scheduling mode (default 1). */
47
+ mode?: number;
48
+ };
49
+ /**
50
+ * Bound spotlight / floodlight controls — the object returned by `dev.light()`.
51
+ *
52
+ * The reads and their setters are DERIVED from `LIGHT_MEMBERS`: one declaration per feature gives
53
+ * the getter, the setter, its argument type and its description. Only what a member table cannot state
54
+ * is written out below.
55
+ */
56
+ export type LightActions = Surface<typeof LIGHT_MEMBERS> & {
57
+ /** Turn the light on. Throws if this model's switch wire is unverified. */
58
+ on(): Promise<void>;
59
+ /** Turn the light off. Throws if this model's switch wire is unverified. */
60
+ off(): Promise<void>;
61
+ /**
62
+ * Enable/disable the **motion-activated light** (auto-spotlight): the spotlight lights up when the
63
+ * camera detects motion.
64
+ *
65
+ * ⚠️ This is a COMPOSITE write, and it is WRITE-ONLY: the setting is not reported back in the device's
66
+ * params, so the current config cannot be read and merged. Calling it with just `on` re-sends the
67
+ * DEFAULT brightness/auto-off/mode ({@link AutoSpotlightOptions}), overwriting whatever the user set.
68
+ * To preserve their config, pass the current values in `opts`. That is why this is a method and not a
69
+ * member — the caller must own the composite.
70
+ */
71
+ setAutoSpotlight(on: boolean, opts?: AutoSpotlightOptions): Promise<void>;
72
+ };
73
+ /**
74
+ * Every `light` feature, declared once. The property schema, the typed getters, the derived setters,
75
+ * the intent routes and the descriptions all come out of this table.
76
+ *
77
+ * Auto-spotlight (1422) is deliberately absent: it is a COMPOSITE write — brightness, auto-off, mode
78
+ * and enable sent together — and WRITE-ONLY, so a plain toggle would re-send hardcoded defaults and
79
+ * clobber whatever the user set. It stays the explicit `setAutoSpotlight(on, opts)` method, where the
80
+ * composite is visible at the call site.
81
+ *
82
+ * Exported but NOT published: each entry states its wire id and the evidence it was confirmed on,
83
+ * which the reference site does not carry.
84
+ * @internal
85
+ */
86
+ export declare const LIGHT_MEMBERS: {
87
+ /**
88
+ * Whether the lamp is lit RIGHT NOW — the momentary switch, not a setting. Measured on a T8170: 1400
89
+ * goes to `1` when a client lights the spotlight and back to `0` when that client stops, so the value
90
+ * tracks whoever is streaming rather than a preference anyone set. The vendor app lights the lamp for a
91
+ * live view and drops it on quitting.
92
+ *
93
+ * So it reads on whenever any client is watching. The setting a user changes and expects to persist
94
+ * is {@link spotlightEnabled}.
95
+ *
96
+ * The switch refuses for a reason a value check cannot give — this model's frame SHAPE is unconfirmed —
97
+ * so `switchFrame` throws that reason and `bindMembers` turns it into the rejection, rather than
98
+ * letting the generated "not a valid value" blame a boolean that was never the problem.
99
+ */
100
+ readonly isOn: {
101
+ readonly param: 1400;
102
+ readonly property: "light";
103
+ readonly type: "bool";
104
+ readonly kind: "boolean";
105
+ readonly provenance: "verified";
106
+ readonly description: string;
107
+ readonly write: (v: string | number | boolean, ctx: CommandContext) => Command;
108
+ readonly writeAs: "set";
109
+ readonly aliases: {
110
+ readonly on: true;
111
+ readonly off: false;
112
+ };
113
+ };
114
+ /**
115
+ * Manual level for the spotlight, 1-100 — the floor is 1 rather than 0 because a 0 reads as "off" and
116
+ * that is `isOn`'s job. The write is pinned to the level-2 direct-binary frame (`directBinary`),
117
+ * the only shape captured for it, so a standalone camera that never negotiates a level-2 key cannot
118
+ * set it even though the getter reads fine.
119
+ */
120
+ readonly brightness: {
121
+ readonly param: 1401;
122
+ readonly type: "number";
123
+ readonly unit: "%";
124
+ readonly kind: "percent";
125
+ readonly provenance: "verified";
126
+ readonly min: 1;
127
+ readonly max: 100;
128
+ readonly description: "Manual brightness, range 1-100 (1401). Verified live (T8425 + T8124).";
129
+ readonly write: (v: string | number | boolean, ctx: CommandContext) => Command;
130
+ };
131
+ /**
132
+ * Warm-to-cool on a 0-100 scale — a `scalar`, not a percentage or a mired value, since 0 is one end
133
+ * of a range rather than "none". `writeOnly`, so there is a setter and no getter at all: the device
134
+ * accepts the setting and never reports it back.
135
+ * Only tunable-white spotlights respond; a white-only model accepts the frame and does nothing.
136
+ */
137
+ readonly colorTemp: {
138
+ readonly param: 1410;
139
+ readonly type: "number";
140
+ readonly kind: "scalar";
141
+ readonly provenance: "verified";
142
+ readonly writeOnly: true;
143
+ readonly min: 0;
144
+ readonly max: 100;
145
+ readonly description: "Colour temperature, 0 (warm) to 100 (cool). Not reported back, so it is write-only.";
146
+ readonly write: (v: string | number | boolean, ctx: CommandContext) => Command;
147
+ };
148
+ /**
149
+ * The master enable, one level above {@link isOn}: this decides whether the spotlight may light at all,
150
+ * where `isOn` is the momentary switch. `writeAs` names the setter `setEnabled` rather than the
151
+ * `setSpotlightEnabled` the key would derive, since the capability is already the spotlight.
152
+ *
153
+ * Reported, so it is a read as well as a write. ✅ Verified live on a T8170: the cloud device list
154
+ * carries 1403, and its value tracks the vendor app's spotlight setting in both directions — `1` to `0`
155
+ * when the setting is switched off, `0` to `1` when it is switched back on, so the polarity is direct
156
+ * and 1 means enabled. The poll reports a param only when its PREVIOUS value differed, so the id is in
157
+ * the record rather than newly appearing. The param dictionary names it `floodlightTotalSwitch`
158
+ * (`app:FLOODLIGHT_TOTAL_SWITCH`) and lists the T8170 among its models.
159
+ *
160
+ * This is the switch a user changes and expects to STAY changed. {@link isOn} is a different fact:
161
+ * the lamp being lit right now, driven by whichever
162
+ * client is streaming — the vendor app lights it for a live view and drops it on quitting.
163
+ */
164
+ readonly spotlightEnabled: {
165
+ readonly param: 1403;
166
+ readonly type: "bool";
167
+ readonly kind: "boolean";
168
+ readonly provenance: "verified";
169
+ readonly description: string;
170
+ readonly write: (v: string | number | boolean, ctx: CommandContext) => Command;
171
+ readonly writeAs: "setEnabled";
172
+ };
173
+ };
174
+ export declare const LIGHT: CapabilityModule;
@@ -0,0 +1,63 @@
1
+ import { isTuyaVacuum } from "../device-family.js";
2
+ import { type Surface } from "./members.js";
3
+ import type { CapabilityModule } from "./types.js";
4
+ /**
5
+ * Every `locate` feature, declared once.
6
+ *
7
+ * `locate()` is a `method` rather than a derived setter because its argument is OPTIONAL — the
8
+ * common call is a bare `locate()` meaning "start beeping" — and a derived setter always takes its
9
+ * value. Dispatches DP 103 (legacy Tuya) or DP 160 (AIoT) based on which DP the device has reported.
10
+ *
11
+ * Exported but NOT published: each entry states its wire id and the evidence it was confirmed on,
12
+ * which the reference site does not carry.
13
+ * @internal
14
+ */
15
+ export declare const LOCATE_MEMBERS: {
16
+ /**
17
+ * The find-robot DP read back. `writtenElsewhere` rather than carrying its own `write`, because the
18
+ * setter is the `locate` method below — its argument is optional, which a derived setter cannot be.
19
+ * Expect this to read `undefined` on most robots: DP 160 is a momentary trigger, so a device that has
20
+ * never been asked to beep has no value to report and the evidence gate skips the getter entirely.
21
+ */
22
+ readonly locating: {
23
+ readonly param: 160;
24
+ readonly type: "bool";
25
+ readonly kind: "boolean";
26
+ readonly provenance: "mega";
27
+ readonly writtenElsewhere: true;
28
+ readonly readAliases: readonly [{
29
+ readonly paramType: 103;
30
+ readonly available: typeof isTuyaVacuum;
31
+ }];
32
+ readonly description: string;
33
+ };
34
+ /**
35
+ * Writes DP 160 (AIoT) — `true` starts the beep, `false` cancels one already sounding. AIoT only:
36
+ * the legacy Tuya DP 103 is read as an alias above, but its WRITE direction is unconfirmed, so no
37
+ * Tuya dispatch is offered. The default
38
+ * argument is what makes this a `method`: a bare `locate()` is the call that matters, and a derived
39
+ * setter always demands its value.
40
+ *
41
+ * That default is also why the argument is named here: it is absent from the function's arity, so the
42
+ * description would otherwise derive as taking NO arguments and leave the cancel form undeclared.
43
+ */
44
+ readonly locate: {
45
+ readonly method: (deps: import("./members.js").MemberDeps) => (on?: boolean) => Promise<void>;
46
+ readonly description: string;
47
+ readonly available: ((ctx: import("./types.js").CommandContext) => boolean) & ((ctx: import("./types.js").CommandContext) => boolean);
48
+ readonly answers?: true;
49
+ readonly args: readonly [{
50
+ readonly name: "on";
51
+ readonly kind: "boolean";
52
+ readonly optional: true;
53
+ readonly description: "False cancels a beep in progress.";
54
+ }];
55
+ };
56
+ };
57
+ /**
58
+ * Bound locate reads and controls — the object returned by `dev.locate()`. The `locating` read is
59
+ * present only when the device has reported DP 160; `locate()` is always present on a bound device.
60
+ */
61
+ export type LocateActions = Surface<typeof LOCATE_MEMBERS>;
62
+ /** `locate` — make the robot beep to find it. */
63
+ export declare const LOCATE: CapabilityModule;
@@ -0,0 +1,242 @@
1
+ import { type Surface } from "./members.js";
2
+ import type { AvailabilityContext, CapabilityModule, CommandContext } from "./types.js";
3
+ import type { Command, AutoLockSnapshot } from "../../core/contracts.js";
4
+ /**
5
+ * Setting-id selectors for the compact `ff09-setting-toggle` write — this capability's OWN wire
6
+ * vocabulary, named not inlined per the capability wire-id convention, so `setRainMode`
7
+ * below references `LOCK_SETTING_ID.RAIN_MODE` and this module's own spec can import it too, instead
8
+ * of a bare `7` at either call site.
9
+ *
10
+ * Unlike a normal capability id (owned by exactly one layer, crossing into `transport/` only as an
11
+ * opaque number), this value is a GENUINE duplicate of `transport/ff09.ts`'s `FF09_SETTING_ID.RAIN_MODE`
12
+ * — the capability↔transport decorrelation rule (`model/` never imports `transport/`) leaves no way to
13
+ * share one source of truth across the boundary, and no test can cross-check them without violating
14
+ * that same rule (`guard:decorrelation` forbids any `transport/` import under `src/model`, including
15
+ * tests). If a future capture revises this id, BOTH copies must be updated by hand.
16
+ */
17
+ export declare const LOCK_SETTING_ID: {
18
+ /** One-touch lock toggle. Structurally confirmed via the app's own JS (2026-07-18) — see the module doc. */
19
+ readonly ONE_TOUCH_LOCK: 1;
20
+ /** Scramble-passcode toggle. Structurally confirmed via the app's own JS (2026-07-18) — see the module doc. */
21
+ readonly SCRAMBLE_PASSCODE: 3;
22
+ /** Wifi-status toggle. Structurally confirmed via the app's own JS (2026-07-18) — see the module doc. */
23
+ readonly WIFI_STATUS: 5;
24
+ /** Event-log-enable toggle. Structurally confirmed via the app's own JS (2026-07-18) — see the module doc. */
25
+ readonly ENABLE_LOG: 6;
26
+ /** Rain Mode toggle on the T8531 video lock. Verified live 2026-07-18. */
27
+ readonly RAIN_MODE: 7;
28
+ /** Privacy-mode toggle. Structurally confirmed via the app's own JS (2026-07-18) — see the module doc. */
29
+ readonly PRIVACY_MODE: 9;
30
+ /** One-touch rear-lock toggle. Structurally confirmed via the app's own JS (2026-07-18) — see the module doc. */
31
+ readonly ONE_TOUCH_REAR_LOCK: 11;
32
+ };
33
+ /**
34
+ * Bound lock controls — the object returned by `dev.lock()`.
35
+ *
36
+ * Everything is DERIVED from `LOCK_MEMBERS`. `lock`/`unlock` and the setting toggles drive any
37
+ * lock-family actuator — currently the T8531 video smart lock and the T85D0 garage door, which share one
38
+ * actuation frame. This module emits ONE transport-neutral intent (identity fields only, no wire bytes,
39
+ * no cipher, no routing key) and names no transport: the command sink routes P2P vs MQTT by the device's
40
+ * topology, and the chosen transport's command router builds the frame + envelope and re-resolves its own
41
+ * routing tail. Which pipe it is does not reach `dev.lock()` — its surface is identical either way,
42
+ * the same way P2P-vs-cloud is hidden for live media.
43
+ */
44
+ export type LockActions = Surface<typeof LOCK_MEMBERS>;
45
+ /**
46
+ * Whether the device is reachable over P2P — the topology fact that decides which lock settings exist.
47
+ * The compact toggles are only known on the P2P video lock; the MQTT garage door does not expose them in
48
+ * the app, so offering them there would guess a frame shape that likely does not exist.
49
+ */
50
+ declare const overP2p: (ctx: AvailabilityContext) => boolean;
51
+ /**
52
+ * Every `lock` feature, declared once.
53
+ *
54
+ * The six toggles after `setRainMode` share its confirmed frame SHAPE and are grounded in the app's own
55
+ * JS, but none has been captured against a real device — so each is `unverified`: DECLARED, so the
56
+ * capability documents what the lock has, and NOT installed, because a fire-and-forget write that is
57
+ * wrong looks exactly like success. A caller sees them as optional and learns at compile time that they
58
+ * are not settable yet; promoting one is a single edit once a capture lands.
59
+ *
60
+ * They carry no `param`: a setting id is not a param id, nothing reports these back, and declaring one
61
+ * would put a wire number where the schema expects a reported value.
62
+ *
63
+ * Exported but NOT published: each entry states its wire id and the evidence it was confirmed on,
64
+ * which the reference site does not carry.
65
+ * @internal
66
+ */
67
+ export declare const LOCK_MEMBERS: {
68
+ /**
69
+ * The lock's own state, and the weakest thing in this table: 1200 is a `guessed` placeholder because
70
+ * the lock announces (un)locking as a pushed event rather than holding a param, so the evidence gate
71
+ * will normally leave this getter uninstalled and `lockState` is the real read. `writtenElsewhere`
72
+ * points at the `lock`/`unlock` methods — a single `write` cannot express two verbs that carry no
73
+ * value.
74
+ */
75
+ readonly locked: {
76
+ readonly param: 1200;
77
+ readonly type: "bool";
78
+ readonly kind: "boolean";
79
+ readonly provenance: "guessed";
80
+ readonly writtenElsewhere: true;
81
+ readonly description: "Lock state, true=locked. UNVERIFIED: no stable state param to key off; placeholder id pending verification.";
82
+ };
83
+ /**
84
+ * Cell charge as a percentage, on the same param 1101 every battery device reports. Named `battery`
85
+ * within this capability rather than deferring to the `battery` capability: a lock resolves as a lock,
86
+ * so the accessor is `dev.lock().battery`.
87
+ */
88
+ readonly battery: {
89
+ readonly param: 1101;
90
+ readonly type: "number";
91
+ readonly unit: "%";
92
+ readonly kind: "percent";
93
+ readonly provenance: "verified";
94
+ readonly description: "Lock battery level 0-100 (verified: param 1101).";
95
+ };
96
+ /**
97
+ * Link quality in dBm as the lock measures it, on the shared param 1141. Both actuation methods here
98
+ * are fire-and-forget, so a weak link is silent rather than an error.
99
+ */
100
+ readonly rssi: {
101
+ readonly param: 1141;
102
+ readonly type: "number";
103
+ readonly unit: "dBm";
104
+ readonly kind: "dbm";
105
+ readonly provenance: "verified";
106
+ readonly description: "Lock signal strength (verified: param 1141 = RSSI).";
107
+ };
108
+ /** Lock the deadbolt/door. Fire-and-forget — rejects only on missing member identity, never on a device timeout. */
109
+ readonly lock: import("./members.js").MethodMember<() => Promise<void>>;
110
+ /** Unlock the deadbolt/door. Fire-and-forget — rejects only on missing member identity. */
111
+ readonly unlock: import("./members.js").MethodMember<() => Promise<void>>;
112
+ /**
113
+ * Read-modify-write the auto-lock setting: the transport GETs the device's current settings, changes
114
+ * only `enabled` (+ `delaySeconds` if given — otherwise the current delay is preserved), and writes the
115
+ * rest back verbatim. Works on both the T8531 video lock and the T85D0 garage/lock, which share the
116
+ * identical settings frame; confirmed on-device in both directions on both families.
117
+ *
118
+ * **Unlike `lock`/`unlock` this can reject on a device TIMEOUT**, not just missing identity — the GET
119
+ * step is a genuine precondition, so this one is not fire-and-forget.
120
+ */
121
+ readonly setAutoLock: import("./members.js").MethodMember<(enabled: boolean, delaySeconds?: number) => Promise<void>>;
122
+ /**
123
+ * Toggle Rain Mode — a pure blind write, unlike `setAutoLock`: the compact frame carries only this one
124
+ * field, so there is no GET pass and it is fire-and-forget. Confirmed on-device end-to-end in both
125
+ * directions, with the app UI reflecting the new state afterward. P2P video lock only.
126
+ */
127
+ readonly setRainMode: import("./members.js").MethodMember<(enabled: boolean) => Promise<void>> & {
128
+ available: (ctx: CommandContext) => boolean;
129
+ };
130
+ /**
131
+ * Lock the door by a single touch on the pad, with no code. First of the six compact toggles: all
132
+ * `unverified` (frame shape read out of the app's JS, never captured), so no setter is installed and
133
+ * `setOneTouchLock` lands optional on the surface — a caller learns at compile time. `writeOnly` too:
134
+ * nothing reports the setting back, so there is no getter either.
135
+ */
136
+ readonly oneTouchLock: {
137
+ readonly type: "bool";
138
+ readonly kind: "boolean";
139
+ readonly writeOnly: true;
140
+ readonly unverified: true;
141
+ readonly available: typeof overP2p;
142
+ readonly provenance: "apk";
143
+ readonly description: "One-touch locking (setting-id 1). Wire shape confirmed in the app's own JS, NOT captured.";
144
+ readonly write: (v: string | number | boolean, ctx: CommandContext) => Command;
145
+ };
146
+ /**
147
+ * Pad anti-shoulder-surfing: the lock asks for extra random digits around the real code so a watcher
148
+ * cannot read it off worn keys. Same `unverified` + `writeOnly` + P2P-only standing as its five
149
+ * siblings — declared so the capability documents the lock, not installed.
150
+ */
151
+ readonly scramblePasscode: {
152
+ readonly type: "bool";
153
+ readonly kind: "boolean";
154
+ readonly writeOnly: true;
155
+ readonly unverified: true;
156
+ readonly available: typeof overP2p;
157
+ readonly provenance: "apk";
158
+ readonly description: "Scramble the passcode entry pad (setting-id 3). Wire shape confirmed in the app's own JS, NOT captured.";
159
+ readonly write: (v: string | number | boolean, ctx: CommandContext) => Command;
160
+ };
161
+ /**
162
+ * Whether the lock reports its Wi-Fi status — a reporting toggle, not the radio itself, on the app's
163
+ * own naming. Same `unverified` + `writeOnly` + P2P-only standing as its five siblings; treat the
164
+ * meaning as the app's label until a capture pins the behaviour.
165
+ */
166
+ readonly wifiStatus: {
167
+ readonly type: "bool";
168
+ readonly kind: "boolean";
169
+ readonly writeOnly: true;
170
+ readonly unverified: true;
171
+ readonly available: typeof overP2p;
172
+ readonly provenance: "apk";
173
+ readonly description: "Wi-Fi status reporting (setting-id 5). Wire shape confirmed in the app's own JS, NOT captured.";
174
+ readonly write: (v: string | number | boolean, ctx: CommandContext) => Command;
175
+ };
176
+ /**
177
+ * Whether the lock records its own event history on-device. Same `unverified` + `writeOnly` +
178
+ * P2P-only standing as its five siblings — the frame shape comes from the app's JS and has never been
179
+ * driven against hardware.
180
+ */
181
+ readonly logEnabled: {
182
+ readonly type: "bool";
183
+ readonly kind: "boolean";
184
+ readonly writeOnly: true;
185
+ readonly unverified: true;
186
+ readonly available: typeof overP2p;
187
+ readonly provenance: "apk";
188
+ readonly description: "Event-log recording (setting-id 6). Wire shape confirmed in the app's own JS, NOT captured.";
189
+ readonly write: (v: string | number | boolean, ctx: CommandContext) => Command;
190
+ };
191
+ /**
192
+ * The lock's own privacy mode — unrelated to the camera capability's privacy burst, which is a
193
+ * different device, a different wire and a different meaning. Same `unverified` + `writeOnly` +
194
+ * P2P-only standing as its five siblings.
195
+ */
196
+ readonly privacyMode: {
197
+ readonly type: "bool";
198
+ readonly kind: "boolean";
199
+ readonly writeOnly: true;
200
+ readonly unverified: true;
201
+ readonly available: typeof overP2p;
202
+ readonly provenance: "apk";
203
+ readonly description: "Lock privacy mode (setting-id 9). Wire shape confirmed in the app's own JS, NOT captured.";
204
+ readonly write: (v: string | number | boolean, ctx: CommandContext) => Command;
205
+ };
206
+ /**
207
+ * The rear-deadbolt counterpart to `oneTouchLock`, on a lock that has a second bolt — so a model with
208
+ * one bolt has nothing for it to drive. Last of the six compact toggles and shares their standing
209
+ * exactly: `unverified`, `writeOnly`, P2P-only, no setter installed.
210
+ */
211
+ readonly oneTouchRearLock: {
212
+ readonly type: "bool";
213
+ readonly kind: "boolean";
214
+ readonly writeOnly: true;
215
+ readonly unverified: true;
216
+ readonly available: typeof overP2p;
217
+ readonly provenance: "apk";
218
+ readonly description: "One-touch rear locking (setting-id 11). Wire shape confirmed in the app's own JS, NOT captured.";
219
+ readonly write: (v: string | number | boolean, ctx: CommandContext) => Command;
220
+ };
221
+ /**
222
+ * Read the device's current auto-lock settings via a live `GET_SETTINGS` round-trip — the SAME read
223
+ * `setAutoLock` does internally, exposed standalone with no write attached. Works over BOTH transports.
224
+ * A genuine request/reply query, not a passive property, so it always talks to the device.
225
+ *
226
+ * `answers` for that reason: the returned snapshot IS the point, so it is not a control to offer even
227
+ * though it takes no arguments — offering it as one would run a round-trip and discard the answer.
228
+ */
229
+ readonly getAutoLockState: {
230
+ readonly needs: "ff09Settings";
231
+ readonly provided: (provider: import("../../core/contracts.js").Ff09SettingsReader, deps: import("./members.js").MemberDeps) => () => Promise<AutoLockSnapshot>;
232
+ readonly description: string;
233
+ readonly requiredCapabilities?: readonly import("../types.js").Capability[];
234
+ readonly answers: true;
235
+ };
236
+ };
237
+ /**
238
+ * `lock` — smart lock. `locked` is the reported lock state, but there's no stable state param to
239
+ * key off (only pushed via a CommandType-style event), so the param id here is a placeholder.
240
+ */
241
+ export declare const LOCK: CapabilityModule;
242
+ export {};
@@ -0,0 +1,107 @@
1
+ /**
2
+ * What a device exposes, as data — one JSON-safe shape per device, with no branch per capability.
3
+ *
4
+ * A bound device is fully callable but states nothing about ITSELF from outside the package: neither
5
+ * what is installed nor what a value means is readable off it. The member table states both, and this
6
+ * turns that statement into a public, JSON-safe shape: which reads a device actually installed, which
7
+ * of its actions are offerable, and which events it emits.
8
+ *
9
+ * **Derived from the LIVE bound objects, not recomputed from the tables.** The descriptors of the bound
10
+ * object are the only source that cannot disagree with what `bindMembers` installed — evidence
11
+ * gates, provider gates and unverified writes are all already applied there. Recomputing the same answer
12
+ * from the module tables would be a second implementation of the gate, and it would be wrong in exactly
13
+ * the cases that matter: a media method on a device bound without that provider, a read for a param
14
+ * the device never reported. The table is joined in only for SEMANTICS (what the value means), which the
15
+ * bound object does not carry.
16
+ *
17
+ * **Never spread, `Object.entries` or `JSON.stringify` a bound object here.** All three invoke its
18
+ * getters, and a getter with a `decode` calls into the injected codec. Property descriptors only.
19
+ *
20
+ * @module model/capabilities/manifest
21
+ */
22
+ import type { Capability, Codec, PropertyValueType, ResolvedDevice, ValueKind } from "../types.js";
23
+ import type { ActionSpec, AvailabilityContext, CapabilityModule } from "./types.js";
24
+ /** One read installed on a bound capability object — a value the device reports, and what it means. */
25
+ export interface ReadDescriptor {
26
+ /** The getter's name on the capability object (`dev.battery()?.level` → `level`). */
27
+ accessor: string;
28
+ /** The name the same value carries in the device's flat property namespace (`getProperty`). */
29
+ property: string;
30
+ /** How the value is stored. */
31
+ type: PropertyValueType;
32
+ /** What the value MEANS, as opposed to how it is stored. See {@link ValueKind}. */
33
+ kind?: ValueKind;
34
+ /** The unit the device reports the value in, when it has one (`"%"`, `"°C"`, `"dBm"`). */
35
+ unit?: string;
36
+ /** The option set, for a value out of a fixed domain. */
37
+ values?: readonly (string | number)[];
38
+ /** Labels for {@link values}, keyed by the raw value as a string. */
39
+ labels?: Readonly<Record<string, string>>;
40
+ /**
41
+ * Whether a setter for this value is installed BESIDE the getter on the same object.
42
+ *
43
+ * Read off the bound object rather than the schema, so it means "a caller can write this on THIS
44
+ * device" — a write the device gave no evidence for, or one whose wire is not confirmed, is not
45
+ * installed and reads `false` here. A value driven by a differently-named method (a pair of frames,
46
+ * a validating setter) is `false` too and appears under the capability's {@link
47
+ * CapabilityDescriptor.actions} instead, which is where its signature is described.
48
+ */
49
+ writable: boolean;
50
+ description?: string;
51
+ }
52
+ /**
53
+ * One offerable action: an {@link ActionSpec} plus the name it is installed under.
54
+ *
55
+ * The name is taken from the enumeration rather than carried in the spec, so a renamed method takes its
56
+ * description with it and cannot leave one behind pointing at nothing.
57
+ */
58
+ export interface ActionDescriptor extends ActionSpec {
59
+ name: string;
60
+ }
61
+ /** What one capability exposes on a device — the join of its bound object and its own declaration. */
62
+ export interface CapabilityDescriptor {
63
+ capability: Capability;
64
+ /** The fluent accessor this capability is reached under: `dev[accessor]()`. */
65
+ accessor: string;
66
+ /** The reads INSTALLED on this device, never the theoretical set. */
67
+ reads: readonly ReadDescriptor[];
68
+ /** The installed actions that carry a description. */
69
+ actions: readonly ActionDescriptor[];
70
+ /**
71
+ * Installed, callable actions with no description — usable, but not auto-offerable. Published rather
72
+ * than hidden so the gap is visible instead of looking like the action doesn't exist.
73
+ */
74
+ undescribedActions: readonly string[];
75
+ /** The semantic event names this capability emits. */
76
+ events: readonly string[];
77
+ }
78
+ /**
79
+ * A device's public shape: its identity, its capabilities, and what each of those exposes.
80
+ *
81
+ * `bound` is explicit because an unbound model object (no live client) has no bound objects to
82
+ * enumerate, so its `details` are empty — a caller has to be able to tell "this device exposes nothing"
83
+ * from "ask again once it is bound".
84
+ */
85
+ export interface DeviceManifest {
86
+ sn: string;
87
+ /** What the user named the device in the app; falls back to {@link modelName} when unnamed. */
88
+ name: string;
89
+ /** Model / T-code ("T8410"), when the record states one. */
90
+ model?: string;
91
+ /** The model's own display name ("Indoor Cam Pan & Tilt") — the product, not this unit. */
92
+ modelName: string;
93
+ codec: Codec;
94
+ source: ResolvedDevice["source"];
95
+ bound: boolean;
96
+ capabilities: readonly Capability[];
97
+ details: readonly CapabilityDescriptor[];
98
+ }
99
+ /**
100
+ * Describe the capability objects a device has bound, one descriptor each.
101
+ *
102
+ * Parameterised over the module list; the barrel binds it to the real one. A capability the device did
103
+ * not bind — because it does not have it, or because nothing is bound yet — contributes no descriptor
104
+ * at all.
105
+ * @internal
106
+ */
107
+ export declare function describeBound(modules: readonly CapabilityModule[], bound: Readonly<Record<string, unknown>>, ctx?: AvailabilityContext): CapabilityDescriptor[];