Listen to this post:

Téléfonefix is a kid-friendly telephone system allowing kids to safely call relatives, locally and abroad.

The main features I sought when building téléfonefix were:

  • Do not expose the user to a screen (no apps or fancy phones).
  • Use a physical phone.
  • Perform calls locally and internationally.
  • Permit/decline calls based on a ruleset. Specifically:
    • only permit calls to be made to an adult-controlled set of numbers.
    • timezones and “awake/asleep” schedules, to avoid calling Europe at their 1am
    • prevent inbound calls entirely.
  • As user-friendly as possible.

Shopping list

  • A corded phone: any phone with an RJ11 connector will work. A rotary dial phone would add cool factor for kids old enough to dial them. I used this “Telephone for Seniors” . The pictured speed dial numbers are perfect for very young kids to choose who they want to call.
  • An analog telephone adapter: I use the Grandstrean HT801 . It is well-documented and reliable.
  • A raspberry pi (or any compute really). For this setup, I used a Pi 4 B. It embeds WiFi and an ethernet port making it easy to connect both to your network and directly wire the HT801 to the Pi.
  • A twilio account with a number purchased (~ $1.15/month).

Architecture

Hardware setup

Phone to HT801

We simply plug an RJ11 cable in between the Phone and the HT801.

HT801 to Raspberry Pi

I opted to connect these two directly with an RJ45 as we will see below.

Phone

On the phone itself, to make updating phone numbers easier, I assigned each of the 9 speed dial picture buttons with a number from 100 to 107 and assigned 200 to the last one (which I keep for debugging). Those extensions will be translated later on to actual numbers by a small golang program I built for the occasion, allo-wed .

Twilio setup

First, you will need to create an Elastic SIP Trunk in twilio.

Once created, in the Termination tab, set up the Authentication. Create a new set of credentials (your telefonefix_twilio_user and telefonefix_twilio_password). IP Access control lists are also available.

In the Numbers tab, buy a new number and assign it to the trunk. That number will be your telefonefix_twilio_phone_number value (formatted as '+15555555555'). Note that numbers have a monthly cost independent of usage (~ $1.15/month).

Incoming calls are not something I am looking for, so I did not change Origination.

Téléfonefix stack configuration

I built ansible-role-telefonefix to handle the configuration. Let’s look at a telefonefix.yml playbook example and detail each task:

- hosts: telefonefix
  become: true
  gather_facts: false
  vars:
    telefonefix_asterisk_playback_patterns:
      connecting:
        fr: "Un instant, je vous connecte à \"${contact_name}\""
        pt: "Só um momento. Ligando para \"${contact_name}.\""
      not-allowed:
        fr: "\"${contact_name}\" fait dodo, rappelle plus tard!"
        pt: "\"${contact_name}.\" está dormindo, ligue mais tarde!"
      currently-busy:
        fr: "\"${contact_name}\" est occupé, rappelle plus tard!"
        pt: "\"${contact_name}.\" está occupado, ligue mais tarde!"
      unavailable:
        fr: "\"${contact_name}\" est occupé, rappelle plus tard!"
        pt: "\"${contact_name}.\" está occupado, ligue mais tarde!"

    telefonefix_voice_map:
      en: "en_US-amy-low"
      fr: "fr_FR-siwis-low" 
      pt: "pt_BR-cadu-medium"

    telefonefix_network_subnet: "192.168.100.0/24"
    telefonefix_gateway_ip: "192.168.100.1"
    ht801_mac: # TODO
    ht801_static_ip: "192.168.100.20"
    telefonefix_dns_servers:
      - "8.8.8.8"
      - "8.8.4.4"
    telefonefix_ethernet_interface: "eth0"

    telefonefix_asterisk_extra_sounds_folder: /tmp/telefonefix/extra_sounds
    telefonefix_super_user_override_prefix: '23'

    telefonefix_public_ip: # TODO
    telefonefix_asterisk_phone_user: '6001'
    telefonefix_asterisk_phone_password: # TODO
    telefonefix_twilio_domaine: # TODO
    telefonefix_twilio_phone_number: # TODO
    telefonefix_twilio_user: # TODO
    telefonefix_twilio_password: # TODO
    ht801_password: # TODO

  tasks:
    - name: Generating asterisk voices
      include_role:
        name: ansible-role-telefonefix
        tasks_from: asterisk_playbacks_generate.yml
    - name: DHCP config
      include_role:
        name: ansible-role-telefonefix
        tasks_from: dhcp.yml
    - name: Asterisk setup
      include_role:
        name: ansible-role-telefonefix
        tasks_from: asterisk.yml
    - name: HT801 setup
      include_role:
        name: ansible-role-telefonefix
        tasks_from: ht801.yml

The variables are mostly self-explanatory and can be updated to fit different needs and setups.

We will review the Generating asterisk voices task at the end, and start with the main tasks first.

Task DHCP config

Configures a dhcp server on the raspberry pi, and has it assign a fixed ip to the HT801 in their own shared very local LAN to keep things isolated and simple.

We can also easily run asterisk on a VPS, and have the HT801 point directly to it.

Task Asterisk setup

This is the main task. It:

  • installs docker.
  • pushes the main configuration files.
  • pushes optional sound files for asterisk.
  • launches the asterisk docker container (asterisk + allo-wed ).

Let’s review the main configuration files.

File extensions.conf

extensions.conf defines what happens when a number is dialed.

