[ Voice-Activated Dog TV in Home Assistant ]
[ Overview |
Requirements |
1. Enable ADB |
2. ADB integration |
3. Find videos |
4. Script |
5. Custom sentence |
6. Intent script |
7. Reload & test |
Troubleshooting |
More TVs / videos ]
[ Overview ]
Voice command that turns on a TV and plays a random YouTube Dog TV video automatically. End-to-end stack: Home Assistant Voice or an Atom Echo satellite captures the phrase, a custom sentence routes it to an intent, the intent fires a script asynchronously, the script powers on the TV via the Android TV Remote integration, waits for the device to fully boot, then deep-links into the YouTube app via an ADB shell command. End result: say "dog tv bedroom" and the TV turns on and starts playing one of a curated list of long-form Dog TV videos — different one each time. The pattern generalises: the same script / sentence / intent triple works for any voice-command-driven "open a deep link on a specific TV" automation. ADB is doing the heavy lifting on the TV side — the rest is HA glue. Swap the URLs and the trigger phrase and you've got cooking shows, ambient music videos, sunset clips for plants, whatever.
[ Requirements ]
— Home Assistant with Home Assistant Voice or an
Atom Echo satellite (or any Assist pipeline you
can speak into — the phone companion app's Assist works
fine for testing)
— An Android TV / Google TV device with YouTube
installed (Hisense, Nvidia Shield, Chromecast with Google TV,
Onn 4K, etc.)
— Android Debug Bridge (ADB) integration
installed in HA — sends shell commands to the TV
— Android TV Remote integration installed in
HA — reliable power-on, plus the standard transport controls
— ADB / Network Debugging enabled on the TV
Both integrations are needed: Android TV Remote owns the power state
(it can wake the TV from standby reliably), ADB owns the deep-link
firing. Trying to do power-on through ADB alone is hit-or-miss because
the ADB daemon often isn't reachable while the TV is fully asleep.
[ Step 1 — Enable ADB on Your TV ]
On the Android / Google TV:
1. Settings → Device Preferences → About
2. Click Build seven times rapidly to enable
Developer Options
3. Back out to Device Preferences → Developer Options
4. Enable Network Debugging (or USB Debugging if
you're tethering)
After this the TV listens on TCP port 5555 for ADB
connections. Some TVs also expose this on a custom port; check the
Developer Options screen.
Security note. Network ADB is unauthenticated until
the first connection prompt is accepted, and even after that the
shell is full-privilege. Keep the TV on a trusted VLAN. Don't open
port 5555 to the internet for any reason.
[ Step 2 — Add the ADB Integration in HA ]
1. Settings → Devices & Services → Add Integration 2. Search for Android Debug Bridge 3. Enter the TV's IP address and port55554. Accept the ADB authorization prompt that appears on the TV screen — tick "always allow from this computer" so it sticks across reboots This creates amedia_playerentity along the lines ofmedia_player.bedroom_tv_adb. That entity is what we send theandroidtv.adb_commandservice call to in step 4. Keep the Android TV Remote integration set up too — it handles power-on and transport controls more reliably than ADB (particularly waking the TV from cold standby). The two integrations coexist: ATR for power, ADB for the deep link.
[ Step 3 — Find Working YouTube Video URLs ]
Curate a short list of long-form, publicly-available Dog TV videos.
Good candidates for the bedroom:
— 10 Hours No Loops —
https://www.youtube.com/watch?v=ggkYESx1Fpc
— Babbling Brook with Birds (8h) —
https://www.youtube.com/watch?v=nYcHi9EgUHs
— Relax My Dog TV (10h) —
https://www.youtube.com/watch?v=ru1z02CXbaA
— DOGTV Outdoor Adventure (1h) —
https://www.youtube.com/watch?v=QVfiP1x-vng
— Dog Relax & Stress Relief (1h) —
https://www.youtube.com/watch?v=j_-azXHHGVc
Verify each URL is public before adding it. YouTube videos go
private without warning, age-restricted ones won't open from a
deep link without a logged-in account, and copyright takedowns
silently break the script. A monthly "are these still alive?"
audit isn't a bad idea.
Tip: Prefer videos that are explicitly published as
ambient / loop content. Anything with mid-roll ads, intros, or
narration defeats the purpose — you want continuous low-stim
visuals.
[ Step 4 — Create the Script in scripts.yaml ]
Open File Editor and edit /config/scripts.yaml:
dog_tv_bedroom:
alias: Dog TV Bedroom
sequence:
- variables:
dog_tv_url: >
{% set videos = [
'https://www.youtube.com/watch?v=ggkYESx1Fpc',
'https://www.youtube.com/watch?v=nYcHi9EgUHs',
'https://www.youtube.com/watch?v=ru1z02CXbaA',
'https://www.youtube.com/watch?v=QVfiP1x-vng',
'https://www.youtube.com/watch?v=j_-azXHHGVc'
] %}
{{ videos | random }}
- action: media_player.turn_on
target:
entity_id: media_player.bedroom_tv # Android TV Remote entity
- delay:
seconds: 15 # wait for TV to fully boot
- action: androidtv.adb_command
target:
entity_id: media_player.bedroom_tv_adb # ADB entity
data:
command: "am start -a android.intent.action.VIEW -d '{{ dog_tv_url }}'"
mode: single
Why two entity IDs? —media_player.bedroom_tv— Android TV Remote entity. Owns power on/off. —media_player.bedroom_tv_adb— ADB entity. Receives the YouTube deep-link command. Two integrations, two entities, one TV. The script touches both in sequence. Why a 15-second delay? The TV needs time to fully boot before its ADB daemon can accept the deep-link intent. Too short and YouTube opens to its home screen but the video never loads (or worse, theam startfails silently). On faster TVs you can drop this to 8–10; on a sluggish older Android TV stick, 20 isn't unreasonable. Tune to your hardware. Why random? The{{ videos | random }}Jinja filter picks a different URL on each invocation so your dog isn't watching the same 10-hour brook every day. Same pattern applies to any "pick one from a curated set" need. mode: single. If the script is already running (e.g. dog walked past the satellite again mid-15-second-delay) new invocations are dropped. Switch torestartif you want the new call to interrupt and restart the boot+launch cycle instead. See the four-modes section of the HA scripting quick-start.
[ Step 5 — Create the Custom Sentence ]
Create a new file at/config/custom_sentences/en/dog_tv.yaml(make thecustom_sentences/en/directory if it doesn't exist):
language: en
intents:
PlayDogTVBedroom:
data:
- sentences:
- "dog tv bedroom"
- "dog tv for (name1|name2) bedroom"
- "start dog tv bedroom"
PlayDogTVLivingRoom:
data:
- sentences:
- "dog tv living room"
- "dog tv for (name1|name2) living room"
- "start dog tv living room"
Replacename1andname2with your dogs' names (or whatever per-dog naming makes sense). The(a|b)syntax is HA's sentence alternation operator — any one of the listed alternatives matches. Important: keep the trigger phrase simple and unique. Avoid starting with the word "play" — HA's built-inHassMediaSearchAndPlayintent will intercept it before the custom sentence ever gets a chance to match, and you'll see "no media player exposed" errors that look nothing like a custom sentence problem. "dog tv bedroom" is plenty distinct. Several phrasing variants per intent improves voice-recognition robustness without adding ambiguity — STT (speech-to-text) sometimes hears "start dog tv bedroom" when you said "dog tv bedroom", and either way the intent fires.
[ Step 6 — Add the Intent Script ]
Open/config/configuration.yamland add underintent_script::
intent_script:
PlayDogTVBedroom:
speech:
text: "Putting on Dog TV in the bedroom."
action:
service: script.turn_on
target:
entity_id: script.dog_tv_bedroom
PlayDogTVLivingRoom:
speech:
text: "Putting on Dog TV in the living room."
action:
service: script.turn_on
target:
entity_id: script.dog_tv_living_room
Three things that look like minor stylistic choices but are actually load-bearing: 1. Async-dispatch the script withscript.turn_on+target:. This is the single most important detail on the page. The script's 15-second TV-boot delay is longer than the Voice satellite's intent-response timeout. If the intent calls the script directly (action: { service: script.dog_tv_bedroom }), the intent BLOCKS for the full 15 seconds before the TTS response can play — the satellite shows a red error ring, the phone Assist UI looks broken, and the next utterance can't fire until the previous one's script returns.script.turn_onwith a target fires the script asynchronously: the intent returns immediately, TTS plays, the satellite goes back to listening, and the 15-second boot wait runs in the background where nobody's waiting on it. The same pattern applies to any intent that fires a multi- second script (fill-a-bag, multi-step macros, anything with a meaningfuldelay:orwait_template:). 2. Use the single-mapping form for the action block, not a list. The single-mapping form (action: { service: …, target: … }in flow style, or two indented keys in block style) is whatintent_scriptexpects. The list-of-actions form (action:on its own line, hyphens below) used to fail silently on older HA versions; current HA generally accepts both, but the single mapping is the safer default and plays nicer with older docs / older installs you might migrate from. 3.service:vsaction:as the key name — either works on current HA. HA 2024.8+ aliasedaction:as a synonym forservice:across most config blocks, includingintent_script. Pre-2024.8 installs only acceptservice:. If you're on a current install the two are interchangeable; if you maintain configs that need to work across versions,service:is the lowest- common-denominator choice. The TTSspeech.textfield is what the satellite says back to confirm the command landed. Keep it short and unambiguous so you can tell at the speaker whether the command was matched even before walking into the room to check the TV.
[ Step 7 — Reload and Test ]
1. Developer Tools → YAML → All YAML → Reload
(or restart Home Assistant outright if you'd rather)
2. Open Developer Tools → Assist (the chat
bubble icon in the sidebar)
3. Type the trigger phrase:
dog tv bedroom
4. Confirm the TV turns on and a video plays within ~15–20
seconds
5. Now test by voice from your Voice satellite
Use the Assist chat for testing, not the Sentence Parser.
The Sentence Parser in Developer Tools only validates that the intent
matches — it does NOT execute the action. Always test
end-to-end via the Assist chat bubble (or by speaking to the
satellite); the parser will lie to you about whether anything
actually runs.
For voice-vs-typed mismatches (works in chat, doesn't work by
voice), pull up the Voice Assistant debug log
under Settings → Voice Assistants → (your pipeline)
→ Debug. The log shows the raw STT transcription, so you
can see exactly what the STT engine thought you said — nine
times out of ten the fix is adding that transcription as another
sentence alternate in dog_tv.yaml.
[ Troubleshooting ]
"No media player exposed" error
Built-in HassMediaSearchAndPlay intent is
intercepting. Don't start the trigger phrase with "play".
"Unknown intent" error
Custom sentences not loaded. Verify the file path
(/config/custom_sentences/en/dog_tv.yaml), reload
Conversation under Developer Tools → YAML, or restart HA
once after creating the directory structure for the first time.
TV turns on but no video plays
15-second delay was too short. Watch the TV boot once with a
stopwatch and pad another 3–5s. Alternatively, switch
from delay-based waiting to a wait_template
polling for media_player.bedroom_tv state to
leave standby.
Red ring on the Voice satellite mid-command
Script is running synchronously and blocking the intent
response past the satellite's timeout. Use
script.turn_on with target: for
async firing (see step 6).
"Unexpected error occurred"
Syntax mismatch inside intent_script. Most common
on older HA installs: the list-of-actions form (hyphenated
actions under action:) silently failing. Switch
to the single-mapping form — action: with
two indented keys (service: +
target:), no leading -. Pre-2024.8
installs also require service: as the key name
(not the newer action: alias).
Video URL no longer plays
YouTube went private / age-restricted / region-locked / DMCA'd.
Open the URL in a browser to verify, then swap it in
scripts.yaml. The remaining URLs in the list
continue to work in the meantime.
Works in Assist chat but not by voice
STT is transcribing differently than you're typing. Check the
Voice Assistant debug log (Settings → Voice Assistants
→ Debug) for the actual transcription, then add that
exact phrasing as another sentence alternate.
ADB connection drops after TV power-cycle
Some TVs revoke the "always allow from this computer" ADB
grant on factory-reset-equivalent reboots. Re-accept the
prompt on the TV screen, or set a static IP on the TV so the
fingerprint stays bound to the same address.
[ Adding More TVs / More Videos ]
Adding another TV.
Repeat steps 4–6 for each TV, creating a new script and
intent pair per location. The intent name, the script name, and
the trigger sentence should all carry the room label.
For non-Android TVs (LG webOS, Samsung Tizen, Roku) you can't use
the ADB deep-link path — switch the action to
media_player.select_source targeting the YouTube app
already installed on that TV:
dog_tv_living_room:
alias: Dog TV Living Room
sequence:
- action: media_player.turn_on
target:
entity_id: media_player.living_room_tv
- delay:
seconds: 5
- action: media_player.select_source
target:
entity_id: media_player.living_room_tv
data:
source: YouTube
mode: single
You lose per-URL control on the webOS path — it just opens the YouTube app and resumes whatever was last playing. For Roku, the same shape works withsource: "YouTube"via the Roku integration'sselect_source. Adding more videos. Just append URLs to thevideoslist inside thevariablesblock:
- variables:
dog_tv_url: >
{% set videos = [
'https://www.youtube.com/watch?v=VIDEO_ID_1',
'https://www.youtube.com/watch?v=VIDEO_ID_2',
'https://www.youtube.com/watch?v=VIDEO_ID_3'
] %}
{{ videos | random }}
Therandomfilter weights each entry equally. If you want certain videos to play more often (e.g. the 10-hour ones weighted heavier than the 1-hour ones because they last the whole nap), build a weighted list manually — each URL repeated N times for an N-weight — or replace the Jinja with a more elaborate template using{{ range(0, videos|length) | random }}and an external weights array. Adding non-Dog-TV variants. The whole pattern is just "voice command → turn on device → deep-link a URL." Swap the video list for kids' lullabies, ambient cooking shows, plant-care livestreams, anything with a deep-linkable YouTube URL. Each variant gets its own script, sentence, and intent — or share one script with afieldparameter to pick the list at call time.
[ See Also ]
HA Scripting Quick-Start # script anatomy, modes, fields, debugging
Volcano Hybrid + HA # another HA Assist voice-command build (6 intents)
HA Snippets # small reusable automations and templates
HA Blueprints # reusable parameterised automation templates
Home Automation (parent) # everything else on the HA side of the site
