Docs

Entities and services

Reading device state, calling services, multi-device inputs and units.

Every entity input hydrates to an EntityWrapper: a handle that reads state synchronously from the hub's cache and turns any method call into a service call.

Reading

const value = door.getState();              // 'locked' | 'unlocked' | 'unavailable' | …
const attrs = door.getAttributes();         // { batteryLevel: 80, friendlyName: 'Front door', … }
const level = light.getAttribute('brightnessPercent');

Reads are synchronous and come from the hub's last known state. They throw when the entity is not registered (deleted device) — wrap in try/catch if that is possible in your app. The getters .state and .attributes are aliases that also work, but are not in the TypeScript types.

Comparing state

getState() returns the raw token the hub stores (on, locked, ARMED_AWAY). To compare, prefer is() / isNot(), which accept the raw token or the friendly label the app shows for that device, ignoring case and separator differences:

door.is('Open');            // binary_sensor/door: 'Open' is `on`
door.is('on');              // the raw token always matches itself
panel.is('Armed Away');     // intrusion panel: ARMED_AWAY
lock.isNot('Locked');
thermostat.getState() === 'heat';   // strict: raw token only, case-sensitive

Inside an event handler, test the snapshot you were given rather than the live state with matchesState(evt.entityId, evt.newState.state, 'Open'). The label table for each domain is on its capability page. See Raw tokens and friendly labels for the rules, and note that is() / matchesState() need a hub on the friendly-state runtime.

Calling services

Any property that is not a real member becomes a service call:

light.turnOn();
light.turnOn({ brightnessPercent: 40, transition: 2 });
thermostat.setHvacMode({ hvacMode: 'heat' });
cover.setCoverPosition({ position: 100 });
await lock.lock();        // resolves when the hub has dispatched the command

Service names and their parameters are per domain; the capability reference lists them, and the pages mark which are in the curated catalog versus merely routable. The call resolves when the command has been handed to the integration, not when the device confirms. The device's new state arrives later as an ordinary entity change, so read it in a subscription handler, not on the next line.

A call for a service the device does not support is rejected by the integration and logged; it does not throw into your handler.

Many devices at once

A multiple: true entity input with two or more picks hydrates to an array-like proxy. Service calls on it fan out to every member in parallel; Array.isArray is true; a single pick is a lone wrapper. Either call the service directly or normalise:

lights.turnOff();                                               // works for one or many
for (const l of Array.isArray(lights) ? lights : [lights]) {    // when you need per-device logic
  if (l.getState() === 'on') console.log(l.entityId, 'is on');
}

getEntityIds(input) returns the ids for either shape.

Entities you did not ask for

You can wrap any id directly: new EntityWrapper('system.mode'). Useful for well-known system entities (the location mode, system.group_presence) and for ids stored in array rows. Prefer inputs for anything a person might want to change.

Units

Temperatures you read and set are in the location's unit system; getUnitPreferences() tells you which ('imperial' / 'metric') so you can label values. Use it to format, not to convert.

Some older material states that all temperatures are Celsius. Treat the runtime types as authoritative until the platform team confirms; the discrepancy is tracked on Known discrepancies.

System entities worth knowing

EntityWhat it is
system.modethe location mode (DAY, EVENING, NIGHT, AWAY, VACATION)
system.group_presencebinary_sensor, on when anyone is home
system.person_presence_<userId>one per person
system.place_presence_<placeId>one per place (getPlacePresenceOptions() lists them)
sun.sunABOVE_HORIZON / BELOW_HORIZON

On this page