On the physical phone, I have defined each speed dial pictured number to dial a number from 100 to 107. The last picture is defined as 200, and currently used for debugging .

Calls coming in using one of those numbers are handled here:

extensions.conf#L17-L21
view code (lines 17-21)
; Pattern _1XX matches any three-digit number starting with 1
exten => _1XX,1,NoOp(Calling extension ${EXTEN})
exten => _1XX,n,Set(TRANSLATED_NUMBER=${SHELL(allo-wed -config /opt/asterisk/allo-wed.yml -is-allowed -phone ${EXTEN})})
exten => _1XX,n,NoOp(Translated to: ${TRANSLATED_NUMBER})
exten => _1XX,n,GotoIf($["${TRANSLATED_NUMBER}" = ""]?not_allowed)

We call the allo-wed script with the dialed extension as a parameter. If calls are allowed to this number, it returns the 1XX number to real world number translation, based on its configuration file .

If the call is denied, we let the user know:

extensions.conf#L31-L33
view code (lines 31-33)
; Call not allowed or no mapping found
exten => _1XX,n(not_allowed),Playback(extra/${EXTEN}/not-allowed)
exten => _1XX,n,Hangup()

Otherwise, we announce success and perform the call:

extensions.conf#L23-L29
view code (lines 23-29)
; Call is allowed and number was translated
exten => _1XX,n,Playback(extra/${EXTEN}/connecting)
exten => _1XX,n,Dial(PJSIP/${TRANSLATED_NUMBER}@twilio,30,L({{ telefonefix_call_duration_limit_minutes * 60 * 1000 }}))
exten => _1XX,n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy)
exten => _1XX,n,GotoIf($["${DIALSTATUS}" = "NOANSWER"]?noanswer)
exten => _1XX,n,GotoIf($["${DIALSTATUS}" = "UNAVAILABLE"]?unavailable)
exten => _1XX,n,Hangup()

A parental override can be enabled by setting a telefonefix_super_user_override_prefix value.

For example, with telefonefix_super_user_override_prefix = 23, you will be able to bypass call restrictions on the 101 extensions by dialing 23101.

File pjsip.conf

This is where we configure the actual communication stack.

First we setup our local extension [6001], our physical phone, and set up authentication for it so the HT801 can connect to asterisk.

Then we set up the Twilio trunk for outbound calls, linking and authenticating to our domain.

File rtp.conf

The RTP configuration is lightly edited to reduce the port range. We have a single user, and not having to add port forwarding for the full range can make home router/firewall configuration easier.

File allo-wed.yml

You need an asterisk/allo-wed.yml file alongside your calling playbook (eg in files/asterisk/allo-wed.yml). A configuration example is available in the allo-wed repo .

This file will define who each speed dial extension will call, their language and name (for fancy spoken messages), their timezone and awake hours, and the number to dial.

Number format tends to be the tricky part here. I have had success putting the full number with country code and leading +. For example, to dial a French cell: '+336XXXXXXXX'.

Task HT801 setup

We send a POST request to the HT801’s administration API, essentially setting up communication and authentication with the asterisk server.

Bonus: Generating asterisk voices

Using my previous Text To Speech Setup and asterisk’s Playback feature, I generate custom messages that asterisk will play for different occasion. For example, when dialing a specific contact, asterisk will announce in the callee’s language “Dialing XXX, please hold”.

The messages are generated based on these variables:

    telefonefix_asterisk_playback_patterns:
      connecting:
        fr: "Un instant, je vous connecte à \"${contact_name}\""
        pt: "Só um momento. Ligando para \"${contact_name}.\""
      not-allowed:
        fr: "\"${contact_name}\" fait dodo, rappelle plus tard!"
        pt: "\"${contact_name}.\" está dormindo, ligue mais tarde!"
      currently-busy:
        fr: "\"${contact_name}\" est occupé, rappelle plus tard!"
        pt: "\"${contact_name}.\" está occupado, ligue mais tarde!"
      unavailable:
        fr: "\"${contact_name}\" est occupé, rappelle plus tard!"
        pt: "\"${contact_name}.\" está occupado, ligue mais tarde!"

Using these voices, which I found to be the best for each language I was interested in:

    telefonefix_voice_map:
      en: "en_US-amy-low"
      fr: "fr_FR-siwis-low" 
      pt: "pt_BR-cadu-medium"

The Generating asterisk voices task will start gopipertts on the local machine, generate all the messages we need, and convert them to be playable by asterisk.

Firewall configuration

Twilio needs to be able to reach asterisk on the following ports, you will need to configure your firewall and port forwarding accordingly:

  • 5060/udp: SIP po rt.
  • 10000-10100/udp: the RTP port range.

Conclusion and future improvements

The phone has been a huge success, and the concept easily graspable by young kids.

A few potential improvements:

  • preventing hangups… Kids get excited, pick up the phone, hang it up, pick it up again. Being able to prevent premature call end would be useful (twilio starts charging immediately too, so you’ll be billed the full minute for that 3s call-hang-up).
  • limiting calls per day/hours? It could be useful to have allo-wed prevent the user from calling the same contact 15 times within a minute.
  • better error handling. There are cases where calls fail with little to no feedback. I’m not sure where the issues come from, but gathering more information from twilio/asterisk and speaking it to the user would be helpful.
  • forcing speaker mode… But that would require tweaking with the phone itself.

If you implement this and have feedback, please reach out !

Core Components

Software & Configuration

References & Inspiration