@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
new Device(
sn,
resolved,
logger?
): Device;Parameters
sn
string
resolved
logger?
Logger = noopLogger
Returns
Device
Properties
arming?
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?
optional audio?: () => AudioActions | undefined;Audio/volume (family-gated): mic, speaker + volume, in-video recording, doorbell ringtone, HomeBase alarm/prompt.
Returns
AudioActions | undefined
battery?
optional battery?: () => BatteryActions | undefined;Battery/power: power source, working mode, and the custom-mode clip/interval/auto-stop settings.
Returns
BatteryActions | undefined
camera?
optional camera?: () => CameraActions | undefined;Camera: on/off, privacy, status LED; snapshot/live/record when bound to a live client.
Returns
CameraActions | undefined
capabilities
capabilities: readonly Capability[];Resolved capability set. Widens if the device later reports evidence for more.
co?
optional co?: () => CoActions | undefined;CO-detector reads (read-only): coDetected, lastSeen.
Returns
CoActions | undefined
codec
codec: Codec;Resolved command-codec family.
contact?
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?
optional doorbell?: () => DoorbellActions | undefined;Doorbell: playQuickResponse(voiceId) (the canned voice replies).
Returns
DoorbellActions | undefined
info?
optional info?: () => DeviceInfo | undefined;Identity metadata (read-only): { manufacturer, model, serialNumber, name, deviceType?, firmwareVersion?, hardwareVersion? }.
Returns
DeviceInfo | undefined
keypad?
optional keypad?: () => KeypadActions | undefined;Security-keypad reads (read-only): rssi.
Returns
KeypadActions | undefined
leak?
optional leak?: () => LeakActions | undefined;Water-leak / freeze sensor reads (read-only): leakDetected, lastSeen.
Returns
LeakActions | undefined
light?
optional light?: () => LightActions | undefined;Floodlight/spotlight: on/off/set, setBrightness/setColorTemp/setEnabled, setAutoSpotlight.
Returns
LightActions | undefined
locate?
optional locate?: () => LocateActions | undefined;RoboVac locate (find-robot beep): locating; locate(on?).
Returns
LocateActions | undefined
lock?
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?
optional model?: string;Model / T-code from the record ("T8410"), when known.
modelName
modelName: string;The model's display name ("Indoor Cam Pan & Tilt") — the product this unit is one of.
motion?
optional motion?: () => MotionActions | undefined;Motion/PIR: setDetection(on) (sensitivity is read-only until its write wire is captured).
Returns
MotionActions | undefined
name
name: string;What the user named this device in the app (device_name), falling back to modelName when the record carries none.
properties
properties: readonly PropertySpec[];Merged property schema (one entry per known property this device exposes).
ptz?
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?
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?
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?
optional smartLight?: () => SmartLightActions | undefined;eufy_life smart light (T8L0x): on/off, setBrightness, T8L02 setColor, setEffect(lightId).
Returns
SmartLightActions | undefined
smoke?
optional smoke?: () => SmokeActions | undefined;Smoke-detector reads (read-only): smokeDetected, lastSeen.
Returns
SmokeActions | undefined
sn
readonly sn: string;Serial number (station/device SN).
source
source: "category" | "model" | "inferred";Which resolver tier produced the codec/caps.
stationSn
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?
optional suction?: () => SuctionActions | undefined;RoboVac suction: level, boostIq, supportedLevels; setSuctionLevel, setBoostIq.
Returns
SuctionActions | undefined
vacuumClean?
optional vacuumClean?: () =>
| VacuumCleanActions
| undefined;RoboVac core state and controls: power, activity (WorkStatus), volume, battery, cleanType; setPower, startCleaning, returnToDock, pauseCleaning.
Returns
| VacuumCleanActions | undefined
vacuumDock?
optional vacuumDock?: () => VacuumDockActions | undefined;RoboVac Omni dock controls: emptyDust, washMops, dryMops; raw dockState unexposed until wire confirmed.
Returns
VacuumDockActions | undefined
Methods
announcements()
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
applyParams()
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
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()
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
sink
media?
ff09Settings?
rawDp?
Returns
void
describe()
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
getProperties()
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()
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()
has(cap): boolean;Does this device have the given capability?
Parameters
cap
Returns
boolean
hasProperty()
hasProperty(name): boolean;Is the given property name part of this device's schema?
Parameters
name
string
Returns
boolean
reresolve()
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
Returns
setFreshnessPolicy()
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()
toJSON(): Record<string, unknown>;Plain-object view: serial, name, resolved codec/source, capabilities, and every current property value.
Returns
Record<string, unknown>
fromRecord()
static fromRecord(
sn,
rec,
logger?
): Device;Build a Device from a raw cloud record (runs the 3-tier resolver).
Parameters
sn
string
rec
logger?
Logger = noopLogger
Returns
Device