“Instant” fall through on the dial plan with a pstn trunk is a common question I’ve seen over the years. PSTN (where I live) has no answer detection, so you have to be a little creative if you cannot use voip channels instead.
I’m not sure where there would be some guide. 10 years ago it took me forever to figure out how to use the AMI via AsteriskDotNet C#, which basically ran the same commands as you can do with CLI. AsteriskDotNet is still around in various branches and updated as well. I had conversations with the original programmer and it’s fairly complex. I only used a small portion of it to run my calls.
You can add the module configEdit which would let you edit dial plan from within FreePbx. I recommend you simply use Winscp and create/edit your custom dial plan. Make sure to reload your dialplan after you make additions or changes.
Running and testing stuff on a production pbx is usually not a good idea unless you are highly skilled. A spare physical box is cheap, virtual machines are even cheaper. No good reason not to have a spare test machine.
In an older FreePbx system, running an older version of Asterisk, I simply wrote some custom dialplan in the extensions_custom.conf file which will (should?) not be overwritten when you reload FreePbx settings.
This dialplan detects whether or not I am using a zap or not zap (sip/iax) trunk as the answer detection behaviors are different.
I play a custom message while waiting to detect a period of silence. This gives instructions for the callee to wait or press 1 to begin, but most importantly prevents the dreaded silence that everyone recognizes as a telemarketer robo call and hangs up prematurely.
You’ll have to debug the example below in the CLI, but it should get you started.
Dial plan and CLI call file syntax, grammar, and functions have changed over time.
Pstn trunks at the time used ZAP trunks. Clearly this channel type will need to be modified below for the dahdi channel type.
Mixmonitor was great for monitoring call behavior as you can hear what the entire call sounds like. Great for debugging not in service numbers, people hanging up early, loud tv’s in background, etc.
Custom PHP code would be more efficient and elegant, but for my simple needs I could write it all in dialplan instead of learning more coding languages.
Good luck!
For example, set your CLI callfile destination context parameter to specific context called myCustomDialPlan which would be your custom dialplan code in extensions_custom.conf:
[myCustomDialPlan]
exten => s,1,Answer
exten => s,2,Set(CALLRESULT=‘Earlyhangup’)
exten => s,3,Wait(1); local channel hangs up in macrodialout trunk, so need a pause here for zap channel to start
exten => s,4,Set(ChannelType=${CUT(CHANNEL,/,1)}) ; get just the trunk type here
exten => s,5,GotoIf($[${ChannelType} = Zap]?12:6) ; case sensitive
;not zap trunk
;exten => s,6,NoOp(Not zap trunk!)
exten => s,6,Set(TIMEOUT(absolute)=140) ;rarely background detect hangs with voip I suspect packet loss
;exten => s,7,MixMonitor(${DIAL}${ChannelType}.wav|V(-2))
exten => s,7,NoOp(No monitor)
exten => s,8,NoOp(Non Zap CHANNEL type: ${ChannelType})
exten => s,9,Set(TIMEOUT(digit)=1) ; go right away to message if button pushed
exten => s,10,BackgroundDetect(custom/message2,3250,10,145000) ; this deals with loud tv issue. goes to talk
exten => s,11,Goto(91,1)
;exten => s,10,WaitForSilence(2500|1|75) or we can use this but still with loud tv issue causes plan to fail
;exten => s,11,GotoIf($[${WAITSTATUS} = SILENCE]?talk,1:91,1) if timesout we go to failed call area
;zap trunk
exten => s,12,Set(TIMEOUT(absolute)=80) ; avoid infinite ring situation for zap calls but long enough for answering machines
;exten => s,13,MixMonitor(${DIAL}${ChannelType}.wav|V(-2) v(2)) ; callee is louder than caller
exten => s,13,NoOp(No monitor)
exten => s,14,Set(TIMEOUT(digit)=1) ; go right away to message if button pushed
exten => s,15,Set(CALLRESULT=‘Failed’) ; a catch all for busy, congestion, not in service, and any hangups during loop coming up below
exten => s,16,NoOp(no)
exten => s,17,BackgroundDetect(custom/tohear_dr_appt_conf_press1_now_loop_2min,5500,1,145000) ; has to be about 5 or 6 secs silence detection threshold in case there is a pause with the answering machine in addition to 4 sec silence between USA rings
exten => s,18,Goto(91,1)
exten => talk,1,Set(TIMEOUT(absolute)=0)
exten => talk,2,Set(CALLRESULT=‘Earlyhangup’) ; it was failed for zap, go back to earlyhangup for everyone
exten => talk,3,Playback(custom/${provider})
;exten => talk,4,NoOp(no swift name)
;exten => talk,4,Swift(${name}) ;this used swift to say persons name. had to record it first then play back to work well
exten => talk,4,AGI(sayname.php|${name})
exten => talk,5,Playback(${TMPWAVE})
;exten => talk,5,NoOp(no playback tmpwave)
exten => talk,6,AGI(removewave.php|${TMPWAVE})
;exten => talk,6,NoOp(no remove tmpwave)
exten => talk,7,Playback(digits/${dayoftheweek})
exten => talk,8,Playback(digits/${month})
exten => talk,9,Playback(digits/${date1})
…
…
exten => t,1,Goto(talk,1)
exten => i,1,Goto(talk,1)
exten => o,1,Goto(talk,1)
…
…
exten => failed,1,NoOp(Failed extension reached for REASON ${REASON})
exten => failed,2,Set(CALLRESULT=‘FailedConnect’)
exten => failed,3,Set(CALLRESULT=${IF($[${REASON} = 0]?‘Failed’:${CALLRESULT})})
exten => failed,4,Set(CALLRESULT=${IF($[${REASON} = 3]?‘TIMEOUT’:${CALLRESULT})})
exten => failed,5,Set(CALLRESULT=${IF($[${REASON} = 5]?‘BUSY’:${CALLRESULT})})
exten => failed,6,Set(CALLRESULT=${IF($[${REASON} = 8]?‘CONGESTION’:${CALLRESULT})})
exten => failed,7,NoOp(CALLRESULT from failed ext: ${CALLRESULT})
exten => failed,8,Hangup
;exten => h,1,NoOp(Hanging up now… CALLRESULT ${CALLRESULT})
exten => h,1,NoOp(Hanging up now… CALLRESULT ${CALLRESULT} REASON ${REASON} DIALSTATUS ${DIALSTATUS} HANGUPCAUSE ${HANGUPCAUSE})
exten => h,2,Set(temp=foo) ; just a helper variable to test null condition which means call not connect so blank result
exten => h,3,Set(responsefound=${IF($[${CALLRESULT}${temp} = ${temp}]?0:1)}) ; if no callresult is 0, if call result is 1
exten => h,4,GotoIf($[${responsefound} = 1]?6:5) ; if call result is 1 report and hangup, if no call result, set to early hangup first
exten => h,5,Set(CALLRESULT=‘Earlyhangup’) ; if no call result, is an early hangup
exten => h,6,Hangup(HUNGUP ${CALLRESULT} ${id}) ; leave busy result alone in app
exten => 91,1,NoOp(Timed out! Set call status to fail, ${ChannelType})
exten => 91,2,Set(CALLRESULT=‘TIMEOUT’)
exten => 91,3,Hangup
exten => T,1,NoOp(Timed out! Set call status to fail, ${ChannelType})
exten => T,2,Set(CALLRESULT=‘TIMEOUT’)
exten => T,3,Hangup
;Here the callee can press any digit except zero to escape from the wait message
;we intend for them to press just digit 1
exten => 1,1,NoOp(User pressed extension 1, going to broadcast)
exten => 1,2,Goto(talk,1)
exten => 2,1,NoOp(User pressed extension 2, going to broadcast)
exten => 2,2,Goto(talk,1)
exten => 3,1,NoOp(User pressed extension 3, going to broadcast)
exten => 3,2,Goto(talk,1)
exten => 4,1,NoOp(User pressed extension 4, going to broadcast)
exten => 4,2,Goto(talk,1)
exten => 5,1,NoOp(User pressed extension 5, going to broadcast)
exten => 5,2,Goto(talk,1)
exten => 6,1,NoOp(User pressed extension 6, going to broadcast)
exten => 6,2,Goto(talk,1)
exten => 7,1,NoOp(User pressed extension 7, going to broadcast)
exten => 7,2,Goto(talk,1)
exten => 8,1,NoOp(User pressed extension 8, going to broadcast)
exten => 8,2,Goto(talk,1)
exten => 9,1,NoOp(User pressed extension 9, going to broadcast)
exten => 9,2,Goto(talk,1)