Skip to content

@mega-yfue/eufy-sdk / Device

Class: Device ​

A eufy device: one class, capability-driven. Construct from a resolved record (or a raw CloudRecord via Device.fromRecord), then feed it param updates.

Extends ​

Constructors ​

Constructor ​

ts
new Device(
   sn, 
   resolved, 
   logger?
): Device;

Parameters ​

sn ​

string

resolved ​

ResolvedDevice

logger? ​

Logger = noopLogger

Returns ​

Device

Properties ​

arming? ​

ts
arming?: () => ArmingActions | undefined;

Guard mode: setMode(ArmingMode) + setAlarmDelayConfig(mode, config). Of the 9 modes a station reports, only away/home/custom1/disarmed are confirmed as writes (ArmingMode); the alarm-delay write takes the narrower byte-captured AlarmDelayMode.

Returns ​

ArmingActions | undefined


audio? ​

ts
audio?: () => AudioActions | undefined;

Audio/volume (family-gated): mic, speaker + volume, in-video recording, doorbell ringtone, HomeBase alarm/prompt.

Returns ​

AudioActions | undefined


battery? ​

ts
battery?: () => BatteryActions | undefined;

Battery/power: power source, working mode, and the custom-mode clip/interval/auto-stop settings.

Returns ​

BatteryActions | undefined


camera? ​

ts
camera?: () => CameraActions | undefined;

Camera: on/off, privacy, status LED; snapshot/live/record when bound to a live client.

Returns ​

CameraActions | undefined


capabilities ​

ts
capabilities: readonly Capability[];

Resolved capability set. Widens if the device later reports evidence for more.


co? ​

ts
co?: () => CoActions | undefined;

CO-detector reads (read-only): coDetected, lastSeen.

Returns ​

CoActions | undefined


codec ​

ts
codec: Codec;

Resolved command-codec family.


contact? ​

ts
contact?: () => ContactActions | undefined;

Entry (door/window) sensor: reads open, lastSeen, rssi, alarmSoundType, alarmVolume; writes setAlarmSoundType, setAlarmVolume (present when the sensor reports the alarm params).

Returns ​

ContactActions | undefined


display? ​

ts
display?: () => DisplayActions | undefined;

Smart Display (read-only): battery. No display write is captured.

Returns ​

DisplayActions | undefined


doorbell? ​

ts
doorbell?: () => DoorbellActions | undefined;

Doorbell: playQuickResponse(voiceId) (the canned voice replies).

Returns ​

DoorbellActions | undefined


info? ​

ts
info?: () => DeviceInfo | undefined;

Identity metadata (read-only): { manufacturer, model, serialNumber, name, deviceType?, firmwareVersion?, hardwareVersion? }.

Returns ​

DeviceInfo | undefined


keypad? ​

ts
keypad?: () => KeypadActions | undefined;

Security-keypad reads (read-only): charging, rssi.

Returns ​

KeypadActions | undefined


leak? ​

ts
leak?: () => LeakActions | undefined;

Water-leak / freeze sensor reads (read-only): leakDetected, lastSeen.

Returns ​

LeakActions | undefined


light? ​

ts
light?: () => LightActions | undefined;

Floodlight/spotlight: on/off/set, setBrightness/setColorTemp/setEnabled, setAutoSpotlight.

Returns ​

LightActions | undefined


locate? ​

ts
locate?: () => LocateActions | undefined;

RoboVac locate (find-robot beep): locating; locate(on?).

Returns ​

LocateActions | undefined


lock? ​

ts
lock?: () => LockActions | undefined;

Smart lock: lock()/unlock() + verified setRainMode/setAutoLock. The 6 source-confirmed-but-uncaptured setting toggles are ABSENT until captured (present method ⇒ wire verified).

Returns ​

LockActions | undefined


model? ​

ts
model?: string;

Model / T-code from the record ("T8410"), when known.


modelName ​

ts
modelName: string;

The model's display name ("Indoor Cam Pan & Tilt") — the product this unit is one of.


motion? ​

ts
motion?: () => MotionActions | undefined;

Motion/PIR: setDetection(on) (sensitivity is read-only until its write wire is captured).

Returns ​

MotionActions | undefined


mower? ​

ts
mower?: () => MowerActions | undefined;

Robot mower (read-only): battery, cutHeight, network, wifiSignal, wear timers and settings. No write is captured.

Returns ​

MowerActions | undefined


name ​

ts
name: string;

What the user named this device in the app (device_name), falling back to modelName when the record carries none.


properties ​

ts
properties: readonly PropertySpec[];

Merged property schema (one entry per known property this device exposes).


ptz? ​

ts
ptz?: () => PtzActions | undefined;

Pan/tilt/zoom control: rotate(dir, step) + left/right/up/down, zoom, preset() namespace (goto/preview/save/setDefault/delete/list/image).

Returns ​

PtzActions | undefined


rtsp? ​

ts
rtsp?: () => RtspActions | undefined;

RTSP publish for a NAS/NVR: publish/withdraw + published, requireAuth/allowAnonymous, recordingMode/setRecordingMode. One camera at a time per station.

