A few weeks ago @kenn10 sent me a PM with a link to this post showing Asterisk dialplan for an in-call feature code to flip an active call to any of the users other devices. So any local PBX user has a number of separate devices/clients registered. While on an active call, the user enters DTMF to initiate the process, all of their other devices start ringing. They then answer any of their devices and the channel they were talking to previously, flips to the new device. #FridayFun this week involves replicating this Asterisk dialplan in a way that’s compatible with fpbx.
To start with, we need to know how to create a custom in-call feature code. By an astonishing coincidence, I’ve posted abut this subject previously:
How to Create a Custom in-call Feature Code
Using that config, we can identify the two channels in the call, the local extension that triggered the feature code and the channel to which they are bridged.
The plan today is to setup a call from Asterisk to all of the user’s local registered devices and clients. This is not difficult to do, as the fpbx internals keep track of all that, we merely need to originate a call to the user’s primary extension number using the correct asterisk context. But we’ll need a way to determine the user’s primary extension number from the Asterisk channel name, and luckily I posted about that previously too:
Determine fpbx AMPUSER from Asterisk channel name
Building on the previous work, the final step in the process is to write dialplan to setup the transfer. In Asterisk dialplan (and with AMI - but that’s not today’s focus), when you want to setup a call between two endpoints that are independent of the current call flow, you do so using the ORIGINATE
application. With ORIGINATE
, you specify two destinations. The first specified channel rings for a certain period of time, and once answered, a call is placed to the second specified channel, then the two are bridged. This is the mechanism you would be familiar with for most (all?) of the various click-to-dial tools for Asterisk.
In fpbx, it is pretty much always preferable to use the Asterisk local channel for setting up the first channel, you don’t need to know anything about the client SIP protocols, registration status or anything else. The only thing we need to know is what Asterisk context to use to setup the call. FPBX has hundreds of Asterisk contexts, most of which are used internally and stay hidden under the hood. Generally the two main contexts that anyone needs to know when setting up calls in fpbx are:
from-internal - this is the primary fpbx context for internal devices. It allows access to all local feature codes, local extensions, ring groups and external trunk calls via the outbound routes. If you want the call to behave EXACTLY as if it’s been dialed from a local extension, this is the context to use.
from-pstn - this is the primary context for authorized external calls coming into the system. If you want a call to be treated as if its coming from one of your trunks and direct it to the inbound routes on the system, this is the context for that. Know that there are many similarly named aliases for this context such as from-trunk, from-analog, from-digital and others, they all do pretty much the same thing.
For today’s purposes, we’re setting up calls to a local extension, we’ll want to use from-internal. One thing that you quickly learn when doing this type of thing is that you need to have very good control of the call flow. You don’t want to initiate a call to a local extension only for the call to go to voicemail. As soon as the vm app answers the channel, Asterisk will dutifully bridge the second leg of the call with unwanted results. Luckily fpbx has a somewhat recent context for handling this situation, originate-skipvm. From the CLI:
tangopbx3*CLI> dialplan show originate-skipvm
[ Context 'originate-skipvm' created by 'pbx_config' ]
'_.X' => 1. Gosub(macro-blkvm-set,s,1()) [extensions_additional.conf:3359]
2. Goto(from-internal,${EXTEN},1) [extensions_additional.conf:3360]
This core context calls a subroutine that blocks the call from going to the local voicemail, and exists for originating calls to local extensions.
Enough preamble, here’s where we’re at now. If you need further explanations, go back and reference the links above
New feature code in features_applicationmap_custom.conf
move_call => *37,self,Gosub(move-active-call,s,1)
Update global var in globals_custom.conf
DYNAMIC_FEATURES = ${DYNAMIC_FEATURES}#move_call
New dialplan in extensions_custom.conf
[move-active-call]
exten => s,1,Noop(Entering user defined context move-active-call in extensions_custom.conf)
; exten => s,n,DumpChan ; uncomment for debug
exten => s,n,Gosub(get-AMPUSER-from-asterisk-channel,s,1(${DYNAMIC_WHO_ACTIVATED}))
exten => s,n,ExecIf($["${GOSUB_RETVAL}"!=""]?Set(MOVING_USER=${GOSUB_RETVAL}) ; will set if local channel initiates
exten => s,n,ExecIf($["${PICKUPMARK}"!="" & "${GOSUB_RETVAL}"=""]?Set(MOVING_USER=${PICKUPMARK}) ; will set if external channel was created via fmfm
exten => s,n,GotoIf($["${MOVING_USER}"=""]?end) ; if all else fails use the value we already have
exten => s,n,Noop(Trying to move ${BRIDGEPEER} to another device for extension ${MOVING_USER})
; Originate call to all local devices, they will ring for 15s
exten => s,n(call-setup),Originate(Local/${MOVING_USER}@originate-skipvm,exten,move-active-call,take_call,1,15,ac(${CALLERID})v(bp=${BRIDGEPEER}^__MOVING_USER=${MOVING_USER}))
exten => s,n(end),Return
exten => take_call,1,Noop(Bridging the call)
exten => take_call,n,Bridge(${bp},x)
[get-AMPUSER-from-asterisk-channel]
; Asterisk sub to isolate a FreePBX AMPUSER from a dialing channel
; pass channel name to sub as ARG1 required
exten => s,1,Noop(Attempting to match FreePBX user with Channel ${ARG1})
exten => s,n,ExecIf($["${ARG1}"=""]?Return())
; strip unique identifier and hypen from channel name
exten => s,n,Set(DIALING_CHANNEL=${CUT(ARG1,-,1)})
exten => s,n,Set(DEVICES=${DB_KEYS(DEVICE)}) ; commma delimited list of dialable devices on system
; step thru the database dial entry for each device looking for match to dialing channel
exten => s,n,Set(foo=)
exten => s,n,While($["${SET(DEV=${POP(DEVICES)})}" != ""])
exten => s,n,noop(dev: ${DEV})
exten => s,n,ExecIf($["${DB(DEVICE/${DEV}/dial)}"="${DIALING_CHANNEL}"]?Set(foo=${DB(DEVICE/${DEV}/default_user)}))
exten => s,n,ExecIF($["${foo}"!=""]?ExitWhile)
exten => s,n,EndWhile()
exten => s,n,Return(${foo})
And that’s it. Make the above edits, and do and apply config or core reload and you’re done. Now any user with multiple registered devices can flip calls between them using *37
For completeness, check out this previous #FridayFun which does much the same thing, but instead of pushing an active call like this does, it pulls an active call by dialing the feature code from an idle device. Note that both feature codes have the same dial string, so by knowing *37 you can both push and pull an active call between any devices that a user has.
Quickly moving a call between devices for the same user