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
optional arming?: () => ArmingActions | undefined;

Guard mode: setMode(ArmingMode) + setAlarmDelayConfig(mode, config). Of the 8 ArmingMode values only away/home/disarmed are confirmed on-device.

Returns

ArmingActions | undefined


audio?

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

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

Returns

AudioActions | undefined


battery?

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

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

Returns

BatteryActions | undefined


camera?

ts
optional 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
optional co?: () => CoActions | undefined;

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

Returns

CoActions | undefined


codec

ts
codec: Codec;

Resolved command-codec family.


contact?

ts
optional 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


doorbell?

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

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

Returns

DoorbellActions | undefined


info?

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

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

Returns

DeviceInfo | undefined


keypad?

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

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

Returns

KeypadActions | undefined


leak?

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

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

Returns

LeakActions | undefined


light?

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

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

Returns

LightActions | undefined


locate?

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

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

Returns

LocateActions | undefined


lock?

ts
optional 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
optional 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
optional motion?: () => MotionActions | undefined;

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

Returns

MotionActions | 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
optional 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
optional 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
optional 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


smartLight?

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

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

Returns

SmartLightActions | undefined


smoke?

ts
optional 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
optional suction?: () => SuctionActions | undefined;

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

Returns

SuctionActions | undefined


vacuumClean?

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

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

Returns

| VacuumCleanActions | undefined


vacuumDock?

ts
optional 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.

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.