Returns ​

RtspActions | undefined


siren? ​

ts
siren?: () => SirenActions | undefined;

Siren: reads active, volume, alarmDuration, doNotDisturb; writes setVolume, setAlarmDuration, test, stop (config setters present when the param is reported). No direct "sound the alarm" wire — a real alarm is driven by the arming system; test is the on-demand trigger.

Returns ​

SirenActions | undefined


smartDrop? ​

ts
smartDrop?: () => SmartDropActions | undefined;

SmartDrop parcel box: openLid().

Returns ​

SmartDropActions | undefined


smartLight? ​

ts
smartLight?: () => SmartLightActions | undefined;

eufy_life smart light (T8L0x): on/off, setBrightness, T8L02 setColor, setEffect(lightId).

Returns ​

SmartLightActions | undefined


smoke? ​

ts
smoke?: () => SmokeActions | undefined;

Smoke-detector reads (read-only): smokeDetected, lastSeen.

Returns ​

SmokeActions | undefined


sn ​

ts
readonly sn: string;

Serial number (station/device SN).


source ​

ts
source: "category" | "model" | "inferred";

Which resolver tier produced the codec/caps.


stationSn ​

ts
stationSn: string;

The station this device's traffic belongs to: its parent HomeBase, or its own sn when it has none.

Set from the record's parentSn, which is present only for a device that hangs off a base. A record that states none leaves the last known value, as every other identity field here does, so it starts at this device's own serial and every device therefore has a station.


suction? ​

ts
suction?: () => SuctionActions | undefined;

RoboVac suction: level, boostIq, supportedLevels; setSuctionLevel, setBoostIq.

Returns ​

SuctionActions | undefined


vacuumClean? ​

ts
vacuumClean?: () => 
  | VacuumCleanActions
  | undefined;

RoboVac core state and controls: power, activity (WorkStatus), volume, battery, cleanType; setPower, startCleaning, returnToDock, pauseCleaning.

Returns ​

| VacuumCleanActions | undefined


vacuumDock? ​

ts
vacuumDock?: () => VacuumDockActions | undefined;

RoboVac Omni dock controls: emptyDust, washMops, dryMops; raw dockState unexposed until wire confirmed.

Returns ​

VacuumDockActions | undefined

Methods ​

announcements() ​

ts
announcements(changed): PropertyChange[];

Which of these changed property names are worth ANNOUNCING, each with the value getProperty now serves for it — the second half of an applyParams call, and the input a facade turns into a property-change event.

Only a name in this device's own schema survives, and EVERY name in it does. The schema is what the SDK published and getProperty serves every entry of, so announcing one is honest; a dictionary-named param and an unknown_<paramType> passthrough are things the SDK makes no claim about, and announcing either would promise a value it never agreed to serve. Diagnostics reach those through inspectParams.

Nothing is withheld for being uninteresting, here or in a capability's own table. Which of a device's truths a host acts on is the host's call: a withheld value cannot be recovered, where an unwanted one costs a caller one comparison on the name.

The value comes out of live state — written microseconds earlier by the same call that produced changed — through the same narrow the capability getters use, which is what makes an announcement and the getter beside it one answer rather than two. Not from the raw wire value: that is a second conversion and a second answer, which is exactly how a payload comes to disagree with its getter. And not by invoking the installed getter, which has read side effects (a scheduled background refresh, a codec call) an announcement must not trigger — and which an unexposed schema property does not have at all.

Kept beside the state and the schema rather than in a caller, because both are here; a caller doing the join would be re-deriving what this object already holds. Says nothing about the previous value: a caller that needs the delta already holds it, because it was told last time.

Parameters ​

changed ​

readonly string[]

Returns ​

PropertyChange[]


applyParams() ​

ts
applyParams(params, ts?): string[];

Apply a raw param map (cloud record or P2P notification). Known params update their named property; unrecognised params are retained as unknown_<paramType> so nothing is lost.

Naming precedence: this device's own PropertySpec (curated), then the param dictionary for its namespace, then the unknown_<paramType> passthrough. The dictionary def is consulted even where a spec exists, because encoding lives there.

A param a resolved capability's gate WITHHELD takes the passthrough instead of its dictionary name. The gate decided the read does not describe this device, the dictionary names it what the member would have, and republishing it there hands a caller a reading indistinguishable from one the device really answered. A capability that never resolved withholds nothing: a param arriving before its capability is still the device's own, and keeps its dictionary name.

Parameters ​

params ​

RawParams

param_type → raw value.

ts? ​

number = ...

observation time (epoch ms); defaults to Date.now().

Returns ​

string[]

the list of property names whose value changed.


bindActions() ​

ts
bindActions(
   ctx, 
   sink, 
   media?, 
   ff09Settings?, 
   rawDp?
): void;

