Skip to content

@mega-yfue/eufy-sdk / ValueMember

Interface: ValueMember

A member backed by a device property: a getter, plus a setter when write is declared.

Properties

accepts?

ts
optional accepts?: unknown;

A value the setter takes BEYOND what the getter answers — declare it with accepts.

A video-quality tier is stored and read as a number, but the write also resolves the resolution NAME it maps to; without this the derived setter would narrow to the stored type and a caller would lose the names it can pass. Only the TYPE is used — nothing reads the value.


aliases?

ts
optional aliases?: Record<string, string | number | boolean>;

Extra intent verbs routed to write, each carrying the value it stands for.


args?

ts
optional args?: readonly ActionArgSpec[];

What the setter accepts, when the member's own kind and bounds do not say it well enough — an argument whose name reads better than the member's, or one the caller may omit.


available?

ts
optional available?: (ctx) => boolean;

Install the write only where this predicate holds — the general form of requires, for a gate no list of params can express.

A lock's rain mode exists on the P2P video lock and not on the MQTT garage door: a TOPOLOGY fact, not a reported param. Audio's controls split the same way on device FAMILY — a HomeBase has an alarm volume, a camera has a microphone, and neither reports the other's params. Like requires, a member that declares this lands OPTIONAL on the surface, because whether it exists is a runtime fact.

Takes an AvailabilityContext (not a full CommandContext): the manifest applies the same gate at resolve time, before a live session exists. May read any field the CloudRecord can supply — codec, model, category, deviceType, capabilities, paramIds — but never transport-only fields (channel).

Parameters

ctx

AvailabilityContext

Returns

boolean


coerce?

ts
optional coerce?: (raw) => string | number | boolean;

Reinterpret the RAW wire value at ingest, for a param the app reads as something other than its face value — a bitfield the device reports as an object, a code that means a flag.

Distinct from decode, and the two are not interchangeable: this runs once when params are applied and REPLACES the default type coercion, so the stored property already holds the corrected value and every reader sees it. decode runs per read, inside the getter, where the injected codec is in scope — which is the only way to reach a field inside a payload.

Parameters

raw

string | number | boolean

Returns

string | number | boolean


decode?

ts
optional decode?: (raw, codec, ctx) => string | number | boolean | undefined;

Derive the getter's value from the stored one, for a param delivered as a payload rather than a scalar. Its return type wins over type on the surface, since it IS the value a caller gets — and it is kept EXACTLY, so a decode answering a named union (VacuumActivity) surfaces that union rather than the string it is stored as. Widening the declared return here would quietly erase it.

codec is the injected RawDpCodec, undefined on an unbound device — the only place a capability gets one, and the reason a structured DP payload can be read without the transport knowing what its fields mean.

Parameters

raw

unknown

codec

RawDpCodec | undefined

ctx

CommandContext

Returns

string | number | boolean | undefined


decodedKind?

ts
optional decodedKind?: ValueKind;

What the decoded value means, when it differs from the stored property's own kind.


decodedValues?

ts
optional decodedValues?: readonly (string | number)[];

The option set of a decoded enum — the counterpart to a property schema's enumValues, for a getter whose value that schema cannot express. Legal only alongside decode, since without one the property's own set is the answer and a second copy could only drift.


description

ts
description: string;

enumValues?

ts
optional enumValues?: Record<number, string>;

enumValuesFor?

ts
optional enumValuesFor?: (ctx) => Record<number, string> | undefined;

A per-device enum resolved at manifest time from the device context — for a value whose options are real but vary by model, so a single static enumValues cannot state them (e.g. workingMode, whose indices number differently per camera). mergeProperties calls this with the device context and stamps the result onto that device's spec. Returning undefined leaves the static enumValues (or none) in place.

Parameters

ctx

AvailabilityContext

Returns

Record<number, string> | undefined


intentNames?

ts
optional intentNames?: readonly string[];

Extra names the intent path should route here, taking the caller's value as-is. Unlike aliases, no value is supplied: it is the same write under a second name.


invert?

ts
optional invert?: boolean;

kind?

ts
optional kind?: ValueKind;

max?

ts
optional max?: number;

min?

ts
optional min?: number;

Numeric bounds the value must fall within — the same constants write clamps with.


param?

ts
optional param?: number;

The wire id carrying this value. Optional only for a writeOnly member whose write is not a param at all — camera privacy is a frame BURST, and the id that burst is built from belongs to the transport that plays it, not to a capability that would be naming another layer's vocabulary.


property?

ts
optional property?: string;

The name this value takes in the device's FLAT property namespace, when the member key would collide there. The key is the accessor and is scoped by its capability (dev.battery().level); a property is not — level alone is claimed by both battery and suction, volume by three capabilities. Defaults to the key, which is right for the ~70% that do not collide.


provenance?

ts
optional provenance?: PropertySource;

readAliases?

ts
optional readAliases?: readonly object[];

Extra wire ids that also carry this value on some families, with their own polarity and family gate.


readAvailable?

ts
optional readAvailable?: (ctx) => boolean;

Whether the primary read parameter carries this member on the current device family.

Parameters

ctx

AvailabilityContext

Returns

boolean


readReflectsWrite?

ts
optional readReflectsWrite?: (ctx) => boolean;

Whether this member's READ observes the same wire its write lands on, for a given device. Absent means yes — the ordinary case, where the setter's effect shows up in the getter.

Declared only where a family routes the write elsewhere: the read then answers honestly about the param it observes while disagreeing with what the setter did, and a caller has no reason to distrust it. Such a member is named by unreflectedMembers for the devices where it applies, so the disagreement is stated rather than left to be discovered.

