How did I set Up Outbound TTS Phone Calls in Home Assistant with Asterisk, Fritz!Box & Vodafone

This guide walks through how I set up outbound voice calls from Home Assistant — where HA calls my mobile phone and plays a spoken TTS message. I use a Fritz!Box router with a Vodafone SIP connection, the TECH7Fox Asterisk add-on, and Google Translate TTS with ffmpeg for audio generation.

I also cover every debugging step I went through, so if something breaks for you, you know how to diagnose it.


My Setup

  • Home Assistant OS on Raspberry Pi

  • Vodafone cable connection in Baden-Württemberg, Germany

  • Fritz!Box router with Vodafone SIP connection

  • TECH7Fox Asterisk add-on (Home Assistant add-on store)

  • File Editor add-on (to edit Asterisk config files)


How It Works


HA Automation → script.make_tts_call → Google TTS → ffmpeg → Asterisk → Fritz!Box → Vodafone SIP → Your Phone

  1. An automation triggers script.make_tts_call with a message and phone number

  2. HA generates a TTS audio file via Google Translate (free, no API key)

  3. ffmpeg converts the MP3 to a WAV format Asterisk can play

  4. Asterisk dials the primary number via Fritz!Box

  5. If no answer after 30 seconds, it dials a fallback number

  6. When answered, the voice message plays (repeated twice)


Phase 1 — Fritz!Box: Create an Internal SIP Account

Fritz!Box acts as your SIP proxy — it handles the Vodafone connection and Asterisk registers as an internal phone.

  1. Open your browser → go to fritz.box

  2. Go to Telephony → Telephony Devices → Configure New Device

  3. Select Telephone (with and without answering machine)

  4. Select LAN/WLAN (IP telephone)

  5. Set name: HomeAssistant

  6. Set username HomeAssistant

  7. Set a password (e.g. hapass123) — write this down

  8. Fritz!Box assigns an internal number like **620write this down

  9. Click Apply


Phase 2 — Install Asterisk Add-on

  1. In HA go to Settings → Add-ons → Add-on Store

  2. Click the three dots (top right) → Repositories

  3. Add: https://github.com/TECH7Fox/asterisk-hass-addons

  4. Search AsteriskInstall

  5. Enable Start on boot and Watchdog → click Start

The add-on will create its config directory at:


/addon_configs/3e123456_asterisk/asterisk/custom/

Note: the folder ID (3e123456_asterisk) may differ on your installation. Check /addon_configs/ after first start.


Phase 3 — Configure Asterisk

Asterisk config files go in /addon_configs/3e123456_asterisk/asterisk/custom/. Use the File Editor add-on to create these files.

pjsip.conf

Important: This Asterisk version uses PJSIP, not the older SIP channel. Using SIP/ in dial strings will fail with “No channel type registered for ‘SIP’”.

Replace 620, hapass123, and HomeAssistant with the values from your Fritz!Box setup.


[transport-udp]

type=transport

protocol=udp

bind=0.0.0.0:5060

[fritzbox-auth]

type=auth

auth_type=userpass

username=HomeAssistant

password=hapass123

[fritzbox-aor]

type=aor

contact=sip:192.168.178.1

[fritzbox]

type=endpoint

outbound_auth=fritzbox-auth

aors=fritzbox-aor

from_user=HomeAssistant

from_domain=192.168.178.1

context=from-internal

disallow=all

allow=ulaw

allow=alaw

send_pai=yes

trust_id_outbound=yes

[fritzbox-reg]

type=registration

outbound_auth=fritzbox-auth

server_uri=sip:192.168.178.1

client_uri=sip:[email protected]

retry_interval=60

extensions.conf

This handles two scenarios:

  • [outbound] — simple single call with playback

  • [alert-with-fallback] — calls primary number, falls back to secondary if no answer, sets caller name


[outbound]

exten => s,1,Answer()

same => n,Wait(2)

same => n,Playback(/media/ha_alert)

same => n,Hangup()

[alert-with-fallback]

exten => _+.,1,Answer()

same => n,Set(PRIMARY=${CUT(EXTEN,_,1)})

same => n,Set(SECONDARY=${CUT(EXTEN,_,2)})

same => n,Set(CALLERID(name)=Home Alert)

same => n,Dial(PJSIP/${PRIMARY}@fritzbox,30,A(/media/ha_alert))

same => n,GotoIf($["${DIALSTATUS}" = "ANSWER"]?done)

