Core concepts
Five nouns carry the entire model. Learn these and the API, the topic grammar and the UI all stop being surprising.
| Entity | What it is | Identity |
|---|---|---|
| Space | The tenant container — owns devices, members and billing. Also the shard key. | 11-char base62 |
| Device | A physical or virtual node exposing one or more endpoints. | (mac, endpoint_id) |
| Endpoint | One independent control surface on a device. | endpoint_id, 0..255 |
| Hub | ESP-NOW ⇄ MQTT bridge, and the local-autonomy tier. | MAC |
| View | A shareable surface composing endpoints across several devices. | 11-char base62 |
Space
The namespace root for everything. Devices, members, schedules, history and billing all
hang off a space, and space_id is the shard key — it is identical in the database, in
MQTT topics and in the streaming layer, on purpose.
One space is the normal case
A home is one space. A restaurant chain is one space per venue. Nothing in the platform assumes a user has more than one, and nothing breaks if they have fifty.
Device and endpoint
A device is a physical node. An endpoint is one independent thing on it. Identity is the pair:
(mac, endpoint_id)mac is 12 lowercase hex characters, no colons. endpoint_id is a uint8; endpoint 0
is the default and is what every single-function device uses implicitly.
Why the pair, and not just the MAC
One ESP8266 can legitimately be several things — a relay on D3, a blind pair on
D5/D6, and a BME680 on D1/D2. There is one MAC on that chip and three unrelated control
surfaces behind it. Collapsing them into one “device” with a single type means the UI
can only render one category of card, and labels, schedules and automations get forced
onto the whole chip.
Every mature spec resolves this the same way:
| Spec | “Node” | “Entity” |
|---|---|---|
| Matter | Node | Endpoint (with Clusters) |
| Zigbee | Device | Endpoint |
| HomeKit / HAP | Accessory | Service |
| Home Assistant | Device | Entity |
| ESPHome | Device | Component |
Two endpoints on one MAC are fully independent for every user-facing purpose. They just happen to share a radio, a firmware image and a power supply.
Endpoint IDs are local to the MAC
There is no global endpoint namespace. Endpoint 1 on two different devices are
unrelated. For anything printed or shared, use the endpoint’s short_id instead.
Capabilities
A device declares what each endpoint can do in its announce payload. The UI is built from that declaration rather than from a hardcoded per-type table, which is why a new device type needs no client release:
{
"mac": "08d1f9fa6288",
"endpoints": [
{
"endpoint": 1,
"type": "sensor",
"variant": "bme680",
"channels": ["temperature", "humidity", "pressure", "gas"]
},
{
"endpoint": 4,
"type": "switch",
"params": [
{ "name": "switch", "kind": "enum", "values": ["on", "off"] }
]
}
]
}A switch is a channel, not a param
This one has bitten more than once: a relay modelled as a capability param rather than
as a switch channel renders on some clients and is invisible on others. Follow the
channel model for anything with a state the device reports back.
Ownership tiers
What a device may do at all depends on where it is rooted:
| Tier | Root | Capability |
|---|---|---|
| unowned | none | mute — no broker access |
| user-attached | under the user | announce, state, commands, config. No history. |
| space-assigned | under the space | the full set |
Detaching a device preserves its identity and history. A change of hands wipes and re-mints it — the new owner inherits nothing.
Hub
The hub bridges ESP-NOW devices onto MQTT, and runs cross-device automation when the server is unreachable. It is space-bound.
Hub expansion is structurally mandatory, not an optimisation: an ESP-NOW device cannot receive a space-wide broadcast, so a single “everything off” has to become N frames somewhere, and the hub is the only place with the peer list.
What survives in rescue mode
Local control and hub-resident automation. Not history, not push notifications, not sharing — those are server-tier by nature.
View
A view is a shareable control surface that composes several endpoints across several devices and declares its own capability set.
The motivating case: heating is a temperature sensor on one device plus a heater on another. Neither endpoint alone is “the heating”. A view is that one thing, and it can be shared — including to anonymous users via an access code — with a capability set narrower than the underlying endpoints.
Views are outside the broker-authorization problem
A view holds no broker credential. Its users go through REST and WebSocket only; the backend evaluates, approves, then publishes. So per-device credentials and broker ACLs simply do not constrain views, and the audit trail comes for free.
View permissions are a separate concern from device and endpoint permissions. A grant on a view says nothing about direct access to the endpoints inside it.
How they fit together
Read VIEW }o--o{ ENDPOINT carefully — it is many-to-many. Several views may cover the
same endpoints with different capabilities: one that operates, one that is read-only and
safe to put on a wall.