Parameters

ctx

CommandContext

Returns

boolean


readsFrom?

ts
optional readsFrom?: 
  | string
  | {
  param: number;
  property: string;
};

This member's value is also a FIELD of another member's payload.

Names the member KEY that owns that payload's param. The owner's stored property is what this member's decode is handed, and the owner's evidence installs this member — so a device that never reported the DP gets neither getter, and one that did gets both.

The inverse of readAliases, which is one value across several wire ids. This is several values inside ONE wire id, which is how the clean line reports most of what it knows: nine consumable counters arrive as nine sub-messages of a single ConsumableRuntime on DP 168, and a CleanParam on DP 154 carries the carpet strategy and the clean type together.

Two shapes, by whether the member also declares a param of its own.

Derived only (no param): the member contributes NO PropertySpec. The schema describes what a device REPORTS and the device reports one DP, so a second spec for that id would give Device two names for one param — and it stores under only the first, leaving the extra getters answering undefined forever. That is exactly the failure the evidence gate exists to prevent.

Second source (with a param): the member keeps its own wire and its own spec, and reaches into the owner's payload only on a device that did not report that wire. This is what lets ONE property span both clean lines when the two report it differently — the legacy Tuya line puts the lifetime cleaned area on its own DP, the AIoT line buries it inside CleanStatistics on DP 167. Without it, the same value would need two names and every host would branch on device family to ask for it. The decode sees whichever raw value the device actually has, so it discriminates on SHAPE, the way the clean line's cross-family decoders already do.

Watch what the owner was installed BY. An owner with readAliases can be present because of an alias, and the value stored under its property is then the ALIAS's — a different wire carrying a different figure. A borrowing member that cannot come from that wire has to screen for it, either with its own available gate or in its decode; lifetimeCleanCount does both.

Read-only either way. Setting a field inside a shared payload means re-encoding the whole message, which needs an encoder and a captured write this SDK does not have; the owner keeps that wire. Guarded by property-id-integrity.spec.ts.

Naming an owner in ANOTHER capability. A string names a sibling in this table, which covers every payload whose readings all belong to one capability. Some do not: DeviceInfo on DP 169 carries the robot's MAC, SSID and IP beside the DOCK's firmware version, and the one-owner rule is per product LINE — so one capability must own that id and the other's reading would otherwise have to hang off the wrong object. The { property, param } form says "read the property another module owns, gated on the param that carries it", which works because Device keys state by NAME in one flat namespace shared across capabilities. Both halves are stated rather than looked up: a member declaring this cannot see the other module's table, and the guard checks the pair against the line's real owner so a rename cannot leave it pointing at nothing.


realtime?

ts
optional realtime?: boolean;

Install the getter on capability alone, not on having seen the param — for state that only ever arrives over realtime, where "has reported already" is the wrong evidence.


requires?

ts
optional requires?: readonly number[];

Install the WRITE only on a device that reported one of these params.

Distinct from the getter's own evidence gate: a siren's volume write was captured on a real siren, and the evidence that a device IS one is that it reports a siren param — so handing the write to a camera the name hint gave this capability to would send a frame that family never accepts. A member that declares this lands OPTIONAL on the surface, since whether it exists is a runtime fact.

Absent = install on any device with the capability, which is right for a wire the whole family speaks.


requiresRead?

ts
optional requiresRead?: true;

Install the write only when this member's family-valid primary param or read alias was reported.


type

ts
type: PropertyValueType;

unexposed?

ts
optional unexposed?: true;

Reported by the device — so it IS in the property schema and reachable through getProperty — but given no typed getter, because its value space is not evidenced.

The distinction from writeOnly is which half is missing: there the device says nothing, here WE cannot yet say what the value means. Publishing a typed getter over a meaning we have not confirmed is the same guess the never-ship-a-guessed-param rule forbids.


unit?

ts
optional unit?: string;

unverified?

ts
optional unverified?: true;

The write wire is NOT confirmed on a real device.

A fire-and-forget write that is wrong looks exactly like success, so it must not ship as a callable method: any write declared beside this is NOT installed, and the setter lands optional on the surface so a caller learns at COMPILE time. Declare it with no write at all when the frame shape is unknown — the flag is what states "the device accepts this, we have not captured how", which is the distinction the intent path answers with rather than a generic "not supported".


write?

ts
optional write?: (value, ctx) => Command | undefined;

The wire, or absent for read-only. undefined from it = this value is not one we accept.

Parameters

value

string | number | boolean

ctx

CommandContext

Returns

Command | undefined


writeAs?

ts
optional writeAs?: string;

Setter name when set + the key reads wrong (isOnset, not setIsOn).


writeOnly?

ts
optional writeOnly?: true;

A setting the device ACCEPTS but never reports back.

No getter (it could only ever answer undefined) and no entry in the property schema, which describes what a device reports. It still declares its param and type, because that is what the write needs. Camera privacy is one of these.


writtenElsewhere?

ts
optional writtenElsewhere?: true;

A setter for this value EXISTS, but is not this member's own write — so the published schema's writable cannot be derived from write alone.

writable means "a setter exists", and one legitimately lives elsewhere when a method member drives the value because a single write cannot (rtsp's recording mode sends a two-frame pair), or the setter needs per-bind state the table cannot hold (a vacuum's suction level validates against the model's own range). Guarded by action-specs.spec.ts, which asserts every writable property has a reachable setter — so this cannot drift into a lie.

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.