same => n,Dial(PJSIP/${SECONDARY}@fritzbox,30,A(/media/ha_alert))

same => n(done),Hangup()

Key design decisions:

  • Both numbers are encoded in the extension using _ as separator: Local/+49XXX_+49YYY@alert-with-fallback

  • Answer() at the start stabilizes the LOCAL channel — without it, the channel dies after the first Dial() times out and the fallback never fires

  • A(/media/ha_alert) plays the audio file to the called party immediately when they answer

  • /n flag on the LOCAL channel disables optimization that was killing the channel between retries

  • 30-second ring timeout per number

Restart the Asterisk add-on after creating/modifying these files.


Phase 4 — TTS Audio Generation

Add these shell commands to your configuration.yaml:


shell_command:

generate_tts: "curl -s 'https://translate.google.com/translate_tts?ie=UTF-8&q={{ message | urlencode }}&tl=en&client=tw-ob' -A 'Mozilla/5.0' -o /media/ha_alert.mp3"

convert_tts: "ffmpeg -y -i /media/ha_alert.mp3 -filter_complex 'aevalsrc=0:c=mono:s=8000:d=1[silence];[0:a][silence][0:a]concat=n=3:v=0:a=1[out]' -map '[out]' -ar 8000 -ac 1 -acodec pcm_s16le /media/ha_alert.wav"

  • generate_tts — downloads TTS audio from Google Translate (free, no API key required)

  • convert_tts — converts MP3 to 8kHz mono WAV (required by Asterisk) and repeats the message twice with a 1-second pause between

Note: Do not combine these into one command using && in a YAML > block — the line folding causes the && to be passed incorrectly to the shell.

Restart HA after adding these.


Phase 5 — The Script

Create a reusable script in Settings → Scripts → Add Script → Edit in YAML:


alias: Make TTS Call

fields:

phone:

description: Primary phone number

example: "+4917XXXXXXXX"

fallback_phone:

description: Fallback phone number if primary does not answer

example: "+4917YYYYYYYY"

message:

description: Message to speak

example: "Motion detected at front door"

sequence:

- service: shell_command.generate_tts

data:

message: "{{ message }}"

- service: shell_command.convert_tts

- service: hassio.addon_stdin

data:

addon: 3e123456_asterisk

input: "channel originate Local/{{ phone }}_{{ fallback_phone }}@alert-with-fallback/n application Wait 120"

mode: single


Phase 6 — Using It in Automations

Add this to any automation’s action:


- service: script.make_tts_call

data:

phone: "+4917XXXXXXXX"

fallback_phone: "+4917YYYYYYYY"

message: "Motion detected at the front door"

Or with dynamic AI-generated messages (see bonus section below).


Debugging with Developer Tools

Throughout the setup, I used Developer Tools → Actions to test every step independently. Here is the full debugging sequence in order.

1. Verify the SIP peer connected to Fritz!Box


action: hassio.addon_stdin

data:

addon: 3e123456_asterisk

input: "pjsip show endpoints"

2. Make a test call using a built-in Asterisk sound


action: hassio.addon_stdin

data:

addon: 3e123456_asterisk

input: "channel originate PJSIP/+4917XXXXXXXX@fritzbox application Playback tt-monkeys"

If your phone rings and plays a monkey sound — Fritz!Box and Asterisk are connected correctly.

3. Verify a dialplan context loaded correctly


action: hassio.addon_stdin

data:

addon: 3e123456_asterisk

input: "dialplan show alert-with-fallback"

4. Reload dialplan without restarting add-on


action: hassio.addon_stdin

data:

addon: 3e123456_asterisk

input: "dialplan reload"

5. Test TTS generation


action: shell_command.generate_tts

data:

message: "Motion detected at front door"

Then check /media/ in File Editor for ha_alert.mp3.

6. Test WAV conversion


action: shell_command.convert_tts

Then check /media/ for ha_alert.wav.

7. Test full call with fallback


action: hassio.addon_stdin

data:

addon: 3e123456_asterisk

input: "channel originate Local/+4917XXXXXXXX_+4917YYYYYYYY@alert-with-fallback/n application Wait 120"


Hope this helps someone avoid the hours of debugging I went through! Feel free to ask questions in the comments.

1 Like

Hi Sushil,

thanks for the how to.
I have a problem with phase 5.
When I’m trying to save it always tells me YAML Error duplicate mapping key.

Can you please post the code with indentation, looks like the editor stripped it while saving.

looks like all conf examples are stripped.

thx
.g