In-call feature code to send a call your other extension

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

3 Likes

Great stuff. Definitely something I’m trying out soon. Like Oliver asking for more, would be nice to be able to configure the call flip feature code from the GUI, maybe even bake the entire thing into TangoPBX eventually. “Greedy” ask, I know. Just something to consider as a nice-to-have

1 Like

I should have included that the above is not something I’ve tested exhaustively, and will probably be a work in progress. I expect it will work fine if using FMFM, but that’s something that needs to be well tested. Likewise what happens if extension is set to DND? Or if call forwarding is enabled? Or CF enabled at the phone instead of the PBX. Lots of what-ifs when testing.

1 Like

So how will this handle a cell phone that you want to switch the call to? Cisco is able to associate the cell phone number with the regular extension and ring it at the same time as the extension. Will FM/FM work the same way? I’ll have to see what happens. I’ll play this weekend.

If your mobile number is configured as FMFM then I would expect it to work.

You would need to enable FMFM in order for this to work. Otherwise, the check will skip over followme and just dial the extension directly. So to transfer out to your cell phone via FMFM you would need to:

  • Enable FMFM
  • Let the Pre Ring go through
  • Ring the list based on FM ring timer
  • If you use ringall-v2 so it pre rings the extension, then continues to ring the extension and the mobile number because both are in the FM list…you cannot use the -prim versions since if the primary extension is in use, the list is ignored.

As for the sim ring, that would require some extra work as it would need to add an additional dial string that hits a local channel.

Yeah. It doesn’t work right. My extension has FM/FM activated with ringall-v2 and has my extension and my cell phone in the group with a 1 second delay upon ring.

I had an active call on my extension and activated the *37. The system launched a call to my extension and the cell phone. My extension rings and the system rings the cell phone and immediately drops the call to the cell phone like it is answered someplace else. I wanted to attach a log but I can’t and its too long to insert here as a code.

Pastebin is your friend for this. Create a pastebin of the log and give us the link.

Do you have call confirmation enabled on the followme? This may have to do with the fact that Followme will use Local channels to make the call. So a log would be helpful to see what is happening.

I’ve tried both with and without call confirmation. Now if Pastebin will ever send me the verification code to activate my account, I’ll post the log.

Here is a drop box link to the log file. Note that I use 2002229999 as a bogus caller-id in this file to anonymize my real phone number. Also the cell phone number shows as “MyCellNo” in the log.

This line where the system drops the call to the cell phone is concerning:

[2025-06-07 07:50:02] VERBOSE[947533][C-0000003f] app_dial.c: Nobody picked up in 2000 ms

If the call gets to the cell phone and I answer within 2 seconds, it works. Any longer and the call is dropped.

https://www.dropbox.com/scl/fi/g3kre1bum4q2rfw4the8k/fmfm_extract.txt?rlkey=6fefqcapldnjig5lk6txus2aq&st=cobe6kjq&dl=0

Looking at the call trace shortly, but I can confirm that using FMFM to an external number is working okay for me, both with confirm calls enabled and disabled. These are the FMFM settings that are working for me.

My FM/FM is the same. I don’t know where the 2000 ms timeout on the call to the cell phone is coming from.

Remember I’m on IncrediblePBX, not Tango.

The 200ms is the timeout for dialing 211, do you have FMFM setup to ring the primary extension for 2s before going to the FMFM group?

I found the likely issue Ken. The originate line in the code above currently looks like this

exten => s,n,Originate(Local/${GOSUB_RETVAL}@originate-skipvm,exten,move-active-call,take_call,1,8,ac(${CALLERID})v(bp=${BRIDGEPEER}))

That digit “8” is the timeout for the second leg of the call, which is only 8s. The originate gives up if there is no answer after 8s. I’m editing post 1 to increase that to 15s and adding a note explaining it.

Yep. That did it. Since I have confirm call turned on, I might make it more like 20 seconds. I hate that it rings and then I have to hit the icon for the dial pad and then hit one. However, I need the confirm on because of spotty cell coverage in my area. Obviously if I’m at my hard phone, switching to cell for this app isn’t an issue.

Now that this works, how can we keep the pbx in the loop so the cell user can dial *37 and return the call to the extension when you walk back to your hard phone?

There’s a couple issues with that. First, the call would be on a channel that does not have your extension in the name. It would be a channel over the trunk. So associating an AMPUSER to the channel name won’t happen in the current method. It would require a lot more work and probably storing that channel in the AstDB somewhere.

Second, you would have to add this dynamic feature to the outbound trunk channel when the call is made. If you’re doing this globally (i.e. with every call) then you are giving all the callee’s being called from that trunk the ability to enter that code.

It can be done, it would take a bit of work and require the default call logic in FreePBX to be modified.

Yeah, I know. This is where Cisco’s shared call appearances come in handy. The hard phone shows a busy call appearance of your extension while the cell phone is on a swapped call. You press the call appearance to bridge onto the call with the cell phone and then hang up the cell phone.

Right, because they have it setup to deal with that scenario. This could be achieved in FreePBX but it would require some big changes to support it.