@mega-yfue/eufy-sdk 0.2.0-beta.5 → 0.2.0-beta.7

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.
@@ -0,0 +1,85 @@
1
+ import type { CapabilityModule } from "./types.js";
2
+ import { type Surface } from "./members.js";
3
+ /**
4
+ * `display` — a eufy Smart Display's charge.
5
+ *
6
+ * One typed read, and the smallness is the finding rather than a gap. The captured unit (a T87A0,
7
+ * 2026-09-04) connects over secure MQTT with no `p2p_did`, so it speaks no P2P at all, and it reported
8
+ * six params in an id range no other line uses. Only one of them tells a caller something it could not
9
+ * get another way:
10
+ *
11
+ * - 8005 and 8006 restate the model's retail name and code, which `info` already answers from the
12
+ * curated registry and the cloud record. Their evidence IS that agreement, so a getter beside `info`
13
+ * would offer a caller a second spelling of what it just read. They are named in the display param
14
+ * dictionary, which is what makes them readable off `getProperties()`.
15
+ * - 8003 is version-shaped and nothing confirms what it means, so it is `unexposed`: in the schema with
16
+ * its type and its `guessed` label, reachable through `getProperty`, no typed getter.
17
+ *
18
+ * Nothing is writable. No capture pins a write for any display param, and an AIoT write is
19
+ * fire-and-forget, so a wrong frame to a device that acknowledges nothing looks exactly like success. A
20
+ * screen, a volume, an assistant: the device plainly has all three and reports none of them in anything
21
+ * captured, which means the reads have to arrive before a control can be honest about what it moves.
22
+ *
23
+ * @module model/capabilities/display
24
+ */
25
+ /** Smart Display param ids this capability reads. The rest of the range is named in the dictionary. */
26
+ export declare const DISPLAY_PARAM: {
27
+ readonly BATTERY: 8001;
28
+ readonly SOFTWARE_VERSION: 8003;
29
+ };
30
+ /**
31
+ * The `display` reads. Read-only; see the module note for why there is no write.
32
+ *
33
+ * Exported but NOT published: the entries state their wire ids and what each claim rests on, which the
34
+ * reference site does not carry.
35
+ * @internal
36
+ */
37
+ export declare const DISPLAY_MEMBERS: {
38
+ /**
39
+ * Battery level, 0-100.
40
+ *
41
+ * **On this capability rather than on `battery`, and that is the line partition doing its job.** The
42
+ * security-line `battery` capability reads param 1101 and a Smart Display's charge is 8001 in its own
43
+ * id space — two wires that happen to mean the same thing. One capability reading both would be a claim
44
+ * that the two ecosystems share a param space, so a consumer reads a display's charge through
45
+ * `dev.display()` and a camera's through `dev.battery()`.
46
+ *
47
+ * `verified` rather than `mega`: the id is in the device's cloud record, but the NAME came from the
48
+ * maintainer's own knowledge of the hardware rather than from the cloud data-point list, and `"100"`
49
+ * fits brightness, volume or charge equally.
50
+ *
51
+ * The scale is `percent` on the reading itself, not on convention alone: a full charge reads `255` on a
52
+ * 0-255 scale and `1000` on a 0-1000 one, so `"100"` on a charged unit is positive evidence for 0-100
53
+ * rather than merely consistent with it. What nobody has done is watch it MOVE, which is why a value
54
+ * frozen at 100 would not yet be distinguishable from a healthy one.
55
+ */
56
+ readonly battery: {
57
+ readonly param: 8001;
58
+ readonly type: "number";
59
+ readonly unit: "%";
60
+ readonly kind: "percent";
61
+ readonly provenance: "verified";
62
+ readonly description: "Battery level, 0-100 (param 8001).";
63
+ };
64
+ /**
65
+ * Version-shaped, and that shape is the whole of the evidence — hence `guessed`, and hence no typed
66
+ * getter: a caller reading this off a bound object cannot see the label.
67
+ *
68
+ * `unexposed` rather than absent, so the schema still carries its type and that label and
69
+ * `getProperty("softwareVersion")` still answers. `dev.info()?.firmwareVersion` is the field to trust
70
+ * where the cloud record carries one; on this display it does not, which is the only reason 8003 is
71
+ * named at all.
72
+ */
73
+ readonly softwareVersion: {
74
+ readonly param: 8003;
75
+ readonly type: "string";
76
+ readonly kind: "text";
77
+ readonly provenance: "guessed";
78
+ readonly unexposed: true;
79
+ readonly description: "Version-shaped string (param 8003), meaning unconfirmed — prefer `info.firmwareVersion`.";
80
+ };
81
+ };
82
+ /** `display` — a eufy Smart Display (T87Ax). Read-only; no display write is captured. */
83
+ export declare const DISPLAY: CapabilityModule;
84
+ /** Bound Smart Display reads — the object returned by `dev.display()`. Read-only. */
85
+ export type DisplayActions = Surface<typeof DISPLAY_MEMBERS>;
@@ -40,6 +40,7 @@ import type { KeypadActions } from "./keypad.js";
40
40
  import type { RtspActions } from "./rtsp.js";
