How to pre-occupy the dial plan READ input?

Working on a user agent for the browser. Now I want to pre-occupy the number plus the dial plan READ input. The call looks like this: sip:31612345678,123,456@ip:5061

Where 31612345678 is the extension, 123 is the pin, and 456 is the ref.

; welcome
; check if this is an variable or none variable call
; a variable call uses the wallet, a none variable calls doesn't
same => n,Verbose(Do we need pin validation cause its a variable call\?);
same => n,GotoIf($[${ctbVariableTpm}=1]?ctbVariablePinValidation,1,1:ctbInboundCallStart,1,1)

[ctbVariablePinValidation]
exten => 1,1,Verbose(Pin validation)

same => n,Read(pin,/etc/asterisk/dialplans/chattabai/prompts/enterPinCode,${ctbPinLength})

same => n,Set(ctbJsonResult=${SHELL(curl -s -k '${ctbAppHost}/wp-json/ctb/v1/wallet/pin/validate?pin=${pin}&authenticationHash=${ctbAuthenticationHash}')})
same => n,Set(ctbJsonStatus=${SHELL(echo '${ctbJsonResult}' | jq '.status' | tr -d '\n"')})
same => n,GotoIf($[${ctbJsonStatus}=1]?ctbVariablePinSuccess,1,1:ctbVariablePinFailed,1,1)

[ctbVariablePinSuccess]
exten => 1,1,Verbose(Pin success)
same => n,Set(ctbVisitorWalletId=${SHELL(echo '${ctbJsonResult}' | jq '.wallet.id' | tr -d '\n"')})
same => n,Set(ctbVisitorUserId=${SHELL(echo '${ctbJsonResult}' | jq '.wallet.userId' | tr -d '\n"')})
same => n,Set(ctbVisitorTokens=${SHELL(echo '${ctbJsonResult}' | jq '.wallet.tokens' | tr -d '\n"')})
same => n,Goto(ctbVariableInboundCallStart,1,1)

[ctbVariableInboundCallStart]
exten => 1,1,Verbose(Creating variable inbound call)
same => n,Set(ctbJsonResult=${SHELL(curl -s -k '${ctbAppHost}/wp-json/ctb/v1/session/call/asterisk/create?walletId=${ctbVisitorWalletId}&userId=&dialedNumber=${CALLERID(dnid)}&inboundNumber=${CALLERID(num)}&outboundNumber=&type=inbound&dialStatus=&channelId=${CDR(uniqueid)}&context=${CONTEXT}&authenticationHash=${ctbAuthenticationHash}')})
same => n,Set(ctbSessionId=${SHELL(echo '${ctbJsonResult}' | jq '.data.sessionId' | tr -d '\n"')})
same => n,Set(ctbSessionStatus=${SHELL(echo '${ctbJsonResult}' | jq '.status' | tr -d '\n"')})
same => n,GotoIf($[${ctbSessionStatus}=1]?ctbVariableRefValidation,1,1:ctbVariableInboundCallStartFailed,1,1)

[ctbVariableRefValidation]
exten => 1,1,Verbose(Variable ref validation)

same => n,Read(ref,/etc/asterisk/dialplans/chattabai/prompts/enterReference,${ctbRefLength})

same => n,Set(ctbJsonResult=${SHELL(curl -s -k '${ctbAppHost}/wp-json/ctb/v1/user/ref/validate?ref=${ref}&authenticationHash=${ctbAuthenticationHash}')})
same => n,Set(ctbSessionStatus=${SHELL(echo '${ctbJsonResult}' | jq '.status' | tr -d '\n"')})
same => n,GotoIf($[${ctbSessionStatus}=1]?ctbVariableRefSuccess,1,1:ctbVariableRefFailed,1,1)

