Determine fpbx AMPUSER from Asterisk channel name

This week’s #FridayFun is step 1 toward a larger goal, something to build on for the Fridays to come. @kenn10 sent me a message this week with just enough information to tease my problem solving compulsion into high gear. But before I could act on his idea, there’s at least one technical problem to overcome.

This week’s problem: Create fpbx dialplan to reliably determine the FreePBX AMPUSER from an Asterisk channel name.

What is the FreePBX AMPUSER you may ask? When you create a fpbx extension, for number 4002 for example, a number of AstDB entries are created. All of the entries connect to the AMPUSER, which is the value set in the GUI for the extension number. After creating and doing apply config, you can query these AstDB entries from the asterisk CLI, for example:

tangopbx3*CLI> database show ampuser 4002
/AMPUSER/4002/cidname                             : Lorne Vader
/AMPUSER/4002/cidnum                              : 6789
/AMPUSER/4002/device                              : 994002&4002
   *remaining entries removed for clarity                      :

tangopbx3*CLI> database show device 4002
/DEVICE/4002/dial                                 : PJSIP/4002
/DEVICE/4002/user                                 : 4002

tangopbx3*CLI> database show device 994002
/DEVICE/994002/dial                               : PJSIP/994002
/DEVICE/994002/user                               : 4002

The above tells us that extension 4002 has a custom internal Caller ID number, and there are also two devices associated with it, 4002 and 994002. We can further query the AstDB for the device details and discover that one device endpoint is PJSIP/4002 where hard phones will register to, and there is also PJSIP/994002 which is used by webrtc phone in UCP. In many cases there is only one, other times there could be more, depending on system config.

When you start making calls with these devices, from Asterisk, you can see all active channels on the system from the Asterisk CLI with:

tangopbx3*CLI> core show channels
Channel                                                          Location                         State   Application(Data)
PJSIP/1.us-east.clearlyip.com-0000007c                           4002@dialOne-with-exten:1        Up      Dial(PJSIP/4002/sip:4002@173.2
PJSIP/4002-0000007d                                              (None)                           Up      AppDial((Outgoing Line))
PJSIP/994002-00000081                                              (None)                           Up      AppDial((Outgoing Line))
PJSIP/4003-0000007f                                              4002@dialOne-with-exten:1        Up      Dial(PJSIP/4002/sip:4002@173.2

We’re interested in the first column on the left, the list of active Asterisk channels. There are many times when doing development when you query asterisk for some active event, and you are provided with the channel name. It has the format of

TECH/[contact_name]-[random_identifier]

Unfortunately, the raw channel name on it’s own is seldom useful, we need to know which extension (the AMPUSER) the channel belongs to. Often you will see custom dialplan use the CALLERID(number) to determine this value, which works in most situations, but not all, because the CID name and number can both be altered in some call flows. Others might be tempted to look in the list above and see this channel:

PJSIP/4002-0000007d

And say, this is obviously extension 4002, so do a simple string manipulation to isolate the digits 4002. They might also note this channel:

PJSIP/994002-00000081

And be tempted to likewise isolate the extension number and strip off the webrtc prefix and call it done. But there other prefixes for other extension types, and there is provision in the fpbx GUI to add your own dial strings, there is no way to know in advance what prefixes you will run into. Furthermore, when parsing numeric values it’s not obvious if 9911 is extension 9911 or if it’s extension 11 with a webrtc prefix.

As far as I can tell, the ONLY way to do this cleanly is to isolate the channel name without the random identifier, and attempt to match that string against all of the dial strings for all the AstDB device entries on the system. If you find a match, then get the associated AstDB user. And so, I have created an Asterisk subroutine that does this and am maintaining it here:

Get FreePBX AMPUSER from Asterisk channel · GitHub

pass the channel name in full as an argument to the gosub, and it will return the AMPUSER of the channel if possible. This effectively gives you the ‘owner’ of the channel from fpbx’s point of view, and once you know that you can do things like move the connected caller to other devices owned by the freepbx user.

; Asterisk dialplan sub that takes an Asterisk channel name and returns the matching FreePBX AMPUSER or null if no matching user
;
; License GNU/GPL3+
;
; Latest Version: https://gist.github.com/lgaetz/1d75bab3c975f0b6b54071a7c7c07d6d
;
; Usage: When writing asterisk dialplan for fpbx, reference the subroutine and pass the channel name as ARG1
;        Channel name format follows something like PJSIP/4002-0000004f
;    Example dialplan:
;        exten => s,n,Gosub(get-AMPUSER-from-asterisk-channel,s,1(${DYNAMIC_WHO_ACTIVATED}))
;        exten => s,n,Noop(Found FreePBX AMPUSER: ${GOSUB_RETVAL})
;
; Version 2025-05-22  First commit

[get-AMPUSER-from-asterisk-channel]
exten => s,1,Noop(Attempting to match FreePBX user with Channel ${ARG1})
exten => s,n,ExecIf($["${ARG1}"=""]?Return())  ; return immediately if no channel name passed

; strip trailing random identifier and hypen from channel name to end up with just the dialing channel i.e. PJSIP/4002
exten => s,n,Set(DIALING_CHANNEL=${CUT(ARG1,-,1)})

exten => s,n,Set(DEVICES=${DB_KEYS(DEVICE)})   ; get a full commma delimited list of all dialable devices on system

; step thru the database dial entry for each dialable device looking for match to dialing channel
exten => s,n,Set(foo=)
exten => s,n,While($["${SET(DEV=${POP(DEVICES)})}" != ""])
; see if the dial string for the device matches the dialing channel part of the channel name
exten => s,n,ExecIf($["${DB(DEVICE/${DEV}/dial)}"="${DIALING_CHANNEL}"]?Set(foo=${DB(DEVICE/${DEV}/user)}))
exten => s,n,ExecIF($["${foo}"!=""]?ExitWhile)
exten => s,n,EndWhile()
exten => s,n,Return(${foo})
3 Likes

Wow! I really did encourage the flow of your creative juices! :grinning:

1 Like