41
41
  import type { VacuumCleanActions } from "./vacuum-clean.js";
42
42
  import type { VacuumDockActions } from "./vacuum-dock.js";
43
+ import type { DisplayActions } from "./display.js";
43
44
  import type { SuctionActions } from "./suction.js";
44
45
  import type { LocateActions } from "./locate.js";
45
46
  import type { DeviceInfo } from "./info.js";
@@ -411,6 +412,8 @@ export interface DeviceActionMap {
411
412
  suction: SuctionActions;
412
413
  /** RoboVac locate (find-robot beep): `locating`; `locate(on?)`. */
413
414
  locate: LocateActions;
415
+ /** Smart Display (read-only): `battery`. No display write is captured. */
416
+ display: DisplayActions;
414
417
  /** Identity metadata (read-only): `{ manufacturer, model, serialNumber, name, deviceType?, firmwareVersion?, hardwareVersion? }`. */
415
418
  info: DeviceInfo;
416
419
  }
@@ -529,6 +532,7 @@ export { RTSP_MEMBERS } from "./rtsp.js";
529
532
  export { SIREN_MEMBERS } from "./siren.js";
530
533
  export { SMART_LIGHT_MEMBERS } from "./smart-light.js";
531
534
  export { SMOKE_MEMBERS } from "./smoke.js";
535
+ export { DISPLAY_MEMBERS, type DisplayActions } from "./display.js";
532
536
  export { SUCTION_MEMBERS } from "./suction.js";
533
537
  export { VACUUM_CLEAN_MEMBERS } from "./vacuum-clean.js";
534
538
  export type { DeviceInfo } from "./info.js";
@@ -41,12 +41,19 @@ export interface DetectionSpec {
41
41
  *
42
42
  * eufy ships several ecosystems that share a cloud account and nothing else: `security` (cameras,
43
43
  * stations, locks, sensors — P2P plus the security-scoped broker), `life` (the T8L0x smart-lighting
44
- * line — its own credential and its own DP wire), and `clean` (robot vacuums — Tuya data points).
44
+ * line — its own credential and its own DP wire), `clean` (robot vacuums — Tuya data points) and
45
+ * `display` (the T87Ax Smart Display — secure MQTT, never P2P, its own 8001-8006 param space).
45
46
  * They overlap in retail vocabulary but share no wire, no param space and no semantics.
46
47
  *
48
+ * `display` is a line of its own for the second of those reasons rather than the first: without it,
49
+ * every security capability detected by a NAME regex is attachable to a Smart Display — measured at six,
50
+ * on a device that can answer for none of them because it speaks no P2P at all. A line holding one
51
+ * capability still buys that, which is why the count is not the measure of whether a line is worth
52
+ * declaring.
53
+ *
47
54
  * `any` is for the handful of capabilities that are genuinely line-independent (device identity).
48
55
  */
49
- export type ProductLine = "security" | "life" | "clean" | "print" | "any";
56
+ export type ProductLine = "security" | "life" | "clean" | "print" | "display" | "any";
50
57
  /**
51
58
  * A structural subset of a P2P frame. Deliberately NOT `import`ed from `p2p/*` — keeping it
52
59
  * structural avoids a model→p2p cycle, and the real `P2PFrame` is assignable to it. It is the
@@ -26,3 +26,27 @@ export interface ParamDef {
26
26
  export declare const SECURITY_PARAMS: Record<number, ParamDef>;
27
27
  /** RoboVac Tuya DP space (ids ~150-180), from get_product_data_point. */
