Repository navigation
MQTT Integration
MQTT is Murdock's recommended Home Assistant integration. It is
token-free in both directions: recognition results go out via HA's
MQTT auto-discovery, and HA pushes context (which satellite is speaking,
which media players are playing, presence) back in on retained topics.
The legacy REST path (long-lived token + input_text helpers) still
exists for setups without a broker.
In the add-on, the Mosquitto broker is auto-wired via HA's service API — zero configuration. Standalone, enter the broker host/credentials under Settings → MQTT in the Web UI.
All grouped under one Murdock device:
-
sensor.murdock_current_speaker— recognised name, orunknown -
sensor.murdock_speaker_confidence— calibrated probability in % -
sensor.murdock_speaker_distance— raw cosine distance -
sensor.murdock_nearest_speaker— closest enrolled voice -
sensor.murdock_speaker_role— the speaker's configured role -
sensor.murdock_speaker_emotion— (experimental, usually empty) -
sensor.murdock_active_satellite— which satellite the result came from -
sensor.murdock_whisper_score— (experimental) measured whisper score -
binary_sensor.murdock_speaker_recognized— ON/OFF -
binary_sensor.murdock_whispering— (experimental) ON while the last utterance was whispered, regardless of whether the speaker was known
| Topic | Direction | Payload |
|---|---|---|
homeassistant/<comp>/murdock/<id>/config |
Murdock → HA | discovery (retained) |
murdock/status |
Murdock → HA |
online / offline (LWT, retained) |
murdock/sensor/<name>/state |
Murdock → HA | recognition state (retained) |
murdock/event/recognition |
Murdock → HA | full JSON event |
murdock/active_satellite |
HA → Murdock | {"id": "...", "area": "..."} |
murdock/context/media/<entity_id> |
HA → Murdock |
{"playing": true, "area": "..."} (retain!) |
murdock/context/<room>/presence |
HA → Murdock |
{"present": true} (retain!) |
murdock/context/<room>/tv |
HA → Murdock | legacy single-TV topic (retain!) |
murdock/event/recognition (MQTT) and the HA event
speaker_recognition_detected (REST) carry the same JSON shape.
{
"speaker": "Jonas",
"confidence": 0.9432,
"satellite_id": "wohnzimmer",
"is_known": true,
"timestamp": 1785000000.12,
"distance": 0.31,
"threshold": 0.38,
"nearest_speaker": "Nils",
"nearest_distance": 0.61,
"margin": 0.30,
"weight": 1.0,
"role": "admin"
}| Field | Meaning |
|---|---|
speaker |
Name on a verified match, null otherwise |
reason |
Only when speaker is null: unknown, uncertain, tv-noise, early-reject, short, no-speakers, embed-failed
|
nearest_speaker / nearest_distance
|
On a match the runner-up; otherwise the closest candidate |
margin |
nearest_distance − distance — input to the margin gate
|
weight |
1.0 when verified, decaying linearly to 0 at threshold + 0.15 — how much a speaker-bound rule is trusted |
uncertain |
true when the margin gate downgraded a match |
emotion / emotion_confidence
|
Only when emotion classification is enabled |
whisper / whisper_score
|
Whether the utterance was whispered, and the measured score (see Whisper detection) |
ambiguities |
Sidecar hints: [{original, alternative, kind}], kind ∈ alternative / additional / reading (see Speaker Context) |
Two properties worth relying on:
- The event fires on every completed utterance, including non-recognitions. Before 0.8.0 a blocked or unknown utterance left the previous speaker standing until something else overwrote it.
-
speakeris null rather than a sentinel string. Automations written against"unknown"/"tv-noise"inspeakerneed to readreasoninstead (⚠️ changed in 0.8.0). The MQTT sensor states are unchanged — only the JSON event moved.
Inbound context topics must be published with retain: true so
Murdock picks up the last state immediately after a (re)connect. The
momentary active_satellite signal needs no retain.
HA's Assist pipeline does not tell the STT stage which device is
speaking — the Wyoming Transcribe.name field arrives empty. Murdock
recovers the identity from MQTT: a small automation publishes the active
satellite when it starts listening. Murdock uses it only when fresh
(≤ 30 s) and when no name came in over Wyoming, so directly-connected
wyoming-satellite devices with their own name still win.
alias: Murdock — publish active satellite
trigger:
- platform: state
entity_id:
- assist_satellite.living_room # ← your assist_satellite entities
- assist_satellite.kitchen
to: listening
action:
- service: mqtt.publish
data:
topic: murdock/active_satellite
payload: >-
{{ {'id': trigger.entity_id, 'area': area_name(trigger.entity_id)} | to_json }}
mode: queued
max: 10Publishing the id (entity id) lets two satellites in the same room be
tuned separately; the area feeds room-based media gating below.
Each TV/radio/speaker publishes its own playing-state and room; Murdock tightens the verify threshold while something plays in the active satellite's room (see Tuning for the per-source matrix).
alias: Murdock — publish media players
trigger:
- platform: state
entity_id:
- media_player.living_room_tv # ← every TV/radio you care about
- media_player.kitchen_radio
action:
- service: mqtt.publish
data:
topic: "murdock/context/media/{{ trigger.entity_id }}"
retain: true
payload: >-
{{ {'playing': is_state(trigger.entity_id,'playing'),
'area': area_name(trigger.entity_id)} | to_json }}
mode: queued
max: 20Both automations are generated with your topic prefix in the Web UI's MQTT card.
Murdock deliberately does not do this. It only ever sees audio after the wake word has already fired, so ducking from here would ramp the volume down during your first word — too late to help recognition, and loudest exactly when it is most in the way.
Home Assistant's own assist_satellite entity flips to listening the
moment the wake word lands, which is the right trigger. A blueprint
ships with the repo:
Import: Murdock — duck media while Assist is listening
Pick the satellite, pick the media players in that room, choose how far to duck. One automation per room.
Three properties worth knowing:
- Only the volume is touched. Playback state is left alone on purpose — restoring a snapshot could resume something you paused during the conversation.
- A safety timeout restores the volume even if the satellite never reports idle, so a crashed pipeline cannot leave a room quiet forever.
-
mode: restart, so a second wake word while the assistant is still answering resets the timer instead of queueing a second restore.
The volume comes back when the satellite returns to idle, which is
after the spoken answer — so the room stays quiet while the assistant
talks, not just while it listens.
GET /api/settings/mqtt-context (or via the add-on ingress) shows the
live context cache: connected state, every media entry with age, plus
active_satellite / active_satellite_area. If an automation doesn't
arrive, check there first.