To make a a bit more clear, this is how it works now with JsSIP. Using setTimeout((), 2000) and setTimeout((), 4000) it will send the pin and the ref. Which works, but its a “dirty hack” and would rather have it the dial plan.

 // handle start web phone call
    startWebPhoneCall(data) {
        // register callbacks to desired call events
        let eventHandlers = {
            'progress': function (e) {
                console.log('call is in progress');
            },
            'failed': function (e) {
                console.log('call failed with cause: ' + e.cause);
            },
            'ended': function (e) {
                console.log('call ended with cause: ' + e.cause);
            },
            'confirmed': function (e) {
                console.log('call confirmed');
            },
        };

        let options = {
            'eventHandlers': eventHandlers,
            'mediaConstraints': {'audio': true},
            'stun_servers': ctbMethod.stunTurn.url + ':' + ctbMethod.stunTurn.port
        };

        // make the call
        let call = webPhoneUserAgent.call('sip:'+ data.getAttribute('data-web-phone-number') + '@'+ ctbMethod.asterisk.realm + ':' + ctbMethod.asterisk.port, options);
        if (call) {
            call.connection.addEventListener('addstream', (e) => {
                let audio = document.createElement('audio');
                audio.srcObject = e.stream;
                audio.play();
            });

            // wait 2 seconds before entering the pincode
            setTimeout(() => {
                call.sendDTMF(ctbMethod.wallet.pin);
            }, 2000);
            // wait 2 seconds before entering the reference
            setTimeout(() => {
                call.sendDTMF(data.getAttribute('data-ref'));
            }, 4000);
        }

Any suggestions how to automate this input in the dial plan.

It’s not clear whether you are asking how to write Asterisk dialplan which will make a SIP call with pauses in the number, or how to parse an extension than contains commas.

The digits in the dialstring are sent as a block of characters, with no real implication of being DTMF digits. Typical SIP UAS do not treat the user part as anything other than a single sequence, with no timing information.

If you want to send DTMF to IVRs, using Asterisk, you have to send it using the D option, where it is treated as DTMF, and not sent until the call is answered.

If you want to send parameter in the request URI, from another application, you need to use wild cards in the dialplan, to cover them, and use string operations to parse them, and not use anything that expects a flow of media.

Hello David,

Perhaps writing it without all the code will make it clear.

Current situation (which works):

  • Dial extension with JsSIP client.
  • Play soundfile and wait for MANUAL user input pin.
  • Play sound file and wait for MANUAL user input ref.
  • Continue.

Desired situation

  • Dial extension with JsSip client + user input pin + user input ref already provided
  • Play soundfile and grab user input pin from predefined in SIP url (sip:31612345678,123,456@ip:5061)
  • Play sound file and grab user input ref from predefined in SIP url (sip:31612345678,123,456@ip:5061)
  • Continue.

In other words can you predefine user input that can be used inside the dial plan. In this case a combination of 2 groups of 3 numbers. Which can be used by the application read Asterisk 18 Application_Read - Asterisk Project - Asterisk Project Wiki

(seen on other websites)

<a href='tel:555333,123,566'>555333</a>

That’s basically my second scenario; you have to do a wild card match, (_555333.) and then parse the extra digits. It’s probably safest without th commas, but you should be able to escape them one way or another. Having patterns like _555333XXX. and _555333XXXXXX may make the dialplan easier for cases where some, but all, of the extra information is pre-dialled.

chan_pjsip rejects tel: URIs, and chan_sip is about to be removed, and tel: URIs have not been tested to a level where they can be considered supported.

Also, I rather suspect that that example will only work when using something like an analogue modem to dial, and, at least in the UK, the analogue PSTN is scheduled for closure in 2025.

I would say that, if using this form of URI with SIP, it is the responsibility of the browser, or the URI handler on the machine with the browser, to separate the phone number from the following DTMF, send the former in the request URI, wait for answer, then use RFC 4733, in-band generated audio, or SIP INFO for the remaining digits.

I’m pretty sure that after POTS shutdown the replacement mechanism for handling PSTN numbers will require the phone number to be handled separately, in that way, so I think parsing the URI will only be an option within your intranet.

WIth ISDN generation PSTN technology, what will happen with that URI, sent as an ATDT string to a modem, is that the subscriber line termination will decode digits until either it has reached the maximum number length consistent with the number, or it has reached the minimum length, followed by a timeout, then probably send an IFAM (initial and final address message) over SS7, which cause the network to route the call and ring (alert) the other party. Subsequent DTMF will be examined for feature codes, but otherwise passed through as audio, the same as speech. The final destination may or may not see any of the phone number, depending on whether they have direct in dialling; simple analogue lines will see none of it.

Looking at RFC 3966, your tel: URI is not actually a valid tel: URI! “,” is a reserved character, and not allowed in numbers. The numbers has to be just the subscriber number, and anything else would have to be done as ;key=value.

As hinted above, I think what your example is really doing is treating the subscriber number as something to append to ATDT and send to a modem, rather than a true tel: URI.

You mention the ;key=value. I am currently looking at a custom sip header in the invite request. And get that value with Asterisk 18 Function_SIP_HEADER - Asterisk Project - Asterisk Project Wiki

Got more info here Asterisk func sip_header: Description and Examples - VoIP-Info

In principle that would work with sip: URIs, where the UAC should turn parameters into headers. They are not included in the request URI.

However voip.info pages are unreliable as they haven’t been updated for many years, and SIP_HEADER only works for chan_sip, which is deprecated and scheduled for removal. In fact it won’t build or load, by default, in Asterisk 18. There is a PJSIP_HEADER function, with different usage, for chan_pjsip.

I wouldn’t rely on headers getting through except within an intranet.

Got it working with the user agent and PJSIP_HEADER.

In the useragent, ( as mendioned here JsSIP - JsSIP.UA )

        let options = {
            'eventHandlers': eventHandlers,
            'mediaConstraints': {'audio': true},
            'extraHeaders': [
                'ctbPin: ' + ctbMethod.wallet.pin,
                'ctbRef: ' + data.getAttribute('data-ref'),
            ],
            'stun_servers': ctbMethod.stunTurn.url + ':' + ctbMethod.stunTurn.port
        };

        // make the call
        let call = webPhoneUserAgent.call('sip:' + data.getAttribute('data-web-phone-number') + '@' + ctbMethod.asterisk.realm + ':' + ctbMethod.asterisk.port, options);
        if (call) {
            call.connection.addEventListener('addstream', (e) => {
                let audio = document.createElement('audio');
                audio.srcObject = e.stream;
                audio.play();
            });
        }

In the dialplan i am trying to send the digits after READ, but asterisk is not accepting the SendDTMF input. (Asterisk 18 Application_SendDTMF - Asterisk Project - Asterisk Project Wiki)

exten => 1,1,Verbose(Pin validation)
same => n,Playback(/etc/asterisk/dialplans/chattabai/prompts/enterPinCode)
same => n,Read(pin,${ctbPinLength})
same => n,SendDTMF(${PJSIP_HEADER(read,ctbPin)}, 400)
same => n,Set(ctbJsonResult=${SHELL(curl -s -k ‘${ctbAppHost}/wp-json/ctb/v1/wallet/pin/validate?pin=${pin}&authenticationHash=${ctbAuthenticationHash}’)})
same => n,Set(ctbJsonStatus=${SHELL(echo ‘${ctbJsonResult}’ | jq ‘.status’ | tr -d ‘\n"’)})
same => n,GotoIf($[${ctbJsonStatus}=1]?ctbVariablePinSuccess,1,1:ctbVariablePinFailed,1,1)

-- Goto (ctbVariablePinValidation,1,1)
    -- Executing [1@ctbVariablePinValidation:1] Verbose("PJSIP/3DEED5E1-8759-4BAB-84F4-3C2E635C5CD5-0000001e", "Pin validation") in new stack
Pin validation
    -- Executing [1@ctbVariablePinValidation:2] Read("PJSIP/3DEED5E1-8759-4BAB-84F4-3C2E635C5CD5-0000001e", "pin,/etc/asterisk/dialplans/chattabai/prompts/enterPinCode,6") in new stack
    -- Accepting a maximum of 6 digits.
    -- <PJSIP/3DEED5E1-8759-4BAB-84F4-3C2E635C5CD5-0000001e> Playing '/etc/asterisk/dialplans/chattabai/prompts/enterPinCode.slin' (language 'en')
       > 0x7f29ac04b3f0 -- Strict RTP learning complete - Locking on source address 172.26.15.14:61564
    -- User entered nothing.
    -- Executing [1@ctbVariablePinValidation:3] SendDTMF("PJSIP/3DEED5E1-8759-4BAB-84F4-3C2E635C5CD5-0000001e", "969486, 400") in new stack


@Southernphonemans_Te I believe I have deleted a few of your violin images now. Please cease posting them.

  1. SendDTMF sends the DTMF towards your client; nothing in Asterisk will act on it.

  2. The Read is completely finished before the SendDTMF starts, so if you were in the misapprehension that the DTMF would be treated as input to the Read, that is a further reason why it would work.

To do what I think you want to do, you would need to skip the Read, if the header was present, and use the header to set the variable that would have been set by the Read.

Yes i did mis apprehend the DTMF. ill create a second context seperated from the main context and directly post the values needed for validation. That should solve the issue.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.