28
28
  export declare const CLEAN_PARAMS: Record<number, ParamDef>;
29
+ /**
30
+ * eufy Smart Display (T87Ax) param space — ids 8001-8006.
31
+ *
32
+ * Its own table rather than a corner of {@link SECURITY_PARAMS}: nothing in the 8000s carries a security
33
+ * meaning, so reading these ids there would decode a future security param assigned in this range as
34
+ * whatever it means on a camera.
35
+ *
36
+ * Every id here was reported by a live T87A0 (captured 2026-09-04). That the device SENT an id is what
37
+ * earns it a place in this table; the provenance label beside each one rates something narrower — how far
38
+ * its NAME is trusted. `modelName` and `modelCode` are `mega`, their values matching what the cloud
39
+ * record already carried. `battery` is `verified`: the id is real and the reading consistent, but the
40
+ * name came from the maintainer's own knowledge of the hardware rather than from the cloud data-point
41
+ * list, and `"100"` fits brightness, volume or charge equally.
42
+ *
43
+ * A dictionary entry is what makes a param readable by name off `getProperties()`. `capabilities/display.ts`
44
+ * decides separately which of them reach the typed surface, and only `battery` does.
45
+ *
46
+ * **8002 (`"1"`) and 8004 (a serial-shaped string) are absent, deliberately.** Neither meaning is
47
+ * legible from one value: `1` fits any enum or flag, and a serial could be the display's own or the
48
+ * station's it is bound to. They arrive as `unknown_8002` / `unknown_8004`, which is the measure of this
49
+ * list: it holds what is unknown, not what is unknowable. What would settle them: the vendor app's own
50
+ * display settings screen, one control at a time, params diffed after each.
51
+ */
52
+ export declare const DISPLAY_PARAMS: Record<number, ParamDef>;
@@ -14,7 +14,7 @@
14
14
  import type { Codec } from "./types.js";
15
15
  import { type ParamDef } from "./param-dictionary.js";
16
16
  /** The param id spaces this SDK models. */
17
- export type ParamNamespace = "security" | "clean" | "life" | "print";
17
+ export type ParamNamespace = "security" | "clean" | "life" | "print" | "display";
18
18
  /** Look up a param def in the given namespace. */
19
19
  export declare function paramDef(ns: ParamNamespace, paramType: number): ParamDef | undefined;
20
20
  /** The param namespace a device's ids live in, from its codec, via the module-local `NAMESPACE_BY_CODEC` table. */
@@ -28,10 +28,10 @@
28
28
  *
29
29
  * `display` is the T87Ax Smart Display line — its own codec because its `device_type` collides with
30
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).
31
+ * `p2p_did`, never P2P). It owns its own param namespace (ids 8001-8006) and its own product line, so
32
+ * no other line's capability can attach to it — see `namespaceForCodec` and `CODEC_LINE`. The `display`
33
+ * capability targets it and reads the charge; no screen/audio/assistant param has been observed, so
34
+ * those are absent rather than deferred.
35
35
  */
36
36
  export type Codec = "station" | "camera" | "sensor" | "lock" | "keypad" | "vacuum" | "mower" | "light" | "printer" | "display";
37
37
  /**
@@ -39,7 +39,7 @@ export type Codec = "station" | "camera" | "sensor" | "lock" | "keypad" | "vacuu
39
39
  * it maps to a {@link CapabilityModule} that owns its property schema. Extend this union as
40
40
  * new capabilities are modelled — adding one never requires a subclass.
41
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";
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" | "display" | "info";
43
43
  /** Value type of a property. */
44
44
  export type PropertyValueType = "bool" | "number" | "string" | "enum";
45
45
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mega-yfue/eufy-sdk",
3
- "version": "0.2.0-beta.5",
3
+ "version": "0.2.0-beta.7",
4
4
  "description": "One typed TypeScript client for the Anker eufy v6 cloud — capability-driven devices, realtime events over P2P/MQTT/push, and live media",
5
5
  "license": "Apache-2.0",
6
6
  "author": "mega-yfue",