Wire this device to a CommandSink so its semantic action objects become live (device.light?.on()). ctx carries the evidence a capability uses to resolve the right command variant. Called by EufyMega.getDevice; a raw model object left unbound simply has no action objects (all accessors return undefined). ff09Settings reads that frame family's settings over whichever transport the device has; rawDp reads the structured payloads a few params carry in place of a scalar. Both are optional and both are named for the job, not the caller — a read needing one returns undefined without it. See CapabilityModule.actions's doc before threading a third. The final buildActions arg is the live-state reader backing the capabilities' typed read getters (dev.battery()?.level): it closes over this.getProperty, so a getter built once here stays current as realtime/poll updates land in this.state.

Parameters ​

ctx ​

CommandContext

sink ​

CommandSink

media? ​

MediaProvider

ff09Settings? ​

Ff09SettingsReader

rawDp? ​

RawDpCodec

Returns ​

void


describe() ​

ts
describe(): DeviceManifest;

What this device exposes, as data — every installed read with what its value MEANS, every offerable action with what it accepts, and every event each capability emits.

Beside toJSON rather than folded into it, because the two answer different questions and toJSON fires on every implicit JSON.stringify (an event payload, a log line) where the shape is not wanted. This one carries shape only — no values; read those through the capability getters it names.

The reads and actions are the ones this device actually installed, so a caller can offer everything listed: a write the device gave no evidence for, or one whose wire is not confirmed, is absent rather than described. An unbound device (no live client) has nothing bound to enumerate and answers bound: false with empty details. details is resolved against this device's own facts (codec, model, capabilities), so a per-device enum domain (e.g. workingMode) carries the same options the property schema does.

Returns ​

DeviceManifest


getProperties() ​

ts
getProperties(): Record<string, PropertyValue>;

Snapshot of all current property values (named + unknown passthrough) — the untyped bulk read, for diagnostics / discovery. The typed fluent capability getters (dev.battery()?.level, …) answer one specific known property; this loose map answers every observed value at once. Under a freshness policy, schedules a background refresh when the OLDEST observed value is stale (one fetch covers every param) and returns the current snapshot immediately.

Returns ​

Record<string, PropertyValue>


getProperty() ​

ts
getProperty(name): PropertyValue | undefined;

Low-level property read by name — the untyped escape hatch. The typed fluent capability getters (dev.battery()?.level, dev.contact()?.open) answer the value already narrowed to its declared type where the property is exposed by a capability; this answers the loose PropertyValue.value (boolean|number|string|object). It is the only read for an unbound model object (no live client → no dev.<cap>()) and for a param not yet surfaced on a capability. Returns undefined if never observed.

Parameters ​

name ​

string

Returns ​

PropertyValue | undefined


has() ​

ts
has(cap): boolean;

Does this device have the given capability?

Parameters ​

cap ​

Capability

Returns ​

boolean


hasProperty() ​

ts
hasProperty(name): boolean;

Is the given property name part of this device's schema?

Parameters ​

name ​

string

Returns ​

boolean


reresolve() ​

ts
reresolve(rec): Capability[];

Re-resolve against a fresher record and adopt the result if the capability set grew.

A capability is granted on evidence the device reports, so a device that hadn't reported a param when it was first resolved lacks the capability that param proves — and would keep lacking it for the object's whole lifetime, even as the value itself started arriving. Re-resolving on fresh evidence closes that: the accessor appears, already bound if the device is bound.

Only ever widens. A param the device stops reporting does not retract a capability, because the cloud record is a snapshot that can lose a field for reasons that have nothing to do with the hardware, and revoking an accessor a caller already holds is worse than keeping a quiet one.

Returns the capabilities gained, empty when nothing changed — so a caller can skip re-binding.

Parameters ​

rec ​

CloudRecord

Returns ​

Capability[]


setFreshnessPolicy() ​

ts
setFreshnessPolicy(policy): void;

Wire a read-through freshness policy. When a cached property is older than staleAfterMs, a getProperty/getProperties read schedules ONE coalesced background refresh() (the facade supplies it, choosing the cheapest live transport) and returns the last-known value immediately — reads never block. Push / P2P realtime updates refresh ts themselves via applyParams, so a device kept fresh by realtime never re-fetches (a fresh entry is never stale). Keeps model/ transport-free: the device only calls the injected callback.

Parameters ​

policy ​
refresh ​

() => Promise<void>

staleAfterMs ​

number

Returns ​

void


toJSON() ​

ts
toJSON(): Record<string, unknown>;

Plain-object view: serial, name, resolved codec/source, capabilities, and every current property value.

Returns ​

Record<string, unknown>


fromRecord() ​

ts
static fromRecord(
   sn, 
   rec, 
   logger?
): Device;

Build a Device from a raw cloud record (runs the 3-tier resolver).

Parameters ​

sn ​

string

rec ​

CloudRecord

logger? ​

Logger = noopLogger

Returns ​

Device

Independent and unofficial. Not affiliated with, endorsed by, or sponsored by Anker Innovations, Anker eufy, or eufy. "Anker eufy", "eufy" and "Anker" are trademarks of their respective owners. Use responsibly — rapid or failed logins can trigger captcha or temporary cooldowns.