[globals] ; local SIP UA Channels (PJSIP/50X-*) using the IMS endpoint are put in this group. ; This group is used as a semaphore/mutex by using COUNT_GROUP() on it. IMS_GROUP=ims_group ; Stores channel name of local SIP UA (PJSIP/50X-*) using the IMS service (in a call) ; This is used to figure out which local SIP UA can create extra calls due to having its other IMS calls on HOLD. IMS_PEER=none [get-valid-endpoints-500] ; usage: no arguments, returns DIALGROUP object of registered and valid 050x endpoints exten => s,1,Verbose(5, Entering get-valid-endpoints gosub.) same => n,Set(ENDPOINT_1_STATE=${DEVICE_STATE(PJSIP/0501)}) same => n,Set(ENDPOINT_2_STATE=${DEVICE_STATE(PJSIP/0502)}) same => n,Set(ENDPOINT_3_STATE=${DEVICE_STATE(PJSIP/0503)}) same => n,Set(ENDPOINT_4_STATE=${DEVICE_STATE(PJSIP/0504)}) same => n,Set(ENDPOINT_1_EXPR=$[$["${ENDPOINT_1_STATE}" = "UNAVAILABLE"] | $["${ENDPOINT_1_STATE}" = "UNKNOWN"] | $["${ENDPOINT_1_STATE}" = "INVALID"]]) same => n,Set(ENDPOINT_2_EXPR=$[$["${ENDPOINT_2_STATE}" = "UNAVAILABLE"] | $["${ENDPOINT_2_STATE}" = "UNKNOWN"] | $["${ENDPOINT_2_STATE}" = "INVALID"]]) same => n,Set(ENDPOINT_3_EXPR=$[$["${ENDPOINT_3_STATE}" = "UNAVAILABLE"] | $["${ENDPOINT_3_STATE}" = "UNKNOWN"] | $["${ENDPOINT_3_STATE}" = "INVALID"]]) same => n,Set(ENDPOINT_4_EXPR=$[$["${ENDPOINT_4_STATE}" = "UNAVAILABLE"] | $["${ENDPOINT_4_STATE}" = "UNKNOWN"] | $["${ENDPOINT_4_STATE}" = "INVALID"]]) same => n,Set(DIALGROUP(CALL_VALID_LIST)=) ; clear list same => n,ExecIf($[${ENDPOINT_1_EXPR} = 0]?Set(DIALGROUP(CALL_VALID_LIST,add)=PJSIP/0501)) same => n,ExecIf($[${ENDPOINT_2_EXPR} = 0]?Set(DIALGROUP(CALL_VALID_LIST,add)=PJSIP/0502)) same => n,ExecIf($[${ENDPOINT_3_EXPR} = 0]?Set(DIALGROUP(CALL_VALID_LIST,add)=PJSIP/0503)) same => n,ExecIf($[${ENDPOINT_4_EXPR} = 0]?Set(DIALGROUP(CALL_VALID_LIST,add)=PJSIP/0504)) same => n,Return(${DIALGROUP(CALL_VALID_LIST)}) [from-phone] ; Local SIP UA calls 500, broadcast to all other available local SIP UAs: exten => 0500,1,Verbose(5,${EXTEN}: Call all registered pjsips from ${CALLERID(num)}) same => n,Gosub(get-valid-endpoints-500,s,1()) same => n,Set(DIALGROUP(CALL_EVERYONE_LIST)=${GOSUB_RETVAL}) same => n,Set(DIALGROUP(CALL_EVERYONE_LIST,del)=PJSIP/${CALLERID(num)}) ; remove the caller same => n,Dial(${DIALGROUP(CALL_EVERYONE_LIST)}) same => n,Hangup(16) ; Local SIP UA calls 50X, call the target local SIP UA: exten => _050X,1,Verbose(5,${EXTEN}: Call pjsip endpoint from ${CALLERID(num)}) same => n,Dial(PJSIP/${EXTEN}) same => n,Hangup(16) ; MO Call SIP UA -> IMS: exten => _X.!,1,Verbose(5,${EXTEN}: Call external number from ${CALLERID(num)}, IMS DEVICE_STATE=${DEVICE_STATE(PJSIP/volte_ims)}, IMS_PEER=${GLOBAL(IMS_PEER)}) ; Allow only 1 MO call towards IMS, or extra ones if the same SIP UA has put previous call(s) on HOLD: same => n,Set(GROUP()=${GLOBAL(IMS_GROUP)}) same => n,Set(ALLOW_EXTRA_CALL=$[ $["${DEVICE_STATE(PJSIP/volte_ims)}" == "ONHOLD"] & $["${GLOBAL(IMS_PEER)}" == "PJSIP/${CALLERID(num)}"] ]) same => n,GotoIf($[ $[ ${GROUP_COUNT(${GLOBAL(IMS_GROUP)})} > 1] & !${ALLOW_EXTRA_CALL} ]?999) same => n,Set(GLOBAL(IMS_PEER)=PJSIP/${CALLERID(num)}) same => n,Dial(PJSIP/${EXTEN}@volte_ims) ; Channel is removed from GROUP() automatically when it is destroyed after the call finishes. ; It's fine leaving IMS_PEER set since anyway it's only checked in the case where there's a call in place (GROUP_COUNT()>1), ; so it will be set properly whenever an initial call enters the exclusion zone guarded by GROUP_COUNT(). same => n,Hangup(16) ; Reject path: same => 999,Verbose(1,${EXTEN}: VoLTE client already busy (${GROUP_COUNT(${GLOBAL(IMS_GROUP)})}, ${GLOBAL(IMS_PEER)}) rejecting call from SIP UA ${CALLERID(num)}) same => n,Set(DIALSTATUS=CHANUNAVAIL) [get-valid-endpoints-from-volte-ims] ; usage: no arguments, returns DIALGROUP object of registered and valid 050x endpoints exten => s,1,Verbose(5, Entering get-valid-endpoints-500 gosub.) same => n,Set(ENDPOINT_1_STATE=${DEVICE_STATE(PJSIP/0501)}) same => n,Set(ENDPOINT_2_STATE=${DEVICE_STATE(PJSIP/0502)}) same => n,Set(ENDPOINT_3_STATE=${DEVICE_STATE(PJSIP/0503)}) same => n,Set(ENDPOINT_4_STATE=${DEVICE_STATE(PJSIP/0504)}) same => n,Set(ENDPOINT_1_EXPR=$[$["${ENDPOINT_1_STATE}" = "NOT_INUSE"] | $["${ENDPOINT_1_STATE}" = "ONHOLD"]]) same => n,Set(ENDPOINT_2_EXPR=$[$["${ENDPOINT_2_STATE}" = "NOT_INUSE"] | $["${ENDPOINT_2_STATE}" = "ONHOLD"]]) same => n,Set(ENDPOINT_3_EXPR=$[$["${ENDPOINT_3_STATE}" = "NOT_INUSE"] | $["${ENDPOINT_3_STATE}" = "ONHOLD"]]) same => n,Set(ENDPOINT_4_EXPR=$[$["${ENDPOINT_4_STATE}" = "NOT_INUSE"] | $["${ENDPOINT_4_STATE}" = "ONHOLD"]]) same => n,Set(DIALGROUP(CALL_VALID_LIST)=) ; clear list same => n,ExecIf($[${ENDPOINT_1_EXPR} != 0]?Set(DIALGROUP(CALL_VALID_LIST,add)=PJSIP/0501)) same => n,ExecIf($[${ENDPOINT_2_EXPR} != 0]?Set(DIALGROUP(CALL_VALID_LIST,add)=PJSIP/0502)) same => n,ExecIf($[${ENDPOINT_3_EXPR} != 0]?Set(DIALGROUP(CALL_VALID_LIST,add)=PJSIP/0503)) same => n,ExecIf($[${ENDPOINT_4_EXPR} != 0]?Set(DIALGROUP(CALL_VALID_LIST,add)=PJSIP/0504)) same => n,Return(${DIALGROUP(CALL_VALID_LIST)}) [volte-ims-call-established] ; no arguments, SUB to set global variable IMS_PEER to the Channel name of the SIP UA (501-504) using the IMS endpoint ; This SUB (Dial(U())) runs under the Channel context of the SIP UA who answered the MT call. exten => s,1,Verbose(5, PJSIP/${CALLERID(num)}: Entering volte-ims-call-established gosub.) same => n,Set(GLOBAL(IMS_PEER)=PJSIP/${CALLERID(num)}) same => n,Return() [volte_ims] ; MT Call IMS -> SIP UA: exten => _X.!,1,Verbose(5,${EXTEN}: Call internal number from ${CALLERID(num)}, IMS DEVICE_STATE=${DEVICE_STATE(PJSIP/volte_ims)}, IMS_PEER=${GLOBAL(IMS_PEER)}) ; If IMS endpoint is already in use, reject it (999): same => n,Set(GROUP()=${GLOBAL(IMS_GROUP)}) same => n,GotoIf($[${GROUP_COUNT(${GLOBAL(IMS_GROUP)})} > 1]?999) ; Figure out SIP UAs to ring: same => n,Gosub(get-valid-endpoints-from-volte-ims,s,1()) same => n,Set(DIALGROUP(CALL_EVERYONE_LIST)=${GOSUB_RETVAL}) ; Process call establishment: same => n,WaitForPrecondition(10,2000) ; OUTBOUND_GROUP var tells Dial() app to set the GROUP() of the newly created channel (501-504) instead of the calling channel (volte_ims). ; This way IMS_GROUP always contains channels PJSIP/50X: same => n,Set(OUTBOUND_GROUP=${GLOBAL(IMS_GROUP)}) ; Once a SIP UA answers the call, volte-ims-call-established takes care of updating IMS_PEER with the new PJSIP/50X channel name: same => n,Dial(${DIALGROUP(CALL_EVERYONE_LIST)},,U(volte-ims-call-established)) ; Channel is removed from GROUP() automatically when it is destroyed after the call finishes. same => n,Hangup(16) ; Reject path: same => 999,Verbose(1,${EXTEN}: VoLTE client already busy (${GROUP_COUNT(${GLOBAL(IMS_GROUP)})}, ${GLOBAL(IMS_PEER)}) rejecting call from IMS ${CALLERID(num)}) same => n,Set(DIALSTATUS=BUSY)