@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,147 @@
1
+ /**
2
+ * eufy push-notification constants.
3
+ *
4
+ * PROVENANCE: the event-type codes are cross-checked against the v6 APK
5
+ * (com.oceanwing.battery.cam) — the AI-detection codes are present in the v6
6
+ * runtime, and v6 uses the same Firebase project (batterycam-3250a / sender
7
+ * 348804314802), so this set is current. v6-only additions noted inline.
8
+ *
9
+ * These are the push-event SEMANTICS the capability layer matches on (motion/doorbell/person/…);
10
+ * {@link detectionName} maps a code to a human label. Pure MCS wire framing is a transport concern
11
+ * and lives with the transport, not here.
12
+ */
13
+ /** Generic custom push event (field `a` in CusPushData). */
14
+ export declare enum CusPushEvent {
15
+ SECURITY = 1,
16
+ TFCARD = 2,
17
+ DOOR_SENSOR = 3,
18
+ CAM_STATE = 4,
19
+ GSENSOR = 5,
20
+ BATTERY_LOW = 6,
21
+ BATTERY_HOT = 7,
22
+ LIGHT_STATE = 8,
23
+ MODE_SWITCH = 9,
24
+ ALARM = 10,
25
+ BATTERY_FULL = 11,
26
+ REPEATER_RSSI_WEAK = 12,
27
+ UPGRADE_STATUS = 13,
28
+ MOTION_SENSOR_PIR = 14,
29
+ ALARM_DELAY = 16,
30
+ HUB_BATT_POWERED = 17,
31
+ SENSOR_NO_OPEN = 18,
32
+ SMART_DROP = 20
33
+ }
34
+ /** Alarm trigger source. */
35
+ export declare enum CusPushAlarmType {
36
+ HUB_STOP = 0,
37
+ DEV_STOP = 1,
38
+ GSENSOR = 2,
39
+ PIR = 3,
40
+ APP = 4,
41
+ HOT = 5,
42
+ DOOR = 6,
43
+ CAMERA = 7,
44
+ MOTION_SENSOR = 8,
45
+ CAMERA_GSENSOR = 9,
46
+ CAMERA_APP = 10,
47
+ CAMERA_LINKAGE = 11,
48
+ HUB_LINKAGE = 12,
49
+ HUB_KEYPAD_PANIC_BUTTON = 13,
50
+ HUB_KEYPAD_EMERGENCY_CODE = 14,
51
+ HUB_STOP_BY_KEYPAD = 15,
52
+ HUB_STOP_BY_APP = 16,
53
+ HUB_STOP_BY_HUB = 17,
54
+ HUB_KEYPAD_CUSTOM_NOT_MAP = 18
55
+ }
56
+ /** Arming/mode-switch source. */
57
+ export declare enum CusPushMode {
58
+ SWITCH_FROM_KEYPAD = 1,
59
+ SWITCH_FROM_APP = 2,
60
+ SWITCH = 9
61
+ }
62
+ /** Doorbell AI-detection events (3xxx). All v6-confirmed. */
63
+ export declare enum DoorbellPushEvent {
64
+ BACKGROUND_ACTIVE = 3100,
65
+ MOTION_DETECTION = 3101,
66
+ FACE_DETECTION = 3102,
67
+ PRESS_DOORBELL = 3103,
68
+ PET_DETECTION = 3106,
69
+ VEHICLE_DETECTION = 3107,
70
+ PACKAGE_DELIVERED = 3301,
71
+ PACKAGE_TAKEN = 3302,
72
+ FAMILY_DETECTION = 3303,
73
+ PACKAGE_STRANDED = 3304,
74
+ SOMEONE_LOITERING = 3305,
75
+ RADAR_MOTION_DETECTION = 3306,
76
+ AWAY_FROM_HOME = 3307,
77
+ RADAR_DETECTION = 3308
78
+ }
79
+ /** Indoor-camera AI-detection events. */
80
+ export declare enum IndoorPushEvent {
81
+ MOTION_DETECTION = 3101,
82
+ FACE_DETECTION = 3102,
83
+ CRYING_DETECTION = 3104,
84
+ SOUND_DETECTION = 3105,
85
+ PET_DETECTION = 3106,
86
+ VEHICLE_DETECTION = 3107
87
+ }
88
+ /** HomeBase-3 paired-device AI-detection events (3108-3112 are v6-era). */
89
+ export declare enum HB3PairedDevicePushEvent {
90
+ MOTION_DETECTION = 3101,
91
+ FACE_DETECTION = 3102,
92
+ PRESS_DOORBELL = 3103,
93
+ CRYING_DETECTION = 3104,
94
+ SOUND_DETECTION = 3105,
95
+ PET_DETECTION = 3106,
96
+ VEHICLE_DETECTION = 3107,
97
+ DOG_DETECTION = 3108,
98
+ DOG_LICK_DETECTION = 3109,
99
+ DOG_POOP_DETECTION = 3110,
100
+ IDENTITY_PERSON_DETECTION = 3111,
101
+ STRANGER_PERSON_DETECTION = 3112
102
+ }
103
+ /** Lock action / status events. */
104
+ export declare enum LockPushEvent {
105
+ MANUAL_UNLOCK = 257,
106
+ AUTO_UNLOCK = 258,
107
+ PW_UNLOCK = 259,
108
+ FINGERPRINT_UNLOCK = 260,
109
+ APP_UNLOCK = 261,
110
+ MANUAL_LOCK = 262,
111
+ KEYPAD_LOCK = 263,
112
+ APP_LOCK = 264,
113
+ AUTO_LOCK = 265,
114
+ PW_LOCK = 266,
115
+ FINGER_LOCK = 267,
116
+ TEMPORARY_PW_LOCK = 268,
117
+ TEMPORARY_PW_UNLOCK = 269,
118
+ LOW_POWER = 513,
119
+ VERY_LOW_POWER = 514,
120
+ MULTIPLE_ERRORS = 515,
121
+ LOCK_OFFLINE = 516,
122
+ MECHANICAL_ANOMALY = 517,
123
+ VIOLENT_DESTRUCTION = 518,
124
+ LOCK_MECHANICAL_ANOMALY = 519,
125
+ DOOR_OPEN_LEFT = 520,
126
+ DOOR_TAMPER = 521,
127
+ DOOR_STATE_ERROR = 522,
128
+ STATUS_CHANGE = 769,
129
+ OTA_STATUS = 770,
130
+ LOCK_ONLINE = 771
131
+ }
132
+ /** SmartDrop locker events. */
133
+ export declare enum SmartDropPushEvent {
134
+ LOW_BATTERY = 6,
135
+ OVERHEATING_WARNING = 7,
136
+ TAMPERED_WARNING = 10,
137
+ BATTERY_FULLY_CHARGED = 11,
138
+ PERSON_DETECTED = 3102
139
+ }
140
+ /** Push notification presentation style. */
141
+ export declare enum NotificationStyle {
142
+ TEXT = 1,
143
+ THUMB = 2,
144
+ ALL = 3
145
+ }
146
+ /** Resolve a 3xxx AI-detection event id to a human name (camera/doorbell). */
147
+ export declare function detectionName(eventType: number): string;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Device registry + the **3-tier resolver**.
3
+ *
4
+ * Resolving a {@link CloudRecord} to a {@link ResolvedDevice} (`{ codec, capabilities,
5
+ * properties, name }`) is the heart of the data-driven model. Most-specific wins, but the
6
+ * result is always *additive* so nothing is ever lost:
7
+ *
8
+ * 1. **model row** (curated, tier 1) — a hand-authored {@link RegistryEntry} keyed by T-code.
9
+ * Best naming + curated extra capabilities. Optional; most devices never need one.
10
+ * 2. **category default** (tier 2) — {@link classify} maps the cloud `device_type` (or, failing
11
+ * that, the model code) to a {@link Codec}, which contributes its baseline capabilities.
12
+ * 3. **inference** (tier 3) — {@link detectCapabilities} adds whatever the device can *prove*
13
+ * about itself from its reported params + model/category strings (graceful unknown).
14
+ *
15
+ * The final capability set is the union of all three tiers (deduped), minus the ones a module marks
16
+ * {@link import("./capabilities/types").CapabilityModule.ownedByStation} when the record says this
17
+ * device hangs off a parent — the one place the result is subtractive, because a control the group's
18
+ * owner holds is one this device cannot answer for however it was granted. `source` records the most
19
+ * authoritative tier that fired, for diagnostics.
20
+ *
21
+ * The curated rows below are intentionally a **small seed** — the system classifies and works
22
+ * read-only for devices with no row at all. Rows exist only to add curation (pretty names,
23
+ * capabilities not provable from params alone). They are the place to encode model-specific
24
+ * knowledge as it is *confirmed* (via the APK / a mega query) — never as a dumping ground.
25
+ *
26
+ * @module model/registry
27
+ */
28
+ import type { CloudRecord, RegistryEntry, ResolvedDevice, Capability, Codec, PropertySpec } from "./types.js";
29
+ /**
30
+ * Curated model rows (tier 1), keyed by **uppercase T-code**. Seed set only — extend as model
31
+ * specifics are confirmed. Capabilities here are *added* to the codec baseline + inference.
32
+ *
33
+ * NOTE: these are illustrative seeds chosen to exercise the resolver; each should be confirmed
34
+ * against first-party evidence before being relied on (same trust rule as param ids).
35
+ */
36
+ export declare const MODEL_REGISTRY: Readonly<Record<string, RegistryEntry>>;
37
+ /**
38
+ * Resolve a cloud device record into its full model shape via the 3-tier lookup.
39
+ *
40
+ * Capability precedence (earlier = wins on property-name conflicts in `mergeProperties`):
41
+ * curated row caps → codec baseline → inferred extras.
42
+ *
43
+ * @param rec the minimal cloud record (deviceType / model / category / params).
44
+ * @returns the resolved `{ codec, capabilities, properties, name, source }`.
45
+ */
46
+ export declare function resolveDevice(rec: CloudRecord): ResolvedDevice;
47
+ /**
48
+ * The property manifest for a device, from its capabilities and the record's facts. The single place
49
+ * an {@link AvailabilityContext} is built — so a family-gate (`available`) and a per-model enum
50
+ * (`enumValuesFor`) are decided from the same truthful, session-free view on every path (initial
51
+ * resolve and {@link Device.reresolve}). Populated only from what a record carries, never transport
52
+ * fields a live session hasn't produced.
53
+ */
54
+ export declare function resolveProperties(rec: CloudRecord, codec: Codec, capabilities: Capability[]): PropertySpec[];
@@ -0,0 +1,301 @@
1
+ /**
2
+ * Device model — shared contract.
3
+ *
4
+ * The whole model is **data-driven and capability-based**.
5
+ * There is exactly ONE concrete device class; behaviour comes from:
6
+ *
7
+ * - a **codec** (axis B2 — how commands are framed/routed for a device *family*), and
8
+ * - a set of composable **capabilities** (axis A — what the device exposes), each of which
9
+ * contributes a property schema (and, later, command behaviour).
10
+ *
11
+ * A device's `{ codec, capabilities }` is resolved by a 3-tier lookup
12
+ * (exact model row → category default → inference). New SKUs in a known family need no code.
13
+ *
14
+ * This file is the integration contract: every other `src/model/*` module imports from here.
15
+ */
16
+ /**
17
+ * Command-codec families (axis B2). One per genuinely-different wire protocol.
18
+ *
19
+ * `station|camera|sensor|lock|keypad` are the eufy **security** families (P2P; param space
20
+ * 1000+). `vacuum` is the eufy **clean** line (RoboVac) — a different transport (Tuya/MQTT
21
+ * data-points) and a different param namespace (DP ids ~150-180), so its params come from a
22
+ * separate authoritative source (`get_product_data_point` → `data_point_list`), NOT the P2P
23
+ * device list. Per-namespace param dictionaries, not one global table.
24
+ *
25
+ * `light` is the eufy **life** smart-lighting line (e.g. T8L02 "Permanent Outdoor Lights") — its
26
+ * own secure-MQTT "DP" TLV wire, decoded by its own transport codec. Named distinctly
27
+ * from the camera-floodlight `light` *capability* so the two never collide as bare `"light"`.
28
+ *
29
+ * `display` is the T87Ax Smart Display line — its own codec because its `device_type` collides with
30
+ * the security residual range (confirmed live, 2026-09-04: it connects over secure MQTT with no
31
+ * `p2p_did`, never P2P). Its param namespace and product line are nonetheless grouped into `security`
32
+ * by maintainer decision, not wire evidence — see {@link namespaceForCodec}. No capability module
33
+ * targets it yet: no screen/audio/assistant param has been observed, only its own small cloud-param
34
+ * namespace (ids 8001-8006).
35
+ */
36
+ export type Codec = "station" | "camera" | "sensor" | "lock" | "keypad" | "vacuum" | "mower" | "light" | "printer" | "display";
37
+ /**
38
+ * Capability identifiers (axis A). A capability is a composable feature a device exposes;
39
+ * it maps to a {@link CapabilityModule} that owns its property schema. Extend this union as
40
+ * new capabilities are modelled — adding one never requires a subclass.
41
+ */
42
+ export type Capability = "video" | "snapshot" | "motion" | "person_detection" | "battery" | "light" | "ptz" | "doorbell" | "contact" | "leak" | "smoke" | "co" | "siren" | "lock" | "keypad" | "arming" | "storage" | "rtsp" | "camera" | "audio" | "vacuum_clean" | "vacuum_dock" | "suction" | "locate" | "smart_light" | "info";
43
+ /** Value type of a property. */
44
+ export type PropertyValueType = "bool" | "number" | "string" | "enum";
45
+ /**
46
+ * What a value MEANS, as opposed to how it is stored — the semantic annotation that makes a reading
47
+ * convertible without a hardcoded table per property.
48
+ *
49
+ * `PropertyValueType` says a value is a number; this says whether that number is a battery
50
+ * percentage, a temperature, a signal strength, a duration or an instant. The distinction is not
51
+ * cosmetic: a `seconds` duration and a `timestamp` instant are both numbers of seconds, and treating
52
+ * one as the other is wrong in a way no type check catches.
53
+ *
54
+ * - `boolean` — an on/off state (always paired with `type: "bool"`).
55
+ * - `percent` / `celsius` / `dbm` / `seconds` / `hours` / `megabytes` / `degrees` — a measured quantity
56
+ * in the unit the device reports it in; each pairs with the matching `unit`. Values are
57
+ * never converted on the way out — a converted reading is an invented one. `seconds` and `hours`
58
+ * are separate kinds for exactly that reason: a robot reports a run in seconds and a consumable's
59
+ * wear in hours, and normalising one into the other would publish a number the device never sent.
60
+ * - `scalar` — a plain number in no unit at all: a step on a ladder, a mode index, a raw level, a
61
+ * segment count. Ordered and comparable, but its range and direction are the device's, so nothing
62
+ * but the device says what a given value means.
63
+ * - `bitfield` — a number whose individual bits carry the meaning, not its magnitude.
64
+ * - `enum` — one of a named set; the set is the property's `enumValues` (raw → label) or, for a value
65
+ * a read decodes, the read's own `values`.
66
+ * - `identifier` — an opaque id whose domain lives elsewhere (a cloud-fetched list), so it is not
67
+ * ordered and not arithmetic.
68
+ * - `timestamp` — an instant, unix seconds.
69
+ * - `text` — free-form or structured text with no further promise.
70
+ *
71
+ * Three pairs are close enough to pick wrongly, so the test for each:
72
+ *
73
+ * - **`scalar` vs a measured quantity** — is there a unit the device reports it in? A sensitivity
74
+ * step, a mode index and a segment count are all `scalar` precisely because there is none; they
75
+ * are NOT counts of anything, and the name says only "a bare number". A quantity with a unit takes
76
+ * the kind naming that unit, and the two are checked against each other in both directions.
77
+ * - **`identifier` vs `enum`** — can we publish the set? An `enum` ships its options with it, so its
78
+ * label needs nothing else. An `identifier` is a number whose domain is
79
+ * held somewhere we do not control (a catalogue the app fetches), so there is no set to ship and
80
+ * arithmetic on it — ordering, nearest-value, a range — is meaningless.
81
+ * - **`bitfield` vs `enum`** — one value, or several at once? A bitfield's bits combine, so it has no
82
+ * single label and its magnitude means nothing. The named bits belong to the capability that
83
+ * decodes them and are exported beside that decoder; unlike an enum's options they are not carried
84
+ * here, which is a gap this vocabulary does not close on its own.
85
+ */
86
+ export declare const KNOWN_VALUE_KINDS: readonly ["boolean", "percent", "celsius", "dbm", "seconds", "hours", "megabytes", "degrees", "scalar", "bitfield", "enum", "identifier", "timestamp", "text"];
87
+ /** One of the kinds this version models — the closed half of {@link ValueKind}. */
88
+ export type KnownValueKind = (typeof KNOWN_VALUE_KINDS)[number];
89
+ /**
90
+ * A value kind — {@link KnownValueKind}, left **open** on purpose.
91
+ *
92
+ * Adding a member to a closed union is a breaking change for every caller with an exhaustive switch;
93
+ * adding one to the open form is not. A caller maps the kinds it knows and falls through to a default
94
+ * for the rest, so a newly-modelled kind degrades to "shown raw" instead of failing to compile.
95
+ */
96
+ export type ValueKind = KnownValueKind | (string & {});
97
+ /**
98
+ * Whether a kind is one this version models, narrowing it to {@link KnownValueKind}.
99
+ *
100
+ * The counterpart to leaving the union open: the compiler cannot tell a caller that a kind is one it
101
+ * has a branch for, because at a declaration site any string is accepted. This is how a caller writes
102
+ * the fallback branch the open union asks for — and, in the other direction, how the SDK's own specs
103
+ * catch a kind that was typed rather than modelled.
104
+ */
105
+ export declare function isKnownValueKind(kind: ValueKind): kind is KnownValueKind;
106
+ /**
107
+ * Trust provenance of a property's `param_type` mapping, most-trusted first:
108
+ * - `mega` — confirmed against the live mega API / a real device's reported params.
109
+ * - `apk` — extracted from the v6 app itself (the ids the app actually sends — authoritative).
110
+ * - `verified` — confirmed by our own capture/observation.
111
+ * - `guessed` — a plausible placeholder; lowest trust.
112
+ *
113
+ * This project never relies on a third-party reverse-engineering project as a source of trust —
114
+ * every id/behavior we ship is grounded in the app's
115
+ * own decompiled code (`apk`) or our own capture/observation (`verified`), never someone else's
116
+ * unverified guess. Absent provenance is treated as `guessed`.
117
+ *
118
+ * Provenance of a property definition — an internal trust label used when curating the model.
119
+ * @internal
120
+ */
121
+ export type PropertySource = "mega" | "apk" | "verified" | "guessed";
122
+ /**
123
+ * A single device property, mapped to its P2P `param_type`. This is pure data — the same
124
+ * spec is reused across every device whose capability contributes it.
125
+ */
126
+ export interface PropertySpec {
127
+ /** Stable, code-facing name (e.g. "battery", "light", "motionDetection"). */
128
+ name: string;
129
+ /** The eufy P2P `param_type` carrying this value (the wire id). */
130
+ paramType: number;
131
+ type: PropertyValueType;
132
+ /** Human unit, when meaningful (e.g. "%", "°C", "dBm"). */
133
+ unit?: string;
134
+ /**
135
+ * What the value means ({@link ValueKind}) — the machine-readable half of {@link unit}.
136
+ *
137
+ * Absent when the stored value carries no scalar meaning of its own: a structured payload whose
138
+ * semantic value is a field inside it declares the kind on the member that decodes it
139
+ * instead, since that is where the meaning becomes true.
140
+ */
141
+ kind?: ValueKind;
142
+ /** Whether the value can be written back to the device (a setter exists). */
143
+ writable: boolean;
144
+ /** Allowed values for `type: "enum"` (raw → label). */
145
+ enumValues?: Record<number, string>;
146
+ /**
147
+ * Trust level of this `paramType` mapping. Default (absent) = `guessed`. Anything still `guessed`
148
+ * is a candidate for confirmation against a first-party source, never relied on.
149
+ */
150
+ provenance?: PropertySource;
151
+ /**
152
+ * Wire polarity for a `bool` property whose param is a *disable* flag: when `true`, a raw
153
+ * `0`/`false` means the property is TRUE (e.g. a camera's `enabled` — the flag is the
154
+ * disable bit, so `"0"` ⇒ enabled). Ignored for non-bool types.
155
+ */
156
+ invert?: boolean;
157
+ /**
158
+ * Custom decode for a value the wire delivers as a code the app *reinterprets* — a bitfield/enum
159
+ * that means something other than its face value. Given the raw param value, returns the decoded
160
+ * property value. When present it REPLACES the default type-coercion (and `invert`). Example: the
161
+ * battery `charging` flag is derived from the reported charge status, matching the app's
162
+ * own Hermes decode — see `capabilities/battery.ts`.
163
+ */
164
+ decode?: (raw: string | number | boolean) => boolean | number | string;
165
+ /**
166
+ * This param carries a STRUCTURED PAYLOAD rather than a scalar — a base64 protobuf that the
167
+ * capability's own getter reads a field out of, with the injected codec in scope.
168
+ *
169
+ * The stored value is that payload verbatim, and {@link type} describes what the getter ANSWERS
170
+ * rather than what arrives on the wire. Those are different for every Raw DP on the clean line: nine
171
+ * consumable counters are `"number"` over one base64 string, and reading the value as a number is
172
+ * exactly what must NOT happen at ingest.
173
+ *
174
+ * Storage already keeps such a value intact — a non-numeric string cannot be coerced to a number, so
175
+ * it is passed through. What this flag changes is that the pass-through stops being reported as a
176
+ * mistake: a robot reporting ten Raw DPs on every push logged ten warnings a time saying its
177
+ * properties were misdeclared, which is how a real warning goes unread.
178
+ */
179
+ raw?: true;
180
+ /**
181
+ * Extra wire param ids that ALSO carry this property on some device families, with their own
182
+ * polarity. The device's own `paramType` wins; otherwise the first alias the device reports wins.
183
+ * Lets one property (e.g. `enabled`) read correctly across families that report it under
184
+ * different ids (a battery camera and a standalone one disagree) — the family variance lives
185
+ * in the capability spec, not in per-device branches.
186
+ */
187
+ readAliases?: ReadonlyArray<{
188
+ paramType: number;
189
+ invert?: boolean;
190
+ }>;
191
+ /** Short description for docs / discovery. */
192
+ description?: string;
193
+ }
194
+ /**
195
+ * A decoded property value. Most params are scalar, but some carry a structured payload that the
196
+ * device delivers **encoded** (base64-wrapped JSON, or a JSON string) — e.g. motion-detection
197
+ * zones, privacy zones, guard-mode configs. Those are decoded to the object/array form.
198
+ */
199
+ export type ParamValue = boolean | number | string | Record<string, unknown> | unknown[];
200
+ /** How a param's wire value is encoded (when it isn't a plain scalar). */
201
+ export type ParamEncoding = "base64+json" | "json";
202
+ /** A live property value plus the param_type it came from. */
203
+ export interface PropertyValue {
204
+ name: string;
205
+ paramType: number;
206
+ value: ParamValue;
207
+ /** When the value was last observed (epoch ms). */
208
+ ts: number;
209
+ }
210
+ /**
211
+ * One property whose value moved — the payload of a property-change announcement.
212
+ *
213
+ * Identified by property NAME and nothing else. The name is unique per device, is what `applyParams`
214
+ * already answers with, and is the key `Device.getProperty` takes — so a caller can re-read
215
+ * immediately. No wire id travels with it: resolving several ids to one property is the whole job the
216
+ * param → spec map does, and handing the id back out undoes it and gives a caller a second identifier
217
+ * to key on, which then breaks on the family where that property's read alias is promoted. The ids stay
218
+ * available through `inspectDevice` and `Device.describe()`.
219
+ *
220
+ * The capability accessor behind the name is published by `Device.describe()` as the
221
+ * `{ accessor, property }` pair, joined once at setup.
222
+ */
223
+ export interface PropertyChange {
224
+ /** The property whose value moved — a key of this device's own schema. */
225
+ property: string;
226
+ /**
227
+ * What {@link Device.getProperty} now serves for this property, narrowed to its declared type the same
228
+ * way a capability getter narrows it.
229
+ *
230
+ * Read out of live state, never re-converted from the wire, so it cannot disagree with the getter
231
+ * beside it. Said as "what `getProperty` serves" rather than "what the getter answers" because a
232
+ * schema property does not always HAVE a typed getter: an `unexposed` member is reported and readable
233
+ * but has no confirmed meaning for its value, so promising the getter here would be a claim this SDK
234
+ * has not made anywhere else.
235
+ *
236
+ * Absent where no scalar can honestly be given: a property whose stored value is a PAYLOAD rather than
237
+ * the value (see {@link PropertySpec.raw}), and one whose stored value does not match its declared
238
+ * type. In both cases the honest answer is "this moved, re-read it".
239
+ */
240
+ value?: boolean | number | string;
241
+ }
242
+ /**
243
+ * A capability module = property schema + detection + inbound decode + outbound commands.
244
+ * Written ONCE, reused by every device that lists the capability. This is how "extra bits" (a
245
+ * camera's light, pan-tilt, doorbell button) attach without subclassing. The full shape lives in
246
+ * `capabilities/types.ts`; re-exported here so existing `./types` importers keep working.
247
+ */
248
+ export type { CapabilityModule, DetectionSpec, CapabilityFrame, CapabilityEvent, CommandContext, AvailabilityContext, CapabilityActions, } from "./capabilities/types.js";
249
+ /**
250
+ * A curated registry row (tier 1). Keyed by model (T-code) in the registry. Everything is
251
+ * optional except `codec` — capabilities can also come from the category default or inference.
252
+ */
253
+ export interface RegistryEntry {
254
+ codec: Codec;
255
+ /** Capabilities to attach in addition to the codec's baseline. */
256
+ caps?: Capability[];
257
+ /** Pretty display name. */
258
+ name?: string;
259
+ /**
260
+ * Per-device one-off behaviour. Quarantined to this row — the escape hatch that replaces
261
+ * subclassing. Keys are command names; values are device-specific implementations.
262
+ */
263
+ overrides?: Record<string, unknown>;
264
+ }
265
+ /**
266
+ * The minimal slice of a cloud device record the model needs to classify + infer. The real
267
+ * record (`EufyDevice.raw`) has far more; we read only these.
268
+ */
269
+ export interface CloudRecord {
270
+ /** eufy's own numeric DeviceType (authoritative classifier). */
271
+ deviceType?: number;
272
+ /** Model / product code (T-code), e.g. "T8423". */
273
+ model?: string;
274
+ /** Anker category string, e.g. "eufy_security". */
275
+ category?: string;
276
+ /**
277
+ * What the user named this device in the app (`device_name`), when the record carries one. Not a
278
+ * classification signal — carried so a device answers with the name its owner sees.
279
+ */
280
+ name?: string;
281
+ /**
282
+ * The parent HomeBase's serial when this device hangs off one (`parent_sn` ≠ own sn); absent when
283
+ * the device stands alone. A topology signal, not a param — used by {@link resolveDevice} to withhold
284
+ * station-scoped capabilities (guard-mode `arming`) from a camera behind a HomeBase, where the
285
+ * HomeBase owns them.
286
+ */
287
+ parentSn?: string;
288
+ /** Reported param_type → raw value. Presence of a param is a capability signal. */
289
+ params?: Record<number, string>;
290
+ }
291
+ /** The fully-resolved device shape produced by the 3-tier resolver. */
292
+ export interface ResolvedDevice {
293
+ codec: Codec;
294
+ capabilities: Capability[];
295
+ /** Merged, de-duplicated property schema from all resolved capabilities. */
296
+ properties: PropertySpec[];
297
+ /** Display name (curated row → inferred → model code). */
298
+ name: string;
299
+ /** How the codec/caps were resolved, for diagnostics. */
300
+ source: "model" | "category" | "inferred";
301
+ }
@@ -0,0 +1,92 @@
1
+ import type { MapPose, VacuumMapBackup, VacuumMapDescription, VacuumMapPlane, VacuumRestrictedZones, VacuumRoom, VacuumRoomOutline, VacuumRoomParams } from "./vacuum-map.js";
2
+ /** Everything the store currently holds about one map. Any piece may be absent until it arrives. */
3
+ export interface VacuumMapSnapshot {
4
+ /**
5
+ * Which map these pieces describe, or `undefined` when nothing carrying an id has arrived yet.
6
+ *
7
+ * A robot with several floors saved sends whichever is loaded. When this changes, every piece below
8
+ * is from the new map — the store does not merge across maps.
9
+ */
10
+ readonly mapId: number | undefined;
11
+ readonly plane: VacuumMapPlane | undefined;
12
+ readonly outline: VacuumRoomOutline | undefined;
13
+ readonly rooms: VacuumRoomParams | undefined;
14
+ readonly zones: VacuumRestrictedZones | undefined;
15
+ readonly description: VacuumMapDescription | undefined;
16
+ /**
17
+ * Where the robot was when it last said so.
18
+ *
19
+ * Kept across a map switch, unlike everything else: a pose is a position in the world and does not
20
+ * belong to a map. It may fall outside the new map's bounds, in which case a lookup answers nothing.
21
+ */
22
+ readonly pose: MapPose | undefined;
23
+ }
24
+ /** A piece the store can be given, tagged by which one it is. */
25
+ export type VacuumMapPiece = {
26
+ readonly kind: "plane";
27
+ readonly value: VacuumMapPlane;
28
+ } | {
29
+ readonly kind: "outline";
30
+ readonly value: VacuumRoomOutline;
31
+ } | {
32
+ readonly kind: "rooms";
33
+ readonly value: VacuumRoomParams;
34
+ } | {
35
+ readonly kind: "zones";
36
+ readonly value: VacuumRestrictedZones;
37
+ } | {
38
+ readonly kind: "description";
39
+ readonly value: VacuumMapDescription;
40
+ } | {
41
+ readonly kind: "pose";
42
+ readonly value: MapPose;
43
+ } | {
44
+ readonly kind: "backup";
45
+ readonly value: VacuumMapBackup;
46
+ };
47
+ /**
48
+ * Holds the current map for one device, and answers questions about it.
49
+ *
50
+ * Created empty and filled by {@link VacuumMapStore.apply}. Every getter answers `undefined` until the
51
+ * pieces it needs have arrived, rather than answering from a partial map.
52
+ */
53
+ export declare class VacuumMapStore {
54
+ private current;
55
+ /** The highest revision seen per piece, so an out-of-order repeat cannot overwrite a newer one. */
56
+ private readonly revisions;
57
+ /** What the store holds right now. A new object whenever anything changed, the same one when not. */
58
+ get snapshot(): VacuumMapSnapshot;
59
+ /**
60
+ * Take one piece, and say whether it changed anything.
61
+ *
62
+ * `false` means the piece was stale — an older revision of a map already held — and was dropped. The
63
+ * device repeats its map frequently, so the return is what tells a repeat from a change.
64
+ */
65
+ apply(piece: VacuumMapPiece): boolean;
66
+ /** Forget everything. For a device going away, or a caller starting over. */
67
+ clear(): void;
68
+ /**
69
+ * Which room the robot is standing in, or `undefined`.
70
+ *
71
+ * `undefined` covers every honest reason there is no answer: no pose yet, no room outline yet, no
72
+ * room list yet, a robot outside the mapped area, or a cell belonging to no room. None of those is
73
+ * an error, and none should be reported as a guess.
74
+ */
75
+ get currentRoom(): VacuumRoom | undefined;
76
+ /**
77
+ * Apply a whole `MapBackup` as the several pieces it contains.
78
+ *
79
+ * Each part goes through the same path a lone piece takes, so a backup cannot install something a
80
+ * live frame would have rejected. Parts the device omitted are skipped rather than clearing what is
81
+ * held: a backup carrying only a description is a rename.
82
+ */
83
+ private applyBackup;
84
+ /**
85
+ * Store one stamped piece, dropping it when it is older than the piece of its kind already held.
86
+ *
87
+ * The map-switch check comes first and is the important one: a piece naming a different map is not
88
+ * an update, it is a different map. Everything held describes the old one, and a room outline read
89
+ * with the new map's coordinates gives a wrong answer that looks exactly like a right one.
90
+ */
91
+ private put;
92
+ }