Connectivity & battery
The SDK owns connectivity — there's no transport to open by hand. The SDK decides when a transport is worth bringing up for the request at hand, keeps battery cameras asleep when nobody is looking, and serves repeat reads from a cache instead of re-hitting the device or the cloud. This page explains what happens automatically and the few knobs you can turn.
What happens on login
A successful login() starts the always-on, battery-safe channels:
- FCM push — the account-wide event stream (motion, doorbell, contact, lock, …). Events flow over push independently of P2P.
- Secure MQTT — started only if the account has appliances (vacuum / light / plug / display).
Neither touches your cameras, so neither drains a battery. P2P is not opened on login — it comes up on demand (below). Wired stations (HomeBases, mains-powered cameras) are the exception: their P2P session is warmed at login and kept persistent, because a wired device doesn't drain and its realtime keeps device state fresh.
const eufy = new EufyMega({ email, password, countryCode });
await eufy.login(); // push + MQTT (if needed) + wired-P2P warm-up, automaticallyTo manage nothing automatically (advanced / tests), construct with { autoRealtime: false }.
Battery cameras: open on demand, detach when idle
A standalone battery camera is only reached over P2P when something actually needs it — a control command, a live stream, a snapshot, or a doorbell pre-warm. After the last of those finishes, the session lingers briefly and then closes, so the camera can return to sleep. A persistent P2P session would keep it awake with a heartbeat every few seconds; opening on demand avoids that.
- Reads don't wake the camera.
dev.getProperty(...)is served from cache / the cloud, never a P2P pull — so a host polling a device every ~15 s does not drain the battery. - Wired cameras / HomeBases stay connected (they don't drain), so their realtime state is always live.
The tier is decided per station (parentSn), and there is one P2P session per station, not per device — only a genuinely standalone battery device idle-detaches.
new EufyMega({
email,
password,
countryCode,
p2pIdleMs: 5 * 60_000, // idle window before a battery station detaches (default 5 min)
});Battery drain has two windows, one per state — the same power model, enforced at two layers:
- Idle (nobody using it) → the session idle-detach above closes the P2P session so the camera sleeps.
- Actively streaming (you're watching) → the stream can't idle-detach because a viewer is attached, so a separate power budget bounds a long continuous stream: after the budget it asks you to keep watching (
extend()), else it auto-stops the stream — which then releases the session and lets the idle-detach take over. Configure/consume it on the stream itself — see Live media → Power budget.
Battery cameras behind a wired HomeBase
A common worry: if a battery camera is attached to a wired HomeBase (whose session stays persistent), is the camera held awake and draining? No. The persistent session is the HomeBase ↔ app link — the base is wired, so its heartbeat drains nothing, and there is no separate always-on P2P link to the child camera. The HomeBase manages its children's sleep/wake itself, whether or not an app session is open.
- The child camera only draws power when you actually start a stream / command on its channel; when the stream ends its shared pull lingers briefly and stops, and the camera sleeps again. Its drain is bounded by the stream lifecycle, not the session's.
- While you're watching an attached battery child, the battery budget still applies (the
poweredhint comes from the child's own capabilities, not the station), so a long continuous stream auto-stops the same way a standalone one does — see Live media. - Events keep flowing over push regardless. So a HomeBase setup is battery-friendly by design: the wired base absorbs the always-on cost, the children sleep. Keying the tier on the station (not the child) is what makes that work — the base session is never torn down to "save" a child that isn't draining from it.
Event pre-warm → instant live view
When a doorbell rings — or the camera detects a person, pet, or package — the event reaches the Home app first; the user often taps to watch a few seconds later. To make that tap start instantly, the SDK speculatively opens the camera's P2P session the moment the event arrives, and holds it for a short window. If you open a live view (or, later, talkback) within the window it starts with no spin-up; if nobody watches, the session detaches.
Pre-warm fires for high-intent events by default — a doorbell ring plus the AI detections personDetected (human), petDetection (animal), and packageDelivered (object). Raw motion is excluded: a battery camera sees it constantly, which would defeat the idle-detach. Set your own list to override (e.g. add "motion" for a wired camera, or narrow it to just the doorbell).
new EufyMega({
email,
password,
countryCode,
// Defaults shown — override to taste.
prewarmEvents: ["doorbellPress", "personDetected", "petDetection", "packageDelivered"],
prewarmMs: 28_000, // how long to hold the warm session (default)
});Read-through cache
Reads are served from an in-memory cache and only re-fetched when stale. A getProperty / getProperties on a value older than the freshness window schedules one coalesced background refresh (via the cheapest available path) and returns the last-known value immediately — reads never block, and a burst of reads collapses to a single refresh.
- Realtime keeps values fresh. Push and P2P updates land in the same cache, so a device kept fresh by realtime is never considered stale and never triggers a fetch — a wired camera with a live P2P session serves reads with zero cloud calls.
- Reuse the
Device. The cache lives on theDeviceobject, so hold onto the one fromgetDevice(sn)and read from it repeatedly; callinggetDevice(sn)afresh each time re-fetches.
new EufyMega({
email,
password,
countryCode,
cacheTtlMs: 15_000, // freshness window for cached reads (default 15 s)
});Is a device reachable?
The SDK reports facts about a device's liveness and leaves the verdict to you:
const s = eufy.deviceState(sn);
// { sn, stationSn, lastSeenMs? }
eufy.on("deviceState", (s) => {
// fires when a device reports to the cloud
});lastSeenMs is when the device last reported to the cloud, in ms, comparable to Date.now(). It works the same way for every device class — cameras, sensors and MQTT appliances alike — so it's the one portable liveness signal to build on.
There is deliberately no online: boolean. "Unreachable" is a threshold, and the right threshold differs per device: a mains camera reports constantly, while a battery contact sensor can be silent for days by design and be perfectly healthy. If the SDK picked one number it would be wrong for somebody — so you decide:
const stale = Date.now() - (s.lastSeenMs ?? 0) > myThresholdFor(deviceKind);lastSeenMs moves slowly — it comes from the cloud's own device heartbeat, minutes rather than seconds (see below). It answers "is this device alive at all", not "what is it doing right now".
Don't use the P2P session as a reachability check
It looks like one and isn't. Sessions are opened only when something needs one and closed when idle, so a healthy device has no session open most of the time — keying "offline" off that would mark most of an account unreachable. That's why deviceState carries no session flag.
If you want transport visibility for diagnostics, it's still there, station-scoped where the session actually lives:
eufy.getP2pSessions(); // Map<stationSn, session> — currently open
eufy.on("p2pConnect", (stationSn) => …);
eufy.on("p2pClose", (stationSn) => …);Cloud-param polling
Some state has no realtime push of its own — a battery level, or a sensor that only reports to the cloud. For those the SDK re-reads the account's device list on an interval and emits a semantic event for each value that changed, so a host subscribes to one event stream and doesn't poll anything itself.
new EufyMega({
email,
password,
countryCode,
pollMs: 600_000, // how often to re-read cloud params (default 10 min; 0 disables)
});The default is paced to the data, not to how often you'd like updates. The cloud only refreshes a device's params on that device's own slow heartbeat — on a live account the freshest param on an active device was around 12 minutes old, and asking for one device's params returns the same staleness as asking for the whole list. Polling every 30 seconds therefore costs 20× the requests and sees nothing sooner. Lower it only if you have measured that your devices report faster.
This is a slow channel by nature. Motion, doorbell presses, contact changes and lock state arrive over push/P2P/MQTT within seconds and are unaffected by pollMs.
Tear-down
disconnect() closes every channel (push, MQTT, all P2P sessions) and clears the timers — including the poll loop. The login session is untouched — call login() again to bring realtime back up without re-authenticating.
Options summary
| Option | Default | Effect |
|---|---|---|
autoRealtime | true | Auto-start push/MQTT on login + on-demand P2P. false opts out. |
p2pIdleMs | 300000 (5 min) | Idle window before a battery station detaches. |
cacheTtlMs | 15000 (15 s) | Freshness window for cached reads. |
prewarmEvents | doorbell + person / pet / package | Which semantic events speculatively open P2P (not raw motion). |
prewarmMs | 28000 (28 s) | How long a pre-warmed session is held before idle-detach. |
pollMs | 600000 (10 min) | How often cloud params are re-read for changes. 0 disables. Paced to the cloud's own refresh rate. |
localAddresses | — | LAN address override per station (sn → host[:port]) for direct P2P when the record's IP is wrong/blocked. |
Next: Live media.