Macro to automatically send DTMF after call setup

This macro is commonly used to send *9 (via DTMF) to enable Pexip’s dual screen (people + people) feature, or *4 to toggle Pexip’s presentation in mix feature on/off. It can also be used to send other DTMF sequences, just edit line 11 to change from *9 to whatever is required.

Editing line 10 is a required step to dial this macro in for your environment. You will want to specifically change the example.com to match the calling domain used in your environment.

If you are using this macro in combination with Pexip CVI or Google Meet interop, you can leave the other 2 regexes on line 10 (.*@pex.ms & [a-zA-Z-]{10,12}@google.pexip.me) in place which allows for this same DTMF to be sent when use Pexip’s SIP Guest join for both Microsoft Teams and Google Meet.

This macro operates in the background, there is no panel or button exposed to users of the room system. This macro is widely deployed and used on systems registered to Pexip Infinity, Pexip Cloud, Webex cloud, VCS/Expressway, and CUCM.

/*
sendDtmfAfterSelectCallSetup 
WHAT:   This macro is designed to send dtmf after calls that match a regex are successfully setup
WHY:    This can be used to enter PIN codes, or enable other features with DTMF like dual screen, presentation in mix, change layout, etc. 
HOW:    If the dialed SIP URI matches any of the regexs in the selectUriRegexes array, the room system will send the "dtmfSequence" automatically after the call is connected.  
LIMITS: The macro is not designed for calls that use 2 stage dialing (with an IVR).  
*/

import xapi from 'xapi';
const selectUriRegexes = [/\d{9,12}@example\.com/,/.*@pex\.ms/,/[a-zA-Z\-]{10,12}@google\.pexip\.me/]; // modify this array of regexes to match your environment, you can add more regexes or just use one regex
const dtmfSequence = '*9'; // this DTMF sequence to be sent automatically after connecting to any calls matching selectUriRegexes
const msPause = 4000; // millisecond pause, used for delay between when the call status = connected and when the dtmfSequence is sent.
const preferSilentDTMF = true; // true: dtmfSequence sent with "silent feedback, or false: send audible DTMF 

let callId;

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms))
}

async function sendDtmfAfterPause (sequence, pause, trySilent) {
  console.debug(`Sleeping for ${pause} ms...`);
  await sleep(pause);
  console.info(`Sending DTMF sequence of ${sequence} now...`);
  if (trySilent) {
    try {
          await xapi.command('Call DTMFSend', {DTMFString: sequence,Feedback: 'Silent'});
        } catch (error) {
          console.info('Command to send silent DTMF errored, most likely an older system or firmware that does not support silent DTMF. Trying command without Feedback parameter. Error: ' + error);
          try {
            await xapi.command('Call DTMFSend', {DTMFString: sequence});
          }  catch (error) {
              console.error('Command to send audible DTMF failed with error: ' + error);
          }
        }
  }
  if (!trySilent) { 
    try {
          await xapi.command('Call DTMFSend', {DTMFString: sequence});
        } catch (error) {
          console.error('Command to send audible DTMF failed with error: ' + error);
        }
  }
}

const getCalledUri = async () => {
  try {
        const value = await xapi.Status.Call[callId].CallbackNumber.get();
        console.debug('CallbackNumber: ' + JSON.stringify(value));
        const uri = await value.split(':')[1];
        return uri;
      } catch (error) {
        console.error('Failed to retrieve Callback Number from callid, error: ' + error); 
      }
}

xapi.event.on('CallSuccessful', (event) => {
  console.info('Call successful event details: ' + JSON.stringify(event));
  callId = event.CallId;
  getCalledUri().then(alias => {
                  for (const regex of selectUriRegexes) {
                    if(regex.test(alias)) {
                      console.info(`Call with ${alias} matches selectUriRegexes list entry, DTMF sequence will be sent`);
                      sendDtmfAfterPause(dtmfSequence, msPause, preferSilentDTMF);
                      break; //exit the for loop if a match is found after sending DTMF
                    } else {
                      console.info(`Call with ${alias} does NOT match regex ${regex} in list`);
                    }
                  }
    }).catch(error => console.info(error));
});
1 Like

I have a small update to this macro that will be helpful for any organizations that are using this macro with the specific combination of Webex cloud registered room systems and Pexip’s cloud CVI service. If you are using this macro on room systems registered to anything other than Webex cloud, there is no need to make any changes.

