Skip to content

Instantly share code, notes, and snippets.

@lgaetz
Last active August 13, 2024 14:27
Show Gist options
  • Save lgaetz/4c14829c14eb0a739a216016d9f394b2 to your computer and use it in GitHub Desktop.
Save lgaetz/4c14829c14eb0a739a216016d9f394b2 to your computer and use it in GitHub Desktop.
Feature code to seamlessly bounce an active bridged call between a user’s multiple devices
; FreePBX dialplan for a feature code that can be dialed from an idle device that will immediately bounce
; any active call from any of the the same user's extensions. Primary use case is when a user has multiple
; registered devices they can quickly move the active call between deskphone, to mobile client to desktop client.
;
; License: GNU GPL3+
; latest version: https://gist.github.com/lgaetz/4c14829c14eb0a739a216016d9f394b2
;
; version history 2024-03-22 First commit working
; 2024-03-24 Fix hint and change ext away from _.
[from-internal-custom]
exten => _*37!,hint,PJSIP/${EXTEN:3}&PJSIP/98${EXTEN:3}&PJSIP/99${EXTEN:3}
exten => _*37!,1,Noop(User defined context to seize any active call from dialing user)
exten => _*37!,n,Macro(user-callerid)
exten => _*37!,n,Goto(seize-current-active-call,${AMPUSER},1)
[seize-current-active-call]
exten => _X.,1,Noop(User defined context seize-current-active-call to seize any active call from the dialing user)
; get full list of dialable devices for the user from AstDB
exten => _X.,n,Set(DEVS=${DB(AMPUSER/${AMPUSER}/device)}) ; & delimited list of devices
exten => _X.,n,Set(DEVS=${STRREPLACE(DEVS,&,\,)}) ; comma delimited list of devices
; step thru each device and look for an active channel
exten => _X.,n,While($["${SET(DEV=${POP(DEVS)})}" != ""])
exten => _X.,n,noop(device: ${DEV})
; using a regex of SIP/${EXTEN}- will match both SIP and PJSIP channels, and the trailing - character should
; help to ensure there is only a single match.
exten => _X.,n,set(seize_target=${CHANNELS(SIP/${DEV}-)})
; we don't want the dialing channel to seize itself, so remove current dialing channel from channel list and trim string
exten => _X.,n,Noop(Current channel name: ${CHANNEL})
exten => _X.,n,set(seize_target=${STRREPLACE(seize_target,${CHANNEL},)})
exten => _X.,n,set(seize_target=${STRREPLACE(seize_target, ,)})
; if an active channel is found exit the loop, otherwise loop again
exten => _X.,n,ExecIf($["${seize_target}"!=""]?ExitWhile)
exten => _X.,n,EndWhile()
exten => _X.,n,Bridge(${IMPORT(${CHANNELS(${seize_target})},BRIDGEPEER)})
exten => _X.,n,Macro(hangupcall)
@therestlesstexan
Copy link

therestlesstexan commented Aug 12, 2024

Any plans to make this compatible with Gosub? I tried my hand at it, and got somewhere, but not where I wanted to be LOL - the bridge section ends up with no channel to seize.... CLI output below:

-- Executing [s@macro-user-callerid:26] GotoIf("PJSIP/1021-00000001", "0?limit") in new stack
-- Executing [s@macro-user-callerid:27] ExecIf("PJSIP/1021-00000001", "0?Set(GROUP(concurrency_limit)=1021)") in new stack
-- Executing [s@macro-user-callerid:28] ExecIf("PJSIP/1021-00000001", "0?Set(CHANNEL(language)=)") in new stack
-- Executing [s@macro-user-callerid:29] NoOp("PJSIP/1021-00000001", "Macro depricated!! To keep the same line numbers") in new stack
-- Executing [s@macro-user-callerid:30] NoOp("PJSIP/1021-00000001", "Macro depricated !!  To keep the same line numbers") in new stack
-- Executing [s@macro-user-callerid:31] GotoIf("PJSIP/1021-00000001", "0?continue") in new stack
-- Executing [s@macro-user-callerid:32] ExecIf("PJSIP/1021-00000001", "1?Set(__CALLEE_ACCOUNCODE=)") in new stack
-- Executing [s@macro-user-callerid:33] Set("PJSIP/1021-00000001", "__TTL=64") in new stack
-- Executing [s@macro-user-callerid:34] GotoIf("PJSIP/1021-00000001", "1?continue") in new stack
-- Goto (macro-user-callerid,s,50)
-- Executing [s@macro-user-callerid:50] Set("PJSIP/1021-00000001", "CALLERID(number)=1021") in new stack
-- Executing [s@macro-user-callerid:51] Set("PJSIP/1021-00000001", "CALLERID(name)=Andy Pumpelly") in new stack
-- Executing [s@macro-user-callerid:52] GotoIf("PJSIP/1021-00000001", "0?cnum") in new stack
-- Executing [s@macro-user-callerid:53] Set("PJSIP/1021-00000001", "__MCNUM=1021") in new stack
-- Executing [s@macro-user-callerid:54] Set("PJSIP/1021-00000001", "__MCNAME=Andy Pumpelly") in new stack
-- Executing [s@macro-user-callerid:55] Set("PJSIP/1021-00000001", "__MCEXTEN=1021") in new stack
-- Executing [s@macro-user-callerid:56] Set("PJSIP/1021-00000001", "__MCORGCHAN=PJSIP/1021-00000001") in new stack
-- Executing [s@macro-user-callerid:57] Set("PJSIP/1021-00000001", "CDR(cnam)=Andy Pumpelly") in new stack
-- Executing [s@macro-user-callerid:58] Set("PJSIP/1021-00000001", "CDR(cnum)=1021") in new stack
-- Executing [s@macro-user-callerid:59] GosubIf("PJSIP/1021-00000001", "0?app-check-classofservce,s,1()") in new stack
-- Executing [s@macro-user-callerid:60] Return("PJSIP/1021-00000001", "") in new stack
-- Executing [*371021@from-internal:3] Goto("PJSIP/1021-00000001", "seize-current-active-call,1021,1") in new stack
-- Goto (seize-current-active-call,1021,1)
-- Executing [1021@seize-current-active-call:1] NoOp("PJSIP/1021-00000001", "User defined context seize-current-active-call to seize any active call from the dialing user") in new stack
-- Executing [1021@seize-current-active-call:2] Set("PJSIP/1021-00000001", "DEVS=1021&981021&991021") in new stack
-- Executing [1021@seize-current-active-call:3] Set("PJSIP/1021-00000001", "DEVS=1021,981021,991021") in new stack
-- Executing [1021@seize-current-active-call:4] While("PJSIP/1021-00000001", "1") in new stack
-- Executing [1021@seize-current-active-call:5] NoOp("PJSIP/1021-00000001", "device: 991021") in new stack
-- Executing [1021@seize-current-active-call:6] Set("PJSIP/1021-00000001", "seize_target=") in new stack
-- Executing [1021@seize-current-active-call:7] NoOp("PJSIP/1021-00000001", "Current channel name: PJSIP/1021-00000001") in new stack
-- Executing [1021@seize-current-active-call:8] Set("PJSIP/1021-00000001", "seize_target=") in new stack
-- Executing [1021@seize-current-active-call:9] Set("PJSIP/1021-00000001", "seize_target=") in new stack
-- Executing [1021@seize-current-active-call:10] NoOp("PJSIP/1021-00000001", "ST: ") in new stack
-- Executing [1021@seize-current-active-call:11] ExecIf("PJSIP/1021-00000001", "0?ExitWhile") in new stack
-- Executing [1021@seize-current-active-call:12] EndWhile("PJSIP/1021-00000001", "") in new stack
-- Executing [1021@seize-current-active-call:4] While("PJSIP/1021-00000001", "1") in new stack
-- Executing [1021@seize-current-active-call:5] NoOp("PJSIP/1021-00000001", "device: 981021") in new stack
-- Executing [1021@seize-current-active-call:6] Set("PJSIP/1021-00000001", "seize_target=") in new stack
-- Executing [1021@seize-current-active-call:7] NoOp("PJSIP/1021-00000001", "Current channel name: PJSIP/1021-00000001") in new stack
-- Executing [1021@seize-current-active-call:8] Set("PJSIP/1021-00000001", "seize_target=") in new stack
-- Executing [1021@seize-current-active-call:9] Set("PJSIP/1021-00000001", "seize_target=") in new stack
-- Executing [1021@seize-current-active-call:10] NoOp("PJSIP/1021-00000001", "ST: ") in new stack
-- Executing [1021@seize-current-active-call:11] ExecIf("PJSIP/1021-00000001", "0?ExitWhile") in new stack
-- Executing [1021@seize-current-active-call:12] EndWhile("PJSIP/1021-00000001", "") in new stack
-- Executing [1021@seize-current-active-call:4] While("PJSIP/1021-00000001", "1") in new stack
-- Executing [1021@seize-current-active-call:5] NoOp("PJSIP/1021-00000001", "device: 1021") in new stack
-- Executing [1021@seize-current-active-call:6] Set("PJSIP/1021-00000001", "seize_target=PJSIP/1021-00000001 PJSIP/1021-00000000") in new stack
-- Executing [1021@seize-current-active-call:7] NoOp("PJSIP/1021-00000001", "Current channel name: PJSIP/1021-00000001") in new stack
-- Executing [1021@seize-current-active-call:8] Set("PJSIP/1021-00000001", "seize_target= PJSIP/1021-00000000") in new stack
-- Executing [1021@seize-current-active-call:9] Set("PJSIP/1021-00000001", "seize_target=PJSIP/1021-00000000") in new stack
-- Executing [1021@seize-current-active-call:10] NoOp("PJSIP/1021-00000001", "ST: PJSIP/1021-00000000") in new stack
-- Executing [1021@seize-current-active-call:11] ExecIf("PJSIP/1021-00000001", "1?ExitWhile") in new stack
-- Executing [1021@seize-current-active-call:13] Bridge("PJSIP/1021-00000001", "") in new stack
   > Bridge failed because channel '' does not exist
