SkillAgentSearch skills...

Rotary

Adventures with rotary phones and asterisk

Install / Use

/learn @mnutt/Rotary
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Rotary Phone Project

The phone in use

This is an account of a project we recently did. The files aren't intended to constitute a cohesive runnable project, they're just a loose collection of scripts. It's mostly here to remind me how it works, but hopefully it's useful to others.

Background

Recently we went to the MoMA with my 4 year old son. Some modern art is hard to appreciate as a kid, but the exhibit he loved was one where they had a bunch of rotary phones and you could dial a number and it would read you a poem. I wanted to replicate something like this at home.

Features

Some things I wanted it to be able to do:

  • Dial out to a short list of family contacts. It's not something I think about much, but when I was a kid there was a phone on the wall and once I could reach it, I could use it. Now, if you're not old enough to have a cell phone, you also can't call anyone at all.
  • Allow incoming calls from that same list of family contacts. Requires an actual phone number.
  • On a different extension, hear a random joke.
  • ~~Hear a random k-pop song.~~ This was an easy thing to start with, but not actually that fun. The audio quality is pretty terrible for music.
  • Get NYC MTA train status. My son loves the subway, and is already pretty proficient at using a unix terminal to query the status of different trains, but it would be fun to hear an announcement too.

Equipment

60 years ago, every household in America had one or more rotary phones, so they're not exactly hard to find. I bought one off of Ebay for ~$25. Not all of them are guaranteed to be in working order, but these things are pretty solid so if the outside looks undamaged, it's likely alright. Worst case you might have to spend another $25 and try again. I got a Western Electric Bell 500.

If we lived in 1960 (or even 2000) I could plug it into the wall and the project would be mostly done. But if I want to do anything interesting, I need to adapt it to VoIP. My initial thought was to hack up the inside of the phone and use a Raspberry Pi or ESP32 or something, but between driving 48V DC or replacing most of the internals it seemed a bit out of reach. Instead, I used the phone unmodified and connected it to a Grandstream GS-HT802 which lists rotary support in the specs. This currently goes for ~$50 on Amazon.

To accomplish the other features, I wanted to use Asterisk, an open source PBX. Using Asterisk to make a phone that tells you jokes is a bit like using Kubernetes to host your blog, but I had a little bit of experience with it from ~20 years ago and it seemed to do everything I wanted. Ideally I would have been able to attach the Grandstream adapter directly to the machine running the Asterisk server. But for the equipment I had, I ended up attaching the Grandstream to the Raspberry Pi in my son's room, and having it bridge wifi to an old server under my desk running Asterisk. This hasn't flaked out yet but seems like it could be brittle. At some point I'd like to consolidate these into a more powerful Raspberry Pi 5 that runs Asterisk and is directly connected to the GrandStream.

Diagram of equipment setup

Rotary Phone Setup

Pretty much just plug the RJ11 port of the phone into port 1 on the GrandStream. This should mostly Just Work, but there are some potential caveats depending on the rotary phone you receive. I'd try it first, but if you have trouble with it ringing later in the process you can try:

  • It's possible that your phone has been wired for a "party line" instead of regular service. This would require unscrewing the cover (make sure it's unplugged from the Grandstream) and a small amount of rewiring which differs based on your phone model.
  • The one I received wasn't wired for a party line, but was effectively muted. I had to take the cover off and unhook part of the ringer. I haven't seen discussion of this online anywhere.

Opening it up:

Inside the phone

After everything else checked out, I realized the ringer had been locked, and moved it to this position to fix:

Mute lever

Asterisk setup

I installed Asterisk via dpkg on Ubuntu 22. Setup was made a little more complicated by the RPi wifi bridge, which meant that the Grandstream was techically behind NAT from the perspective of Asterisk. Asterisk is trying to move from legacy sip module to pjsip, but I was only able to get sip to work so I stuck with that. The /etc/asterisk/sip.conf relevant parts:

[rotary]
type=friend
nat=force_rport,comedia
secret=s3cr3t
host=dynamic
context=rotary-context
qualify=yes

The nat and host=dynamic aspects wouldn't be necessary if the Grandstream was on the same network as Asterisk.

Connecting the phone to Asterisk

Plugging the Grandstream device in, it automatically obtained an IP via DHCP and I was able to log into its web interface using default credentials. Setup was pleasantly straightforward, I just configured fx-1's sip server to point to the Asterisk server's address.

Relevant settings from fxs-1:

Active: Yes
Primary SIP Server: [asterisk ip]
NAT Traversal: Keep-Alive *
SIP User ID: rotary
SIP Authenticate ID: rotary
SIP Registration: Yes
Enable SIP OPTIONS/Notify Keep-Alive: OPTIONS *
Enable Pulse Dialing: Yes
Enable Hook Flash: Yes
Enable High Ring Power: Yes **

* - only required because of the RPi wifi bridge
** - maybe not necessary, but makes ringer more likely to work

At this point, you should be able to pick up the phone's handset and get a dialtone. If you don't, there are some different debugging avenues:

Asterisk side

You can run sudo asterisk -rvvvvv to start the asterisk repl. Then:

  • sip show peers should list rotary/rotary with the right Host and hopefully a Status of "OK"
  • sip set debug on will give loads of SIP debug data, after enabling that you can unplug and replug the grandstream and hopefully see it trying to connect

Grandstream side

If you're not seeing any peers or any sip logs from asterisk, you can also use the Grandstream web UI and enable sip logging to hopefully see what is going on.

Building Stuff in Asterisk

At this point the phone is connecting to Asterisk and the real fun can begin. :)

Play a K-Pop song

The easiest one first. Asterisk can play back an audio file, but is relatively limited in the types of files it can play. You'll likely need to convert your file to 8KHz wav. Then put something like this in /etc/asterisk/extensions.conf:

[rotary-context]
exten => 1234,1,Answer()
    same => n,Playback(/path/to/file.wav)
    same => n,Hangup()

You can reload Asterisk config with (among others) sudo systemctl reload asterisk. This will let you dial 1,2,3,4 on the phone, answer, play the audio, and hang up on you.

Play MTA Subway Status

Speech Synthesis

For this one, we need some sort of text-to-speech synthesis. TTS has come a long way since Asterisk was initially created back in the early 2000s, so I opted for hooking in a third party TTS engine. I chose piper which sounds pretty good and notably is fast--it can run on a Raspberry Pi.

Piper can pipe to stdout, or a file or whatever. I was hoping I could shell out to piper with my text and stream the result back to asterisk via stdout, but it was not meant to be. I went so far as to try to write a c Asterisk module that would accept raw audio over stdout, and eventually discovered that a) audio formats are hard, and b) I lack any sort of intuition about debugging when all I can get back are silence and a few popping noises.

Instead I wound up writing an extension subroutine that calls out to piper, has it write to a tempfile, plays the tempfile, then deletes the tempfile. This means the entire speech generation has to complete before it can begin playing, but so far that has not taken longer than ~250ms on my Asterisk server. (a decade+ old core i7)

The speak.sh bash script:

#!/bin/bash

/path/to/piper/piper \
    --length_scale=5 --sentence_silence=0.5 \
    --model /path/to/piper/voices/en_US-amy-medium.onnx \
    --output-raw -f - | \
  sox -v 0.7 -t raw -e signed -r 22050 -b 16 - -r 8000 -b 16 -c 1 -t wav -

This uses the "Amy" medium-fidelity model to generate some speech, then sox to translate it to one of the few audio formats that Asterisk can play.

Then we create the subroutine in extensions.conf:

[speak]
exten => s,1,NoOp(Text to speech)
 same => n,Set(filename="speech-${RAND()}${RAND()}")
 same => n,System(echo "${ARG1}" | /path/to/speak.sh > /tmp/${filename}.wav)
 same => n,Playback(/tmp/${filename})
 same => n,System(rm -f /tmp/${filename}.wav)
 same => n,Return()

Then I can call it like this from a regular extension:

exten => 888,1,Answer() ; Say something
 same => n,GoSub(speak,s,1("Hello world"))

This works pretty well, but a very common pattern is that I want to play some audio for the dialer while at the same time accepting input from them. How maddening is it when you're trying to cancel Comcast and they're making you listen to all of the menu options because they might have changed? If we play audio, then accept input, Asterisk blocks and we're no better than Comcast. Instead of Playback() we can use Read(), which accepts some audio and waits at the same time. We can make a separate subroutine for that:

[speak-get-digits]
exten => s,1,NoOp(Text to speech requesting digits)
    same => n,Set(filename="speech-${RAND()}${RAND()}")
    same => n,Set(digitcount=${IF($["${ARG2}"=""]?1:${ARG2})})
    same => n,System(echo "${ARG1}" | /path/t

Related Skills

View on GitHub
GitHub Stars217
CategoryDevelopment
Updated16d ago
Forks4

Languages

JavaScript

Security Score

80/100

Audited on Mar 24, 2026

No findings