There is a planned change coming to the Pexip cloud CVI service that will result in a slightly different Callback number returned from the room system’s xapi when in a Pexip CVI call. So the updated macro adjusts for this change with a modified example regex. I also adjusted a few log lines for clarity.

You can update your macro today as the example regex on line 10 will work with the existing callback number as well as the forthcoming callback number format (planned change mentioned above). Just remember to update the domain on line 9 to match your CVI domain. Reach out to me if you have any questions on regex changes.

If you don’t make this macro update, your Pexip CVI calls are still going to work, but your room systems will likely no longer send the dtmfSequence in the macro after the call is established. And this may result in the lack of *9 sent for Pexip’s dual screen (people + people) feature or *4 sent to toggle Pexip’s presentation in mix feature on/off, or some other feature capability enabled with DTMF.

The new details included in the callback number (and reason behind this change) will allow Pexip to deliver even more capabilities to Webex cloud registered room systems used in combination with Pexip cloud CVI service. So apologies for the small change here, but I’m really excited for what we are going to be able to deliver once this change is in place!

Here’s the updated macro that includes a new example regex.

/*
sendDtmfAfterSelectCallSetup 
WHAT:   This macro is designed to send dtmf after calls that match a regex are successfully setup
WHY:    This can be used to enter PIN codes, or enable other features with DTMF like dual screen, presentation in mix, change layout, etc. 
HOW:    If the dialed SIP URI matches any of the regexs in the selectUriRegexes array, the room system will send the "dtmfSequence" automatically after the call is connected.  
LIMITS: The macro is not designed for calls that use 2 stage dialing (with an IVR).  
*/

import xapi from 'xapi';
const selectUriRegexes = [/^\d+.*@example\.com/,/.*@pex\.ms/,/[a-zA-Z\-]{10,12}(\.[a-f0-9]{32})?@google\.pexip\.me/]; // modify this array of regexes to match your environment, you can add more regexes or just use one regex
const dtmfSequence = '*9'; // this DTMF sequence to be sent automatically after connecting to any calls matching selectUriRegexes
const msPause = 4000; // millisecond pause, used for delay between when the call status = connected and when the dtmfSequence is sent.
const preferSilentDTMF = true; // true: dtmfSequence sent with "silent feedback, or false: send audible DTMF 

let callId;

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms))
}

async function sendDtmfAfterPause (sequence, pause, trySilent) {
  console.debug(`Sleeping for ${pause} ms...`);
  await sleep(pause);
  console.info(`Sending DTMF sequence of ${sequence} now...`);
  if (trySilent) {
    try {
          await xapi.command('Call DTMFSend', {DTMFString: sequence,Feedback: 'Silent'});
        } catch (error) {
          console.info('Command to send silent DTMF errored, most likely an older system or firmware that does not support silent DTMF. Trying command without Feedback parameter. Error: ' + error);
          try {
            await xapi.command('Call DTMFSend', {DTMFString: sequence});
          }  catch (error) {
              console.error('Command to send audible DTMF failed with error: ' + error);
          }
        }
  }
  if (!trySilent) { 
    try {
          await xapi.command('Call DTMFSend', {DTMFString: sequence});
        } catch (error) {
          console.error('Command to send audible DTMF failed with error: ' + error);
        }
  }
}

const getCalledUri = async () => {
  try {
        const value = await xapi.Status.Call[callId].CallbackNumber.get();
        console.debug('CallbackNumber: ' + JSON.stringify(value));
        const uri = await value.split(':')[1];
        return uri;
      } catch (error) {
        console.error('Failed to retrieve Callback Number from callid, error: ' + error); 
      }
}

xapi.event.on('CallSuccessful', (event) => {
  console.info('Call successful event details: ' + JSON.stringify(event));
  callId = event.CallId;
  getCalledUri().then(alias => {
                  for (const regex of selectUriRegexes) {
                    if(regex.test(alias)) {
                      console.info(`CallbackNumber: ${alias} matches selectUriRegexes list entry, DTMF sequence will be sent`);
                      sendDtmfAfterPause(dtmfSequence, msPause, preferSilentDTMF);
                      break; //exit the for loop if a match is found after sending DTMF
                    } else {
                      console.info(`CallbackNumber: ${alias} does NOT match regex ${regex} in list`);
                    }
                  }
    }).catch(error => console.info(error));
});