Devices, units and capabilities
A device in ALYT is the logical thing the household interacts with, not the physical board that happens to be serving it today. That split lets a device keep its room, settings, and history even when hardware changes, and it also lets one physical product grow from a voice satellite into a hub without forcing a new device type. For developers, the important rule is that device behavior is described by capabilities, settings are grouped by capability, and child devices acquired through a hub are represented as devices in their own right.
Device identity and unit identity
Section titled “Device identity and unit identity”Device is the logical device. DeviceUnit is the physical board currently serving as that device. The burned-in serial lives on DeviceUnit.serial, not on Device. That means a warranty replacement can become a new unit for the same logical device instead of becoming a new household object.
Identity for a child device acquired over Zigbee, Matter, or Thread does not use an ALYT serial. It uses Device.protocol and Device.protocolId, together with the hub that owns it. This is the same stable-id rule used for other provider-backed endpoints.
Capabilities are the device shape
Section titled “Capabilities are the device shape”A device’s role is a set of capabilities, not a single kind. The current capability set is Device.capabilities: DeviceCapability[], with values such as VOICE, HUB, and SENSOR. A device can carry more than one capability at the same time, so a satellite that also becomes a hub is represented as both.
The device page is driven by the capability list. A client renders one section per capability the device reports rather than branching on a fixed device type. This leaves room for new capability sections later without changing the page structure.
Settings are grouped by capability
Section titled “Settings are grouped by capability”DeviceSettings.byCapability stores settings as one JSON document per capability. A capability-specific patch must name a capability the device reports. If it names a capability the device does not report, the API returns a 400 rather than ignoring the change.
The same grouping applies to the page UI. Audio settings belong under voice, while radio and join-related settings belong under hub. The page should keep this seam even when only one capability is present today.
Hub ownership and child devices
Section titled “Hub ownership and child devices”A hub owns child devices. A Zigbee or Matter child is a Device whose parentDeviceId points to the hub, in the same Room model, under the same tenancy. The hub relationship is not a separate table or a special one-off case in the room model. Room moves, co-membership pairing, and tenant scoping must work the same way for a hub-owned bulb as for any other device.
The model enforces that a child device under a hub is unique by @@unique([parentDeviceId, protocolId]), and hub deletion cascades to its child devices.
Provisioning and acquisition are different flows
Section titled “Provisioning and acquisition are different flows”Provisioning an ALYT device is the normal onboarding flow: burned-in serial, pairing token minted by a user, credentials handed over BLE or Improv, then POST /devices/register.
Acquiring a child device through Zigbee or Matter is different. The hub is already trusted, so the household puts the hub into permit-join and the child arrives through that hub. The UI must keep those flows separate. One action is to add an ALYT device, and the other is to add something to an existing hub.
What this means for device pages and APIs
Section titled “What this means for device pages and APIs”When you build device UIs or write code against DeviceSettings, keep the capability boundary visible. Do not hardcode the page around a single device kind such as VOICE_SATELLITE. Render sections from the capability list, and keep settings keyed by capability.
For child devices, treat the owning hub as part of the identity. For logical devices with no current unit, keep the row meaningful: a Device without a DeviceUnit is valid.
LAN devices and endpoints
Section titled “LAN devices and endpoints”LAN devices such as a WiZ bulb, a Tuya switch, a Cync dimmer, or an ecobee are not modeled as Device rows in this design. They are Endpoint rows, keyed by a provider’s stable id and described by Endpoint.capabilitySet. Their secrets live in Integration.config. A child acquired by a hub over a radio still uses Device.parentDeviceId; that keeps the two meanings separate.
Switches can be presented differently by the household
Section titled “Switches can be presented differently by the household”A wall switch may drive lights in one household and a fan in another, so the household chooses how it is presented. Endpoint.presentedAs stores light, switch, or null for “as described”, and PATCH /endpoints/:id sets it. That choice changes the category, icon, and which automation targets include the thing. A thing can also be chosen when its only power comes from a switch, or when it is a light with neither dimming nor colour through Thing.presentable.
Key limits to keep in mind
Section titled “Key limits to keep in mind”Device.capabilitiesis a set, not a single kind.DeviceSettings.byCapabilitymust reject a patch for a capability the device does not report.- A hub-owned child uses
Device.parentDeviceId,Device.protocol, andDevice.protocolIdfor identity. DeviceUnit.serialis the burned-in serial;Deviceis the logical device.POST /devices/registeris the provisioning path for ALYT devices, not for hub-acquired children.- LAN devices live as
Endpointrows, not as hub children.