├── .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 | ![The phone in use](./images/header.jpg) 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 | ![Diagram of equipment setup](./images/diagram.png) 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 | ![Inside the phone](./images/inside.jpg) 41 | 42 | After everything else checked out, I realized the ringer had been locked, and moved it to this position to fix: 43 | 44 | ![Mute lever](./images/rotary_mute.jpg) 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 | ;