Home Assistant SMS Alerts via Asterisk and voip.ms

In the previous post we setup inbound and outbound SMS routing through a LAN Asterisk server using a voip.ms SIP trunk. In this post we will make use of this functionality to allow a Home Assistant instance in the LAN to send notifications via SMS. The nice thing about this setup is that critical notifications can be sent to the device(s) even if it is outside the LAN and even outside reach of data connectivity.


Network Architecture

The architecture involves Home Assistant running a connector which connects to the Asterisk server over the Asterisk Management Interface (AMI). The dialplan on Asterisk picks up the messages and sends them over the voip.ms trunk just like any other outbound message.


Asterisk Setup

Enable AMI

Edit /etc/asterisk/manager.conf to add:

[general]
enabled = yes
port = 5038
bindaddr = 0.0.0.0

[homeassistant]
secret = your_ami_password
read = originate
write = originate
deny = 0.0.0.0/0.0.0.0
permit = <HA_Static_IP|HA_Subnet>/255.255.255.255 ; replace with 255.255.255.0 for subnet (or as appropriate for your subnet)

Since AMI is a powerful surface, keeping it restricted to known hosts is a good idea. We configure a user (homeassistant) with a secure password and restrict it to read = originate and write = originate which gives permissions to this user for channel originates. Note however that this a weak restriction as AMI is powerful and the user can circumvent this to do other actions quite likely if they wanted to.

Reload by running asterisk -rx "manager reload"

Dialplan for Home Assistant

We add a dedicated context for HA messages – we take the alert text and destination number as channel variables from the AMI originate action and trigger a message send over the voip.ms trunk. I created /etc/asterisk/homeassistant-extensions.conf with the following:

[ha-sms]
exten => _X.,1,NoOp(HA alert SMS to ${EXTEN})
 ; Message body is passed as a channel variable from AMI
 same => n,Set(MESSAGE(body)=${HA_SMS_BODY})
 same => n,Set(MESSAGE(from)=sip:<DID_Number>@asterisk.lan)
 same => n,MessageSend(pjsip:voipms/sip:${EXTEN}@<POP Server>,sip:<DID_Number>@<POP Server>)
 same => n,Log(NOTICE, HA SMS to ${EXTEN} status: ${MESSAGE_SEND_STATUS})
 same => n,Hangup()

Then included it in /etc/asterisk/extensions.conf with #include homeassistant-extensions.conf

Reload the dialplan with asterisk -rx "dialplan reload". With this dialplan setup, Home Assistant is expected to send an Originate action with the local channel (Local/NUMBER@ha-sms) to the ha-sms context. The message body is expected in the HA_SMS_BODY variable.


Home Assistant Setup

Custom Notify Component

HA has some Asterisk Integrations. But in the age of AI, it is easier to just build a small, custom component for what we want. Essentially we just need to open the TCP socket, authenticate and send some text across to the Asterisk server. Create the following under your HA config directory:

custom_components/asterisk_sms/
__init__.py
manifest.json
notify.py

__init.py__ — empty file.

manifest.json:

{
"domain": "asterisk_sms",
"name": "Asterisk SMS",
"version": "1.0.0",
"documentation": "",
"dependencies": [],
"codeowners": [],
"requirements": []
}

notify.py:

import socket
import logging
from homeassistant.components.notify import BaseNotificationService, ATTR_TARGET

_LOGGER = logging.getLogger(__name__)

CONF_HOST     = "host"
CONF_PORT     = "port"
CONF_USERNAME = "username"
CONF_PASSWORD = "password"


def get_service(hass, config, discovery_info=None):
    return AsteriskSMSNotificationService(
        host     = config.get(CONF_HOST, "asterisk.lan"),
        port     = config.get(CONF_PORT, 5038),
        username = config[CONF_USERNAME],
        password = config[CONF_PASSWORD],
    )


class AsteriskSMSNotificationService(BaseNotificationService):

    def __init__(self, host, port, username, password):
        self.host     = host
        self.port     = port
        self.username = username
        self.password = password

    def send_message(self, message="", **kwargs):
        targets = kwargs.get(ATTR_TARGET) or []
        if not targets:
            _LOGGER.error("asterisk_sms: no target number specified")
            return
        if isinstance(targets, str):
            targets = [targets]
        for number in targets:
            self._send(number, message)

    def _send(self, number, message):
        try:
            with socket.create_connection((self.host, self.port), timeout=10) as s:
                s.recv(1024)  # AMI banner

                self._ami(s, (
                    f"Action: Login\r\n"
                    f"Username: {self.username}\r\n"
                    f"Secret: {self.password}\r\n"
                ))
                self._ami(s, (
                    f"Action: Originate\r\n"
                    f"Channel: Local/{number}@ha-sms\r\n"
                    f"Application: Wait\r\n"
                    f"Data: 2\r\n"
                    f"Variable: HA_SMS_BODY={message}\r\n"
                    f"Async: true\r\n"
                ))
                self._ami(s, "Action: Logoff\r\n")

            _LOGGER.info("asterisk_sms: sent to %s", number)

        except Exception as exc:
            _LOGGER.error("asterisk_sms: AMI error sending to %s: %s", number, exc)

    @staticmethod
    def _ami(sock, action):
        sock.sendall((action + "\r\n").encode())
        sock.recv(4096)

configuration.yaml

notify:
  - platform: asterisk_sms
    name: asterisk_sms
    host: asterisk.lan
    port: 5038
    username: homeassistant
    password: your_ami_password

Restart Home Assistant after adding the component and configuration.


Testing

Verify AMI is listening

From any LAN host:

telnet asterisk.lan 5038

You should see the AMI banner: Asterisk Call Manager/X.X

Test from HA Developer Tools

In Home Assistant, Go To Settings > Developer Tools > Actions

Select notify.asterisk_sms and fill in:

  • Message: test alert
  • Target: 1XXXXXXXXXX

Hit Perform Action. Watch Asterisk logs for confirmation:

asterisk -rvvv

You should see:

NOTICE: HA SMS to 1XXXXXXXXXX status: SUCCESS

Using in Automations

action: notify.asterisk_sms
data:
  message: "Front door motion detected"
  target: "1XXXXXXXXXX"

The target field accepts a single number or a list for multi-recipient alerts.

Leave a Reply

Your email address will not be published. Required fields are marked *