-- Executing [1021@seize-current-active-call:14] Gosub("PJSIP/1021-00000001", "macro-hangupcall,s,1()") in new stack
-- Executing [s@macro-hangupcall:1] Set("PJSIP/1021-00000001", "__MCVMSTATUS=") in new stack
-- Executing [s@macro-hangupcall:2] Gosub("PJSIP/1021-00000001", "app-missedcall-hangup,s,1()") in new stack
-- Executing [s@app-missedcall-hangup:1] NoOp("PJSIP/1021-00000001", "Dialed: s") in new stack
-- Executing [s@app-missedcall-hangup:2] NoOp("PJSIP/1021-00000001", "Caller: 1021") in new stack
-- Executing [s@app-missedcall-hangup:3] GotoIf("PJSIP/1021-00000001", "0?exit") in new stack
-- Executing [s@app-missedcall-hangup:4] Set("PJSIP/1021-00000001", "EXTENNUM=s") in new stack
-- Executing [s@app-missedcall-hangup:5] Set("PJSIP/1021-00000001", "FEXTENNUM=s") in new stack
-- Executing [s@app-missedcall-hangup:6] GotoIf("PJSIP/1021-00000001", "0?exit") in new stack
-- Executing [s@app-missedcall-hangup:7] AGI("PJSIP/1021-00000001", "agi://127.0.0.1/missedcallnotify.php,s,,s,0,,PJSIP/1021-00000001,,,,") in new stack
-- <PJSIP/1021-00000001>AGI Script agi://127.0.0.1/missedcallnotify.php completed, returning 0
-- Executing [s@app-missedcall-hangup:8] Return("PJSIP/1021-00000001", "") in new stack
-- Executing [s@macro-hangupcall:3] UserEvent("PJSIP/1021-00000001", "MES,RTPAUDIOQOSMESBRIDGED:,RTPAUDIOQOSMES:") in new stack
-- Executing [s@macro-hangupcall:4] GotoIf("PJSIP/1021-00000001", "1?theend") in new stack
-- Goto (macro-hangupcall,s,6)
-- Executing [s@macro-hangupcall:6] ExecIf("PJSIP/1021-00000001", "0?Set(CDR(recordingfile)=)") in new stack
-- Executing [s@macro-hangupcall:7] ExecIf("PJSIP/1021-00000001", "0?AGI(pbxmfa.agi,noanswer)") in new stack
-- Executing [s@macro-hangupcall:8] Hangup("PJSIP/1021-00000001", "") in new stack

@lgaetz
Copy link
Author

lgaetz commented Aug 13, 2024

@therestlesstexan it should be a simple matter of changing the callerid macro line to

exten => _*37!,n,Gosub(macro-user-callerid,s,1())

and line 43 bot of seize-current-active-call

exten => _X.,n,Gosub(macro-hangupcall,s,1())

@therestlesstexan
Copy link

Already tried that - that's when the seize target started to be blank.....

This is the dial plan as it reads now, which produces the erroneous result in the CLI output in my original comment:

[from-internal-custom]

exten => _*37!,hint,PJSIP/${EXTEN:3}&PJSIP/98${EXTEN:3}&PJSIP/99${EXTEN:3}
exten => _*37!,1,Noop(User defined context to seize any active call from dialing user)
exten => _*37!,n,Gosub(macro-user-callerid,s,1())
exten => _*37!,n,Goto(seize-current-active-call,${AMPUSER},1)

[seize-current-active-call]

exten => _X.,1,Noop(User defined context seize-current-active-call to seize any active call from the dialing user)

; get full list of dialable devices for the user from AstDB
exten => _X.,n,Set(DEVS=${DB(AMPUSER/${AMPUSER}/device)}) ; & delimited list of devices
exten => _X.,n,Set(DEVS=${STRREPLACE(DEVS,&,,)}) ; comma delimited list of devices

; step thru each device and look for an active channel
exten => _X.,n,While($["${SET(DEV=${POP(DEVS)})}" != ""])
exten => _X.,n,noop(device: ${DEV})
; using a regex of SIP/${EXTEN}- will match both SIP and PJSIP channels, and the trailing - character should
; help to ensure there is only a single match.
exten => _X.,n,set(seize_target=${CHANNELS(SIP/${DEV}-)})

; we don't want the dialing channel to seize itself, so remove current dialing channel from channel list and trim string
exten => _X.,n,Noop(Current channel name: ${CHANNEL})
exten => _X.,n,set(seize_target=${STRREPLACE(seize_target,${CHANNEL},)})
exten => _X.,n,set(seize_target=${STRREPLACE(seize_target, ,)})

; if an active channel is found exit the loop, otherwise loop again
exten => _X.,n,ExecIf($["${seize_target}"!=""]?ExitWhile)
exten => _X.,n,EndWhile()

exten => _X.,n,Bridge(${IMPORT(${CHANNELS(${seize_target})},BRIDGEPEER)})
exten => _X.,n,Gosub(macro-hangupcall,s,1())

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment