Devices
A device in HomeCore is any physical or virtual entity that has a state you can read and optionally command. Devices are registered by plugins via MQTT and stored in the device registry (data/state.redb).
Device model
{
"device_id": "yolink_d88b4c01000e82eb",
"canonical_name": "entryway.front_door",
"name": "Front Door",
"plugin_id": "plugin.yolink",
"area": "entryway",
"device_type": "door_sensor",
"available": true,
"last_seen": "2026-03-28T14:22:00Z",
"attributes": {
"open": false,
"battery": 85,
"signal": -65
}
}
| Field | Description |
|---|---|
device_id | Unique internal identifier; set by the plugin at registration |
canonical_name | Stable HomeCore name used for rules and logs when available |
name | Human-readable name; set by plugin, editable via API |
plugin_id | Which plugin owns this device |
area | Room/zone assignment (optional) |
device_type | Canonical category such as light, door_sensor, thermostat, or media_player |
available | Whether the device is online |
last_seen | Last MQTT state update |
attributes | All current attribute values as a flat JSON object |
device_id vs canonical_name
HomeCore now distinguishes between three kinds of names:
device_id: the plugin-owned internal ID used on MQTT topics and for runtime executioncanonical_name: the stable HomeCore name intended for rules, API consumers, and logsname: the display label shown to humans
Example:
{
"device_id": "hue_001788fffe6841b3_1",
"canonical_name": "living_room.floor_lamp",
"name": "Floor Lamp"
}
In normal rule authoring, prefer canonical_name. Keep device_id for low-level API work, debugging, or when you need the exact plugin identifier.
Why this exists
Display names are not always unique. You may have:
Floor Lampin the living roomFloor Lampin the bedroom- two separate
Floor Lampdevices in the same room
canonical_name gives HomeCore a stable automation reference that remains readable without forcing rules to hardcode plugin IDs.
Device CRUD
List devices
# All devices
curl -s http://localhost:8080/api/v1/devices \
-H "Authorization: Bearer $TOKEN" | jq
# Paginated (large deployments)
curl -s "http://localhost:8080/api/v1/devices?limit=50&offset=0" \
-H "Authorization: Bearer $TOKEN" | jq
# X-Total-Count header gives total
# Filter by device type
curl -s "http://localhost:8080/api/v1/devices?device_type=media_player" \
-H "Authorization: Bearer $TOKEN" | jq '.[].device_id'
# Include resolved capability schema inline
curl -s "http://localhost:8080/api/v1/devices?device_type=media_player&include_schema=true" \
-H "Authorization: Bearer $TOKEN" | jq '.[].name'
Get one device
curl -s http://localhost:8080/api/v1/devices/yolink_front_door \
-H "Authorization: Bearer $TOKEN" | jq
Get device capability schema
curl -s http://localhost:8080/api/v1/devices/hue_001788fffe6841b3_1/schema \
-H "Authorization: Bearer $TOKEN" | jq
Command a device
PATCH /devices/{id}/state sends a command to the device via MQTT.
# Turn a light on
curl -s -X PATCH http://localhost:8080/api/v1/devices/hue_001788fffe6841b3_1/state \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"on": true, "brightness": 200}'
# Unlock a door
curl -s -X PATCH http://localhost:8080/api/v1/devices/zwave_lock_front/state \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"locked": false}'
The command is published to homecore/devices/{id}/cmd. The plugin receives it and publishes the updated state back.
For some device types, especially media_player, the command payload is action-oriented rather than simple attribute assignment. Examples:
# Start playback on a media player
curl -s -X PATCH http://localhost:8080/api/v1/devices/sonos_living_room/state \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action": "play"}'
# Play a Sonos favorite by name
curl -s -X PATCH http://localhost:8080/api/v1/devices/sonos_living_room/state \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action": "play_favorite", "favorite": "Dinner Jazz"}'
# Use the generic media command shape
curl -s -X PATCH http://localhost:8080/api/v1/devices/sonos_living_room/state \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action": "play_media", "media_type": "playlist", "name": "Dinner"}'
This is deliberate. The device ID stays stable inside HomeCore while the plugin handles the Sonos-specific lookup and transport details internally.
Update device metadata
Change the display name, area, or canonical rule name without touching state:
curl -s -X PATCH http://localhost:8080/api/v1/devices/yolink_front_door \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Front Door Sensor", "area": "entryway", "canonical_name": "entryway.front_door"}'
Canonical naming guidance
Good canonical names are:
- lowercase
- stable over time
- area-qualified when useful
- descriptive enough to avoid collisions
Examples:
living_room.floor_lampbedroom.floor_lampliving_room.floor_lamp_sofa
Avoid using temporary or positional labels that are likely to change.
Delete a device
Cascades to all rules that reference the device — affected rules are disabled with a DELETED: placeholder.
curl -s -X DELETE http://localhost:8080/api/v1/devices/old_sensor \
-H "Authorization: Bearer $TOKEN" | jq
# → {"deleted": true, "affected_rules": ["Morning lights", "Away mode check"]}
Bulk operations
Bulk area assignment
curl -s -X PATCH http://localhost:8080/api/v1/devices \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"ids": ["sensor_01", "sensor_02", "light_01"], "area": "garage"}' | jq
# → {"updated": 3, "not_found": []}
Bulk delete
curl -s -X DELETE http://localhost:8080/api/v1/devices \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"ids": ["old_sensor_01", "old_sensor_02"]}' | jq
# → {"deleted": 2, "not_found": [], "affected_rules": ["Garage lights"]}
Wipe all devices for one plugin
Removes every device whose plugin_id matches, with rule cascade.
The plugin itself stays registered — devices it still cares about
are re-registered automatically on its next sync cycle. Useful for
cleaning up zombies left over from plugin development churn or
config rearrangements without dropping the whole state DB.
curl -s -X DELETE http://localhost:8080/api/v1/plugins/plugin.hue/devices \
-H "Authorization: Bearer $TOKEN" | jq
# → {
# "deleted": 47,
# "device_ids": ["hue_light_1", "hue_scene_2", ...],
# "affected_rules": ["Morning routine"]
# }
The Leptos admin UI exposes this as a Wipe all devices button in the Devices section of each plugin's detail page (admin role required, hidden when the plugin has no registered devices).
Device history
Time-series attribute changes stored in SQLite (data/history.db):
# Last 24 hours (default)
curl -s "http://localhost:8080/api/v1/devices/thermostat_main/history" \
-H "Authorization: Bearer $TOKEN" | jq
# Specific attribute, last 7 days
curl -s "http://localhost:8080/api/v1/devices/thermostat_main/history?attribute=temperature&from=$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ)" \
-H "Authorization: Bearer $TOKEN" | jq
# Response shape
# [{"attribute":"temperature","value":72.1,"recorded_at":"2026-03-28T14:00:00Z"}, ...]
| Parameter | Default | Description |
|---|---|---|
from | 24 hours ago | ISO-8601 UTC start |
to | now | ISO-8601 UTC end |
attribute | all | Filter to one attribute |
limit | 500 | Max entries (cap 5000) |
Areas
Assign devices to logical rooms/zones:
# List areas
curl -s http://localhost:8080/api/v1/areas \
-H "Authorization: Bearer $TOKEN" | jq
# Create an area
curl -s -X POST http://localhost:8080/api/v1/areas \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Living Room"}' | jq
# Assign devices to an area
curl -s -X PUT http://localhost:8080/api/v1/areas/AREA_ID/devices \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"device_ids": ["light_living_room_1", "light_living_room_2"]}' | jq
Device IDs by plugin
| Plugin | Device ID pattern | Example |
|---|---|---|
| hc-hue | hue_{bridge_id}_{resource_id} | hue_001788fffe6841b3_1 |
| hc-yolink | yolink_{device_id} | yolink_d88b4c01000e82eb |
| hc-lutron | lutron_{device_id} | lutron_21 |
| hc-lutron (Pico) | lutron_pico_{id} | lutron_pico_5 |
| hc-zwave | zwave_{node_id} | zwave_23 |
| hc-sonos | sonos_{room_name} | sonos_living_room |
| hc-wled | wled_{name} | wled_deck |
| hc-isy | isy_{address} | isy_1_2_3_1 |
| Timers | timer_{name} | timer_garage_close |
| Switches | switch_{name} | switch_away_mode |
| Modes | mode_{name} | mode_night |
| Lutron scenes | lutron_scene_{id} | lutron_scene_42 |
| Hue scenes | hue_{bridge}_scene_{id} | hue_001788fffe6841b3_scene_abc123 |
Media players as first-class devices
HomeCore now understands media_player as a built-in device type. A plugin can discover players and register them without manual configuration, and those players immediately become available in:
GET /api/v1/devicesGET /api/v1/devices?device_type=media_player- rules using
set_device_state - scenes and any UI built on the device registry
For Sonos specifically, the player device can expose:
- transport state such as
playing,paused, and current position - volume and mute state
- currently playing title, artist, and album
- group topology
- discovered Sonos favorites and playlists
That lets HomeCore rules refer to a logical device such as sonos_living_room instead of embedding http://speaker-ip:port/... URLs in automation logic.