Skip to main content

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
}
}
FieldDescription
device_idUnique internal identifier; set by the plugin at registration
canonical_nameStable HomeCore name used for rules and logs when available
nameHuman-readable name; set by plugin, editable via API
plugin_idWhich plugin owns this device
areaRoom/zone assignment (optional)
device_typeCanonical category such as light, door_sensor, thermostat, or media_player
availableWhether the device is online
last_seenLast MQTT state update
attributesAll 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 execution
  • canonical_name: the stable HomeCore name intended for rules, API consumers, and logs
  • name: 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 Lamp in the living room
  • Floor Lamp in the bedroom
  • two separate Floor Lamp devices 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_lamp
  • bedroom.floor_lamp
  • living_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"}, ...]
ParameterDefaultDescription
from24 hours agoISO-8601 UTC start
tonowISO-8601 UTC end
attributeallFilter to one attribute
limit500Max 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

PluginDevice ID patternExample
hc-huehue_{bridge_id}_{resource_id}hue_001788fffe6841b3_1
hc-yolinkyolink_{device_id}yolink_d88b4c01000e82eb
hc-lutronlutron_{device_id}lutron_21
hc-lutron (Pico)lutron_pico_{id}lutron_pico_5
hc-zwavezwave_{node_id}zwave_23
hc-sonossonos_{room_name}sonos_living_room
hc-wledwled_{name}wled_deck
hc-isyisy_{address}isy_1_2_3_1
Timerstimer_{name}timer_garage_close
Switchesswitch_{name}switch_away_mode
Modesmode_{name}mode_night
Lutron sceneslutron_scene_{id}lutron_scene_42
Hue sceneshue_{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/devices
  • GET /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.