Skip to main content

Plugins Overview

Plugins are separate processes that bridge physical or cloud devices to HomeCore via MQTT. They run independently — crashing or restarting a plugin never affects HomeCore itself.

Architecture

Physical device (Zigbee, Z-Wave, WiFi, cloud API)
↕ native protocol
Plugin process
↕ MQTT
HomeCore embedded broker (port 1883)
↕ internal event bus
HomeCore state bridge → rule engine

Each plugin:

  1. Connects to the embedded MQTT broker with its own plugin_id credential
  2. Publishes device discovery via homecore/plugins/{plugin_id}/register
  3. Publishes device state to homecore/devices/{device_id}/state (retained)
  4. Publishes availability to homecore/devices/{device_id}/availability (retained)
  5. Subscribes to homecore/devices/{device_id}/cmd for commands from HomeCore

Plugin isolation: The SDK subscribes to specific device command topics (homecore/devices/{device_id}/cmd) rather than using wildcards. This prevents cross-plugin command contamination — a plugin only receives commands for devices it owns. Subscriptions are tracked internally and restored automatically on MQTT reconnect.

HomeCore treats the plugin as the source of truth for device discovery. In normal operation you do not pre-create devices manually. When the plugin discovers a light, sensor, speaker, or scene, it registers that device and HomeCore adds it to the registry automatically.

Plugins can also declare capability actions — plugin-specific commands like "Pair Hue bridge", "Include Z-Wave device", "Rescan devices". These show up as buttons under Actions on the plugin's page in the web UI and are exposable to MCP clients without plugin-specific code. See Plugin Capabilities & Actions.

Available plugins

PluginLanguageDevices
hc-hueRustPhilips Hue lights, groups, scenes
hc-yolinkRustYoLink sensors, door sensors, outlets
hc-lutronRustLutron RadioRA2 dimmers, switches, scenes
hc-sonosRustSonos speakers, transport controls, favorites, playlists
hc-zwaveRustZ-Wave devices via zwave-js WebSocket
hc-wledRustWLED LED controllers
hc-isyRustISY/IoX hub (Insteon, Z-Wave, Zigbee)
hc-casetaRustLutron Caséta Smart Bridge Pro
hc-ecowittRustEcowitt weather gateways and consoles
hc-rokuRustRoku players and TVs (ECP)
hc-thermostatRustVirtual thermostats (aggregated sensors → actuator)
http-pollerRustGeneric HTTP endpoint polling

Every one of these is its own repository, releases its own signed artifact, and is installed from the registry rather than being built into homeCore.

Installing a plugin

The normal path is Plugins → Add in the web UI, or POST /api/v1/plugins/install. homeCore fetches the plugin's artifact from the signed registry, verifies the index's ed25519 signature and the artifact's SHA-256 before unpacking anything, seeds a config file, and starts supervising it. No compose file to edit, no binary to place.

Both [registry].url and [registry].public_key must be set or the registry endpoints return 503 — see Configuration.

An installed plugin is recorded in config/plugins/managed.toml, which homeCore owns. Your homecore.toml is never rewritten; a [[plugins]] block there is for a plugin you built or unpacked yourself.

Plugin notices

A plugin reports its own problems as structured notices, and the web UI renders them inline on the plugin's card: missing credentials, a hub that will not answer, nothing discovered yet, a permission that needs changing on the device. They carry a severity and, where there is one, the action that fixes it.

Notices are state, not log lines — a notice stays up while the condition holds and clears when it stops being true, so a plugin that says "no devices found" must re-evaluate that after each discovery sweep rather than deciding once at startup.

Plugin configuration

All plugins share the same [homecore] config section:

# config/plugins/<plugin_id>.toml — core-owned, passed to the plugin at launch

[homecore]
broker_host = "127.0.0.1"
broker_port = 1883
plugin_id = "plugin.hue"
password = "" # set if broker auth is enabled

homeCore owns that file. Each plugin's config lives at <base>/config/plugins/<plugin_id>.toml and the supervisor passes the path as the plugin's first argument, so the plugin still just reads a file — only the location moved out of the plugin's own tree, where a plugin upgrade could clobber it and where the API, the settings form, and an operator editing by hand all disagreed about which copy was real.

Edit it from the UI (Plugins → the plugin → Configuration), over the API (PUT /api/v1/plugins/{id}/config), or by editing the file directly. Core watches the directory and restarts that one plugin so it re-reads its config — core itself does not restart, and no other plugin is touched. A write whose bytes are unchanged is ignored, and a transient unlink or rename never kills a running plugin.

Run standalone for development by passing a path:

./hc-hue /etc/homecore-plugins/hue.toml

Running plugins

Plugins are independent binaries. Run them from their installation directory:

cd /path/to/hc-hue
./hc-hue config/config.toml

Or with run-dev.sh for local development, from the workspace root:

./hc-scripts/run-dev.sh   # builds everything, then starts core + its plugins

Plugin device registration

Plugins register devices by publishing a JSON payload to homecore/plugins/{plugin_id}/register.

For well-known device classes, the recommended path is typed registration. The plugin sends a canonical device_type, and HomeCore resolves the built-in schema automatically. This is how different plugins can expose lights, switches, and now media players in a consistent way.

Typed registration example

{
"device_id": "sonos_living_room",
"plugin_id": "plugin.sonos",
"name": "Living Room",
"area": "living_room",
"device_type": "media_player"
}

When HomeCore receives that registration it:

  1. Creates or updates the device record
  2. Stores the device_type
  3. Resolves the matching built-in device schema
  4. Exposes the device to the API, UI, and rule engine without manual setup

