├── .gitignore
├── README.md
├── asterisk-config
├── extensions.conf
└── sip.conf
├── images
├── diagram.png
├── header.jpg
├── inside.jpg
└── rotary_mute.jpg
├── mta-status
├── .gitignore
├── bin
│ └── mta
├── index.js
└── package.json
└── scripts
├── jokes.js
├── mta-stream.sh
├── mta.sh
├── speak.sh
└── waitfor.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Rotary Phone Project
2 |
3 | 
4 |
5 | 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.
6 |
7 | ## Background
8 |
9 | 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](https://www.moma.org/collection/works/294325). I wanted to replicate something like this at home.
10 |
11 | ## Features
12 |
13 | Some things I wanted it to be able to do:
14 |
15 | * 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.
16 | * Allow incoming calls from that same list of family contacts. Requires an actual phone number.
17 | * On a different extension, hear a random joke.
18 | * ~~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.
19 | * 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.
20 |
21 | ## Equipment
22 |
23 | 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](https://en.wikipedia.org/wiki/Model_500_telephone).
24 |
25 | 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](https://www.grandstream.com/products/gateways-and-atas/analog-telephone-adaptors/product/ht802) which lists rotary support in the specs. This currently goes for ~$50 [on Amazon](https://www.amazon.com/Grandstream-GS-HT802-Analog-Telephone-Adapter/dp/B01JH7MYKA).
26 |
27 | 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.
28 |
29 | 
30 |
31 | ## Rotary Phone Setup
32 |
33 | 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:
34 |
35 | * 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.
36 | * 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.
37 |
38 | Opening it up:
39 |
40 | 
41 |
42 | After everything else checked out, I realized the ringer had been locked, and moved it to this position to fix:
43 |
44 | 
45 |
46 | ## Asterisk setup
47 |
48 | 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:
49 |
50 | ```
51 | [rotary]
52 | type=friend
53 | nat=force_rport,comedia
54 | secret=s3cr3t
55 | host=dynamic
56 | context=rotary-context
57 | qualify=yes
58 | ```
59 |
60 | The `nat` and `host=dynamic` aspects wouldn't be necessary if the Grandstream was on the same network as Asterisk.
61 |
62 | ## Connecting the phone to Asterisk
63 |
64 | 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.
65 |
66 | Relevant settings from `fxs-1`:
67 |
68 | ```
69 | Active: Yes
70 | Primary SIP Server: [asterisk ip]
71 | NAT Traversal: Keep-Alive *
72 | SIP User ID: rotary
73 | SIP Authenticate ID: rotary
74 | SIP Registration: Yes
75 | Enable SIP OPTIONS/Notify Keep-Alive: OPTIONS *
76 | Enable Pulse Dialing: Yes
77 | Enable Hook Flash: Yes
78 | Enable High Ring Power: Yes **
79 |
80 | * - only required because of the RPi wifi bridge
81 | ** - maybe not necessary, but makes ringer more likely to work
82 | ```
83 |
84 | 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:
85 |
86 | ### Asterisk side
87 |
88 | You can run `sudo asterisk -rvvvvv` to start the asterisk repl. Then:
89 |
90 | * `sip show peers` should list `rotary/rotary` with the right Host and hopefully a Status of "OK"
91 | * `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
92 |
93 | ### Grandstream side
94 |
95 | 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.
96 |
97 | ## Building Stuff in Asterisk
98 |
99 | At this point the phone is connecting to Asterisk and the real fun can begin. :)
100 |
101 | ### Play a K-Pop song
102 |
103 | 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`:
104 |
105 | ```
106 | [rotary-context]
107 | exten => 1234,1,Answer()
108 | same => n,Playback(/path/to/file.wav)
109 | same => n,Hangup()
110 | ```
111 |
112 | 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.
113 |
114 | ### Play MTA Subway Status
115 |
116 | ### Speech Synthesis
117 |
118 | 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](https://github.com/rhasspy/piper) which sounds pretty good and notably is fast--it can run on a Raspberry Pi.
119 |
120 | 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.
121 |
122 | 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)
123 |
124 | The `speak.sh` bash script:
125 |
126 | ```sh
127 | #!/bin/bash
128 |
129 | /path/to/piper/piper \
130 | --length_scale=5 --sentence_silence=0.5 \
131 | --model /path/to/piper/voices/en_US-amy-medium.onnx \
132 | --output-raw -f - | \
133 | sox -v 0.7 -t raw -e signed -r 22050 -b 16 - -r 8000 -b 16 -c 1 -t wav -
134 | ```
135 |
136 | 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.
137 |
138 | Then we create the subroutine in `extensions.conf`:
139 |
140 | ```
141 | [speak]
142 | exten => s,1,NoOp(Text to speech)
143 | same => n,Set(filename="speech-${RAND()}${RAND()}")
144 | same => n,System(echo "${ARG1}" | /path/to/speak.sh > /tmp/${filename}.wav)
145 | same => n,Playback(/tmp/${filename})
146 | same => n,System(rm -f /tmp/${filename}.wav)
147 | same => n,Return()
148 | ```
149 |
150 | Then I can call it like this from a regular extension:
151 |
152 | ```
153 | exten => 888,1,Answer() ; Say something
154 | same => n,GoSub(speak,s,1("Hello world"))
155 | ```
156 |
157 | 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:
158 |
159 | ```
160 | [speak-get-digits]
161 | exten => s,1,NoOp(Text to speech requesting digits)
162 | same => n,Set(filename="speech-${RAND()}${RAND()}")
163 | same => n,Set(digitcount=${IF($["${ARG2}"=""]?1:${ARG2})})
164 | same => n,System(echo "${ARG1}" | /path/to/rotary/speak.sh > /tmp/${filename}.wav)
165 | same => n,Read(digits,/tmp/${filename},${digitcount})
166 | same => n,System(rm -f /tmp/${filename}.wav)
167 | same => n,Return(${digits})
168 | ```
169 |
170 | Asterisk has a long lineage and unfortunately has some serious warts, one of them being return values. In order to call this you need to do:
171 |
172 | ```
173 | same => n,GoSub(speak-get-digits,s,1("Dial a number to hear the status of a numbered train or dial 9 for a letter train.",1))
174 | same => n,Set(digit=${GOSUB_RETVAL})
175 | ```
176 |
177 | `${GOSUB_RETVAL}` is a sort of environment variable set upon completion of your subroutine. I'd recommend immediately setting it to a different name like I've done above. The `1` we pass in as a second argument tells asterisk to only wait for one digit before proceeding.
178 |
179 | #### Talking to the MTA
180 |
181 | This part came from the pieces of the aforementioned unix terminal mta status project. It's a command line script that takes the number or letter of a train, fetches from the HTTP API the MTA website runs on, and returns the status in text form.
182 |
183 | What I discovered was that the response from the MTA was pretty good for viewing on a screen, but not quite suitable for text-to-speech. I had to modify the app to do a replacement pass, to change abbreviations where appropriate: Sq to Square, St to Street (but also St George to Saint George), etc. Then a secondary pass to correct mispronunciations. (Houston -> Howston, Coney Island -> Coaney Island etc) The last part probably varies a lot from one TTS engine to the next.
184 |
185 | #### Putting it together
186 |
187 | The numbered trains (1-7) were a piece of cake. Read a digit, phone user dials it on the rotary, pass it to the mta script, generate audio. Letters were quite a bit harder:
188 |
189 | * While there are only 9 numbers, you can achieve the letters by doing a proto-T9 implementation where the user dials "2" for "A", "22" for "B", etc.
190 | * But rotary phones and the subway share an interesting mid-century oddity that they just _skip_ various letters. Unfortunately these aren't the same letters. While the subway skips "I", "O", "P", etc, rotary phones skip "Q", and "Z". These are both actual subway lines so need to be mapped to something else.
191 | * The subway has 3 shuttle lines (Grand central, Franklin, Rockaways) and the Staten Island Railway; I just made "S" its own sub-menu with 4 options.
192 |
193 | Dial "33" to start the subway status tree:
194 |
195 | ```
196 | exten => 33,1(start),Answer()
197 | same => n,GoSub(speak-get-digits,s,1("Dial a number to hear the status of a numbered train or dial 9 for a letter train.",1))
198 | same => n,Set(digit=${GOSUB_RETVAL})
199 | same => n,GotoIf($["${digit}" = "9"]?lettertrains)
200 | same => n,System(/path/to/rotary/mta.sh ${digit} > /tmp/piper.wav)
201 | same => n,Playback(/tmp/piper)
202 | same => n,Goto(start)
203 | same => n(lettertrains),NoOp(Status for letter trains)
204 | same => n,GoSub(speak-get-digits,s,1("Dial the letter for your train",4))
205 | same => n,Set(letters=${GOSUB_RETVAL})
206 | same => n,Gotoif($["${letters}" = "777"]?shuttle)
207 | same => n,System(/path/to/rotary/mta.sh letter:${letters} > /tmp/piper.wav)
208 | same => n,Playback(/tmp/piper)
209 | same => n,Goto(start)
210 | same => n(shuttle),NoOp(Status for shuttles)
211 | same => n,GoSub(speak-get-digits,s,1("Dial 1 for grand central shuttle, dial 2 for franklin avenue, dial 3 for far rockaway shuttle, dial 4 for staten island railway", 1))
212 | same => n,Set(digit=${GOSUB_RETVAL})
213 | same => n,System(/path/to/rotary/mta.sh shuttle:${digit} > /tmp/piper.wav)
214 | same => n,Playback(/tmp/piper)
215 | same => n,Goto(start)
216 | ```
217 |
218 | ### Phone Calls
219 |
220 | At this point many hours in, we're almost to the part you could have achieved 30 years ago by buying a phone and plugging it in to any house in America.
221 |
222 | In order for someone to use the phone to talk to another person with a regular phone, we need to connect to the phone network. Long ago it was apparently possible to use Google Voice, but from everything I've read it's no longer possible. I signed up for Twilio: not the cheapest, but it's reputable and developer-focused so pretty easy to work with.
223 |
224 | Buying a number will cost a few dollars a month. I let my son pick his own phone number. It has a lot of sevens in it.
225 |
226 | Twilio offers two different services that seem like they could be relevant: I started out exploring "Twilio Elastic Sip Trunking" which "enables you to make & receive telephone calls from your IP communications infrastructure around the globe over a public or private connection" and sounds like exactly what I need. This is probably the "correct" way to bridge Asterisk into Twilio, especially if you're doing something real. But this approach assumes your Asterisk server has a public IP and mine sits behind my home router. Using this approach I was able to have Asterisk dial out, but Twilio could not make calls into Asterisk. NAT strikes again!
227 |
228 | Instead, I went the route of creating a Programmable SIP Domain in Twilio and having Asterisk just act like another SIP client. This let Asterisk register with Twilio and punch through NAT using the usual OPTIONS keepalive trick.
229 |
230 | The steps are:
231 |
232 | 1. Create a [SIP Domain](https://console.twilio.com/us1/develop/voice/manage/sip-domains)
233 | 2. Create a Credential List. This is how Asterisk will authenticate with Twilio. Set up Voice Authentication with the Credential List.
234 | 3. Enable SIP Registration.
235 |
236 | On the Asterisk side, we'll need something like this in our `/etc/asterisk/sip.conf`:
237 |
238 | ```
239 | [twilio-trunk](!)
240 | type=peer
241 | context=from-twilio ; Which dialplan to use for incoming calls
242 | dtmfmode=rfc4733
243 | canreinivite=no
244 | insecure=port,invite
245 |
246 | [twilio](twilio-trunk)
247 | host=replace-with-your-sip-name.sip.us1.twilio.com
248 | defaultuser=michael ; username
249 | remotesecret=totallysecure ; password
250 | defaultexpiry=605
251 | minexpiry=605
252 | qualify=yes
253 | ```
254 |
255 | Replace the host with the host from the SIP Domain you created in Twilio. And replace `defaultuser` and `remotesecret` values with the username and password from the Credentials List you created.
256 |
257 | * `qualify=yes` will cause Asterisk to keep the NAT hole alive by periodically sending OPTIONS requests.
258 | * `defaultexpiry` and `minexpiry` keep us from sending so many pings that Twilio rate-limits us.
259 | * `context=from-twilio` means that incoming calls will wind up in the `from-twilio` context in `extensions.conf`, where you can route them to your rotary phone:
260 |
261 | After you reload Asterisk, you can run `sudo asterisk -rvvvvv` and `show sip peers` and see both your rotary phone as well as Twilio:
262 |
263 | ```
264 | CLI> sip show peers
265 | Name/username Host Dyn Forcerport Comedia ACL Port Status Description
266 | rotary/rotary 192.168.1.33 D Yes Yes 5060 OK (22 ms)
267 | twilio/michael 54.172.60.0 Yes No 5060 OK (18 ms)
268 | ```
269 |
270 | #### Dialing out
271 |
272 | With the connection established, we need to tell Asterisk how to route a call to Twilio, and need to tell Twilio what to do when it gets it.
273 |
274 | Asterisk config, in `extensions.conf`:
275 |
276 | ```
277 | exten => 81,1,Answer() ; Call Dad
278 | same => n,GoSub(speak,s,1("Calling dad\!\!"))
279 | same => n,Set(CALLERID(all)="Rotary"<19787777777>)
280 | same => n,Dial(SIP/twilio/+12565121024)
281 | ```
282 |
283 | In this case you'd replace `19787777777` with the Twilio phone number you purchased, and `12565121024` with the number you're calling. I'm not entirely sure the caller ID part is necessary, but it seems like it.
284 |
285 | Back in Twilio, we want our SIP domain to receive a call and pass it right on through via our purchased phone number. To do this we can create a [TwiML bin](https://console.twilio.com/us1/develop/twiml-bins/twiml-bins):
286 |
287 | ```
288 |
289 |
290 | {{#e164}}{{To}}{{/e164}}
291 |
292 | ```
293 |
294 | Give it a name like "OutboundCall", then go back to the SIP Domain configuration:
295 | * Set Call Control Configuration to configure with "Webhooks, TwiML Bins, Functions, Studio, Proxy"
296 | * A call comes in: set to "TwiML Bin" then select your bin name
297 | * The rest of the fields don't matter
298 |
299 | Make sure SIP routing is Active.
300 |
301 | Now try dialing "81" from your rotary phone. It should route to your recipient.
302 |
303 | I went back and forth a bit on setting up emergency service. (911, in the US) If you do this, you'll absolutely want to make sure your home address in Twilio is accurate. I haven't set up emergency dialing yet because while I can impress on my son the serious nature of calling 911, his friends might treat it more like a toy. Still, something worth considering.
304 |
305 | #### Receiving calls
306 |
307 | With our outbound setup, Asterisk could technically dial _any_ number but we limit which numbers can actually be dialed by only explicitly dialing them in `extensions.conf`. For receiving calls, we want to make sure we also limit it to specific callers. We could easily achieve "pass any call through" with the same TwiML Bin approach, but "limit to phone numbers x,y,z" is a bit too much for TwiML so I opted for a Twilio Function.
308 |
309 | In Twilio, [create a Service](https://console.twilio.com/us1/develop/functions/services) to make a new function. I named mine `incoming-call`, with this logic:
310 |
311 | ```javascript
312 | const allowed = ['+12565121024', '+13435555555', ...];
313 |
314 | exports.handler = function(context, event, callback) {
315 | console.log("Receiving call", event.From);
316 | let twiml = new Twilio.twiml.VoiceResponse();
317 |
318 | if(allowed.includes(event.From)) { // Ensure the number is in E.164 format
319 | console.log("Matched, dialing...");
320 | const dial = twiml.dial({answerOnBridge: true}).sip("rotary@replace-with-your-sip-name.sip.twilio.com");
321 | } else {
322 | console.log("Call was rejected");
323 | twiml.reject(); // Rejects the call if the number does not match
324 | }
325 |
326 | callback(null, twiml);
327 | };
328 | ```
329 |
330 | Make sure you save and click "Deploy all" to actually deploy it.
331 |
332 | Then go to Phone Numbers -> Manage -> Active Phone Numbers and select the number you purchased to configure it. Choose "Webhooks, TwiML Bin, Function, Studio Flow, Proxy Service", then choose "Function" and the one you named `incoming-call` and choose the Environment and Function Path. (there should be only one option for each)
333 |
334 | At this point, you can go back to the Function editor, enable Live Logs, and actually try to call your Twilio number and see it route the call. It'll send it on to Asterisk, who will probably immediately dump it since you haven't configured anywhere for it to go. We can do that in `extensions.conf`:
335 |
336 | ```
337 | [from-twilio]
338 | exten => s,1,NoOp(Incoming Call)
339 | same => n,Dial(SIP/rotary)
340 | same => n,Hangup()
341 | ```
342 |
343 | This is where you could put other fancy logic, for instance if you wanted to let other people call you and hear MTA announcements or something.
344 |
345 | At this point you should be able to dial your Twilio number from your cell phone and, with a great deal of luck, your rotary phone will ring.
346 |
347 | ### Tell a Joke
348 |
349 | This one needs the most future work. Conceptually it's mostly a simpler version of the MTA script: put list of jokes in an array, then choose one. This works okay, but somehow there is _absolutely no way_ to get piper to pause, which destroys the comedic timing.
350 |
351 | While I need to figure out how to get piper to just wait a bit, I also want to add support for knock-knock jokes, where it waits for the caller to actually say "who's there?". I don't think this needs to go nearly as far as actual speech-to-text, and could instead detect any kind of sound and probably be good enough.
352 |
353 | You also don't want a random joke: unless you have a really long list, you'll eventually end up with it telling the same joke twice in a row. This may be funny the first time, but gets old fast. Instead we need some way to tell a new joke each time.
354 |
355 | ## Conclusion
356 |
357 | The physical aspects of the project held my son's attention the best, especially taking the phone cover off. (make sure it's unplugged!) Debugging SIP connections was by far the most tedious part of the project and I did most of that at night. But building out the phone menu was also a fun activity where I let him dictate what the numbers did and we drew out how it would work.
358 |
359 | By far the best part of the project is that he'll randomly call up his grandparents to talk. As an adult I talk to loved ones on the phone, but calls tend to be scheduled and coordinated in advance via text. An unexpected call triggers annoyance or concern. Four year olds have none of that, and are happy just to chat.
360 |
--------------------------------------------------------------------------------
/asterisk-config/extensions.conf:
--------------------------------------------------------------------------------
1 | ; extensions.conf - the Asterisk dial plan
2 | ;
3 | ; Static extension configuration file, used by
4 | ; the pbx_config module. This is where you configure all your
5 | ; inbound and outbound calls in Asterisk.
6 | ;
7 | ; This configuration file is reloaded
8 | ; - With the "dialplan reload" command in the CLI
9 | ; - With the "reload" command (that reloads everything) in the CLI
10 |
11 | ;
12 | ; The "General" category is for certain variables.
13 | ;
14 | [general]
15 | ;
16 | ; If static is set to no, or omitted, then the pbx_config will rewrite
17 | ; this file when extensions are modified. Remember that all comments
18 | ; made in the file will be lost when that happens.
19 | ;
20 | ; XXX Not yet implemented XXX
21 | ;
22 | static=yes
23 | ;
24 | ; if static=yes and writeprotect=no, you can save dialplan by
25 | ; CLI command "dialplan save" too
26 | ;
27 | writeprotect=no
28 | ;
29 | ; If autofallthrough is set, then if an extension runs out of
30 | ; things to do, it will terminate the call with BUSY, CONGESTION
31 | ; or HANGUP depending on Asterisk's best guess. This is the default.
32 | ;
33 | ; If autofallthrough is not set, then if an extension runs out of
34 | ; things to do, Asterisk will wait for a new extension to be dialed
35 | ; (this is the original behavior of Asterisk 1.0 and earlier).
36 | ;
37 | ;autofallthrough=no
38 | ;
39 | ;
40 | ;
41 | ; If extenpatternmatchnew is set (true, yes, etc), then a new algorithm that uses
42 | ; a Trie to find the best matching pattern is used. In dialplans
43 | ; with more than about 20-40 extensions in a single context, this
44 | ; new algorithm can provide a noticeable speedup.
45 | ; With 50 extensions, the speedup is 1.32x
46 | ; with 88 extensions, the speedup is 2.23x
47 | ; with 138 extensions, the speedup is 3.44x
48 | ; with 238 extensions, the speedup is 5.8x
49 | ; with 438 extensions, the speedup is 10.4x
50 | ; With 1000 extensions, the speedup is ~25x
51 | ; with 10,000 extensions, the speedup is 374x
52 | ; Basically, the new algorithm provides a flat response
53 | ; time, no matter the number of extensions.
54 | ;
55 | ; By default, the old pattern matcher is used.
56 | ;
57 | ; ****This is a new feature! *********************
58 | ; The new pattern matcher is for the brave, the bold, and
59 | ; the desperate. If you have large dialplans (more than about 50 extensions
60 | ; in a context), and/or high call volume, you might consider setting
61 | ; this value to "yes" !!
62 | ; Please, if you try this out, and are forced to return to the
63 | ; old pattern matcher, please report your reasons in a bug report
64 | ; on https://issues.asterisk.org. We have made good progress in providing
65 | ; something compatible with the old matcher; help us finish the job!
66 | ;
67 | ; This value can be switched at runtime using the cli command "dialplan set extenpatternmatchnew true"
68 | ; or "dialplan set extenpatternmatchnew false", so you can experiment to your hearts content.
69 | ;
70 | ;extenpatternmatchnew=no
71 | ;
72 | ; If clearglobalvars is set, global variables will be cleared
73 | ; and reparsed on a dialplan reload, or Asterisk reload.
74 | ;
75 | ; If clearglobalvars is not set, then global variables will persist
76 | ; through reloads, and even if deleted from the extensions.conf or
77 | ; one of its included files, will remain set to the previous value.
78 | ;
79 | ; NOTE: A complication sets in, if you put your global variables into
80 | ; the AEL file, instead of the extensions.conf file. With clearglobalvars
81 | ; set, a "reload" will often leave the globals vars cleared, because it
82 | ; is not unusual to have extensions.conf (which will have no globals)
83 | ; load after the extensions.ael file (where the global vars are stored).
84 | ; So, with "reload" in this particular situation, first the AEL file will
85 | ; clear and then set all the global vars, then, later, when the extensions.conf
86 | ; file is loaded, the global vars are all cleared, and then not set, because
87 | ; they are not stored in the extensions.conf file.
88 | ;
89 | clearglobalvars=no
90 | ;
91 | ; User context is where entries from users.conf are registered. The
92 | ; default value is 'default'
93 | ;
94 | ;userscontext=default
95 | ;
96 | ; You can include other config files, use the #include command
97 | ; (without the ';'). Note that this is different from the "include" command
98 | ; that includes contexts within other contexts. The #include command works
99 | ; in all asterisk configuration files.
100 | ;#include "filename.conf"
101 | ;#include
102 | ;#include filename.conf
103 | ;
104 | ; You can execute a program or script that produces config files, and they
105 | ; will be inserted where you insert the #exec command. The #exec command
106 | ; works on all asterisk configuration files. However, you will need to
107 | ; activate them within asterisk.conf with the "execincludes" option. They
108 | ; are otherwise considered a security risk.
109 | ;#exec /opt/bin/build-extra-contexts.sh
110 | ;#exec /opt/bin/build-extra-contexts.sh --foo="bar"
111 | ;#exec
112 | ;#exec "/opt/bin/build-extra-contexts.sh --foo=\"bar\""
113 | ;
114 |
115 | ; The "Globals" category contains global variables that can be referenced
116 | ; in the dialplan with the GLOBAL dialplan function:
117 | ; ${GLOBAL(VARIABLE)}
118 | ; ${${GLOBAL(VARIABLE)}} or ${text${GLOBAL(VARIABLE)}} or any hybrid
119 | ; Unix/Linux environmental variables can be reached with the ENV dialplan
120 | ; function: ${ENV(VARIABLE)}
121 | ;
122 | [globals]
123 | CONSOLE=Console/dsp ; Console interface for demo
124 | ;CONSOLE=DAHDI/1
125 | ;CONSOLE=Phone/phone0
126 | IAXINFO=guest ; IAXtel username/password
127 | ;IAXINFO=myuser:mypass
128 | TRUNK=DAHDI/G2 ; Trunk interface
129 | ;
130 | ; Note the 'G2' in the TRUNK variable above. It specifies which group (defined
131 | ; in chan_dahdi.conf) to dial, i.e. group 2, and how to choose a channel to use
132 | ; in the specified group. The four possible options are:
133 | ;
134 | ; g: select the lowest-numbered non-busy DAHDI channel
135 | ; (aka. ascending sequential hunt group).
136 | ; G: select the highest-numbered non-busy DAHDI channel
137 | ; (aka. descending sequential hunt group).
138 | ; r: use a round-robin search, starting at the next highest channel than last
139 | ; time (aka. ascending rotary hunt group).
140 | ; R: use a round-robin search, starting at the next lowest channel than last
141 | ; time (aka. descending rotary hunt group).
142 | ;
143 | TRUNKMSD=1 ; MSD digits to strip (usually 1 or 0)
144 | ;TRUNK=IAX2/user:pass@provider
145 |
146 | ;FREENUMDOMAIN=mydomain.com ; domain to send on outbound
147 | ; freenum calls (uses outbound-freenum
148 | ; context)
149 |
150 | ;
151 | ; WARNING WARNING WARNING WARNING
152 | ; If you load any other extension configuration engine, such as pbx_ael.so,
153 | ; your global variables may be overridden by that file. Please take care to
154 | ; use only one location to set global variables, and you will likely save
155 | ; yourself a ton of grief.
156 | ; WARNING WARNING WARNING WARNING
157 | ;
158 | ; Any category other than "General" and "Globals" represent
159 | ; extension contexts, which are collections of extensions.
160 | ;
161 | ; Extension names may be numbers, letters, or combinations
162 | ; thereof. If an extension name is prefixed by a '_'
163 | ; character, it is interpreted as a pattern rather than a
164 | ; literal. In patterns, some characters have special meanings:
165 | ;
166 | ; X - any digit from 0-9
167 | ; Z - any digit from 1-9
168 | ; N - any digit from 2-9
169 | ; [1235-9] - any digit in the brackets (in this example, 1,2,3,5,6,7,8,9)
170 | ; . - wildcard, matches anything remaining (e.g. _9011. matches
171 | ; anything starting with 9011 excluding 9011 itself)
172 | ; ! - wildcard, causes the matching process to complete as soon as
173 | ; it can unambiguously determine that no other matches are possible
174 | ;
175 | ; For example, the extension _NXXXXXX would match normal 7 digit dialings,
176 | ; while _1NXXNXXXXXX would represent an area code plus phone number
177 | ; preceded by a one.
178 | ;
179 | ; Each step of an extension is ordered by priority, which must always start
180 | ; with 1 to be considered a valid extension. The priority "next" or "n" means
181 | ; the previous priority plus one, regardless of whether the previous priority
182 | ; was associated with the current extension or not. The priority "same" or "s"
183 | ; means the same as the previously specified priority, again regardless of
184 | ; whether the previous entry was for the same extension. Priorities may be
185 | ; immediately followed by a plus sign and another integer to add that amount
186 | ; (most useful with 's' or 'n'). Priorities may then also have an alias, or
187 | ; label, in parentheses after their name which can be used in goto situations.
188 | ;
189 | ; Contexts contain several lines, one for each step of each extension. One may
190 | ; include another context in the current one as well, optionally with a date
191 | ; and time. Included contexts are included in the order they are listed.
192 | ; Switches may also be included within a context. The order of matching within
193 | ; a context is always exact extensions, pattern match extensions, includes, and
194 | ; switches. Includes are always processed depth-first. So for example, if you
195 | ; would like a switch "A" to match before context "B", simply put switch "A" in
196 | ; an included context "C", where "C" is included in your original context
197 | ; before "B".
198 | ;
199 | ;[context]
200 | ;
201 | ;autohints = yes
202 | ; If enabled for a context, a device state hint will be automatically created in
203 | ; the context with the name of the device and updated with device state changes.
204 | ;
205 | ;exten => someexten,{priority|label{+|-}offset}[(alias)],application(arg1,arg2,...)
206 | ;
207 | ; Timing list for includes is
208 | ;
209 | ;