Explicit capabilities example

Plugins can also publish an explicit schema when they need a custom device shape:

{
"device_id": "hue_001788fffe6841b3_1",
"plugin_id": "plugin.hue",
"name": "Living Room Main",
"area": "living_room",
"device_type": "light",
"capabilities": {
"on": {"type": "boolean"},
"brightness": {"type": "integer", "minimum": 0, "maximum": 254},
"color_temp": {"type": "integer", "minimum": 2000, "maximum": 6500}
}
}

Use explicit capabilities when the device does not fit a built-in type cleanly. Use device_type when you want HomeCore to understand the device as a known category.

Known device types

The built-in catalog includes common categories such as:

  • switch
  • light
  • lock
  • cover
  • climate
  • binary_sensor
  • temperature_sensor
  • media_player

This matters because HomeCore can now treat media players from different plugins the same way at the automation layer. A plugin such as hc-sonos can register speakers as media_player, and rules can target those players by HomeCore device ID instead of plugin-local URLs or IP addresses.

HomeCore stores the device in the registry. Subsequent registrations on plugin restart are upserts, so names, areas, types, and schemas stay in sync with what the plugin reports.

Plugin startup race condition

Problem: If a plugin starts very quickly and HomeCore's internal MQTT client isn't subscribed to homecore/# yet, the registration message is missed.

Solution: Set plugin_ready_delay_secs in homecore.toml (default: 10 seconds). HomeCore waits this many seconds after startup before the mode manager publishes initial states. The MQTT client subscription is established well before plugins connect.

For automated scripts, wait for HomeCore to print API server starting before launching plugins.

Connecting to an external broker

For multi-machine deployments, configure plugins to connect to the same external broker as HomeCore:

[homecore]
broker_host = "192.168.1.10" # HomeCore machine IP
broker_port = 1883
plugin_id = "plugin.hue"
password = "hue-password"

Plugin management protocol

HomeCore includes a PluginManager that supervises managed plugins. Each managed plugin runs under a per-plugin supervisor task with start/stop/restart support and exponential backoff on crashes.

Management MQTT topics

DirectionTopicPurpose
Plugin → HChomecore/plugins/{id}/heartbeatPlugin liveness signal (30-60s interval)
HC → Pluginhomecore/plugins/{id}/manage/cmdManagement commands from core
Plugin → HChomecore/plugins/{id}/manage/responsePlugin response to management commands

Management commands

CommandDescription
pingHealth check — plugin responds with pong
get_configRequest plugin's current configuration
set_configPush configuration changes to the plugin
set_log_levelChange the plugin's log level at runtime

Heartbeat and timeout

Managed plugins publish a heartbeat message every 30-60 seconds. The PluginManager runs a timeout sweep and marks a plugin as offline after 90 seconds without a heartbeat.

Management API

EndpointDescription
GET /api/v1/plugins/:idGet plugin status and details
PATCH /api/v1/plugins/:idUpdate plugin metadata
POST /api/v1/plugins/:id/startStart a managed plugin
POST /api/v1/plugins/:id/stopStop a managed plugin
POST /api/v1/plugins/:id/restartRestart a managed plugin
GET /api/v1/plugins/:id/configGet plugin configuration
PUT /api/v1/plugins/:id/configUpdate plugin configuration
DELETE /api/v1/plugins/:id/devicesBulk-wipe every device the plugin owns; plugin re-registers live ones on next sync. See Devices → Wipe all devices for one plugin.

Managed plugins

All Rust device plugins use the official SDK with full management protocol support:

  • hc-hue — heartbeat, remote config, dynamic log level, MQTT log forwarding
  • hc-wled — heartbeat, remote config, dynamic log level, MQTT log forwarding
  • hc-yolink — + custom action: rescan_devices
  • hc-lutron — heartbeat, remote config, dynamic log level, MQTT log forwarding
  • hc-sonos — heartbeat, remote config, dynamic log level, MQTT log forwarding
  • hc-isy — heartbeat, remote config, dynamic log level, MQTT log forwarding
  • hc-zwave — heartbeat, remote config, dynamic log level, MQTT log forwarding
  • hc-caseta — heartbeat, remote config, dynamic log level, MQTT log forwarding
  • hc-ecowitt — + gateway actions: discover_gateways, refresh_sensors, get_gateway_info, set_custom_server
  • hc-roku — + device actions: discover_devices, list_devices, refresh_catalog, device_info, send_command, app_icon, forget_stale_devices
  • hc-thermostat — + custom actions: recalculate_all, reload_config, add_thermostat, remove_thermostat, get_thermostats. First plugin to use the SDK's cross-device state subscription (subscribe_state / run_managed_with_state).

Plugin MQTT topics (reference)

DirectionTopicPurpose
Plugin → HChomecore/devices/{id}/stateFull state update (retained)
Plugin → HChomecore/devices/{id}/state/partialPartial update (JSON merge-patch)
Plugin → HChomecore/devices/{id}/availability"online" or "offline" (retained)
Plugin → HChomecore/plugins/{id}/registerDevice registration
HC → Pluginhomecore/devices/{id}/cmdCommand from HomeCore/API
Plugin → HChomecore/plugins/{id}/heartbeatLiveness signal (managed plugins)
HC → Pluginhomecore/plugins/{id}/manage/cmdManagement command (managed plugins)
Plugin → HChomecore/plugins/{id}/manage/responseManagement response (managed plugins)
Plugin → HChomecore/plugins/{id}/logsForwarded plugin log lines (JSON LogLine)