Asterisk tips voicemail live

Voicemail Live

Answering machine mimic: Listen while caller is leaving voicemail for you; with pick-up option
Posted by Philipp von Klitzing in Dec. 2005, updated in Apr. 2010

Idea

You'd like to have the PBX voicemail act just like your answering machine at home? It can be done!
First of all you'd probably want to listen in on the new call while the caller dictates her message to the system. Then, if you wish, press # to stop the recording and connect to the caller. All this is most useful if your phone comes with auto-answer/intercom support and a decent speaker.

Concept

  • ideally you have a multi-line speaker phone that allows to configure one of the lines for auto-answer/intercom (SNOM or the like); the speaker phone (listener) is muted in that conference
  • place caller, voicemail and speaker phone into a first dynamic MeetMe conference with the help of AGI-generated .call files
  • dissolve that first MeetMe upon a # key press by the callee on the speaker phone
  • now engage in a second dynamic MeetMe to begin a two-way conversation; we allow for a maximum of two simultaneous instances of this second MeetMe

Potential enhancements

Note: For testing purposes the freely available (for private use) SNOM softphone proved to be a useful tool

  1. Use app_bridge (or ChannelRedirect ?) instead of 2nd MeetMe for duplex talking - bug/patch 5841 - similar to Asterisk n-way call HOWTO
  2. Consider ChanSpy instead of the 1st MeetMe (for listening in)
  3. Instead of jumping into a new MeetMe room we could stay inside and instead unmute ourselves (get out, unmute, get in again); possibly the voicemail recording leg can be hung up using "soft hangup", but we will first have to obtain the channel name, or we kick only that specific user from the conference (but: how to correctly determine that user?) - Workaround: Use option 'N' of MeetMeAdmin to at least silence the Voicemail application.
  4. Insert an astdb (DBGet/Put) switch for "Voicemail Live" on/off
  5. Use 'dE' instead of just 'd' to create a free dynamic conference and then do away with MeetMeCount
  6. Find out why we sometimes get the VM prompts rather late?! Possible cause is CPU load, should be low (check with 'top')
  7. Dedicate a SNOM button of type "DTMF" to issue the # and label it "voicemail pick-up"
  8. Use the SNOM "URL Action" for Off-Hook to talk to the vm caller by simply lifting the handset (no need to press #)
  9. Rewrite the dialplan part without priority jumping
  10. Play the VM intro to the caller _before_ entering the MeetMe room so that the listener (=VM box owner) does not always have to listen to it
  11. Fix me: If the caller hangs up before having entered MeetMe, then the listener and voicemail will remain in MeetMe until time out . Cause: the x option of MeetMe first of all needs the marked user to enter before it gets a chance to close the conference. Solutions:
    1. use ChanSpy instead of MeetMe (but then owner can't exit by pressing #), or
    2. let a 4th fake & marked user (option A) quickly enter and leave the MeetMe room after caller hangup (this caller's presence must then overlap with the entry of the caller into MeetMe; we would need to stay below vm config for minimum message duration), or
    3. FIXED: Set a channel variable as flag, and then use the h extension to call MeetMeAdmin on hangup and kick those that remain in MeetMe (downsides: a channel variable is probably already destroyed when we reach the h extension, but maybe we can address this with a global variable? Also employing the h extension might mess up our CDRs a little, and this h extension will be called by any closing call, not just our VM calls). This is the best way to go, probably with passing arguments to the AGI scripts on the command line (see 'ORIGVAL' below)
  12. consider to introduce the 'w' flag for the listener and set up special music-on-hold that plays pick-up instructions for the owner/listener
  13. check if $agi["callerid"] gives us trouble when a Caller ID Name is present and we thus have a space in the filename - we should better use the unique ID of the call to determine the file name for the .call files!



extensions.conf

Note: You'll most probably need priorityjumping=yes in the [general] section if you are using Asterik v1.2.x. If you don't like that then you'll need to rework the parts where jumpts to n+101 are performed, e.g. look for Dial() and ChanIsAvail()


  [vm-meet-listener]
  ; We need this (and the Local channel) in order to set the Caller ID correctly
  ; Bridges the speakerphone, which is in auto-answer mode, with vm-meet-join
  ; Used by macro-vm-meetme (initiated by macro-vm-listen.agi)
  exten => listen,1,Set(TIMEOUT(absolute)=200)  ; timeout should be slightly larger than max. VM message length
  ; if you have a SNOM then EITHER set the header below, OR configure the phone's line to auto-answer
  ;exten => listen,2,SIPAddHeader(Call-Info: <sip:domain>\;answer-after=0)       ; try SNOM auto-answer, add YOUR domain
  exten => listen,2,NoOp
  exten => listen,n,Set(CALLERID(name)=${ORIGCIDNAME})
  exten => listen,n,Set(CALLERID(num)=${ORIGCIDNUM})
  ; you could add an additional availability check with ChanIsAvail() or DEVICE_STATE() before starting the dial
  exten => listen,n,Dial(${TARGET},4)    ; give intercom/auto-answer 4 sec to answer
  exten => listen,n,Hangup  

  exten => t,1,NoOp(=== The auto-answer setup of ${TARGET} might not be correctly configured ===)
  exten => t,2,HangUp

  exten => h,1,NoOp(-- Call hung up in the middle of our Voicemail Live context - ORIGVAL: ${ORIGVAL} --)
  exten => h,n,GotoIf($["${ORIGVAL}" = ""]?cont)  ; a VM caller would have this set to 'vm-meet-exit'
  exten => h,n,MeetMeAdmin(99${ORIGVAL},K)        ; the VM caller hung up, now kill the conference
  exten => h,n(cont),NoOp  ; if desired do additional cleaning up at this point

  [vm-meet-join]
  ; -- Dialplan logic for the callee (aka 'silent listener' aka 'vm-owner') --
  ; This bridges with vm-meet-listener above
  ; Press # to exit the voicemail meetme room and kick the calling user
  ; OPTIONAL: Dedicate a "DTMF" type SNOM button to send #
  ; REQUIRES: Language directory "mm" with silenced "conf-kicked.gsm" soundfile (short!)
  ;        e.g. [root@sounds]# cp silence/1.gsm mm/conf-kicked.gsm
  ; REQUIRES: vm-instructions-short.gsm (2nd half of vm-instructions.gsm)
  ; NOTE: The two bugs/patches mentioned below are not necessary for Asterisk v1.2.1 or later
  ; NOTE: Use [http://bugs.digium.com/view.php?id=5773|bug/patch 5773] to fix MeetMe X option in Asterisk v1.2.0 (ref. bug 5631)
  ; NOTE: Use [http://bugs.digium.com/view.php?id=5810|bug/patch 5810] to pass variables from .call files to Local channels in Asterisk v1.2.0
  ; NOTE: We can't transfer from within 2nd MeetMe
  exten => join,1,Set(TIMEOUT(absolute)=200)
  ; The playback statement also ensures that we will enter the MeetMe room after the caller
  exten => join,n,Playback(vm-instructions-short) ; play "#-pickup" instructions for the listener
  exten => join,n,Wait(.5)
  exten => join,n,MeetMeCount(99${ROOMNO}|mcount)  ; did the caller hang up already?
  exten => join,n,GotoIf($[${mcount} > 1]?end)
  exten => join,n,MeetMe(99${ROOMNO}|dmqpx)       ; due to playback we enter after caller (we are muted)
  exten => join,n,MeetMeAdmin(99${ROOMNO},K)      ; Kick all users after exiting MeetMe by pressing #
  ; now establish another MeetMe where we are not muted and can therefore have a two-way talk with the caller
  exten => join,n,MeetMeCount(98${ROOMNO}|mcount)
  exten => join,n,GotoIf($[${mcount} > 1]?third)  ; check if our two-way conversation room is already in use
  exten => join,n,Set(ROOMSELECT=98${ROOMNO})     ; remember the chosen room - we are the first to enter
  exten => join,n,MeetMe(98${ROOMNO}|dqA)       ; Consider app_bridge instead (Asterisk 1.6?)
  exten => join,n,MeetMeAdmin(98${ROOMNO},K)     ; Kick with # to make sure the room is closed
  exten => join,n,Hangup
  ; the first two-way room is still occupied by a previous caller so let's move on to the second one
  exten => join,n(third),MeetMeCount(97${ROOMNO}|mcount)
  exten => join,n,GotoIf($[${mcount} > 1]?end)
  exten => join,n,Set(ROOMSELECT=97${ROOMNO})
  exten => join,n,MeetMe(97${ROOMNO}|dqA)       ; Consider app_bridge instead (Asterisk 1.6?)
  exten => join,n,MeetMeAdmin(97${ROOMNO},K)     ; Kick with # to make sure its closed
  exten => join,n(end),Hangup                         ; we dont allow more than 2 vm callers

  ; Macros need their own h extension!
  exten => h,1,GotoIf($["${ROOMSELECT}" = ""]?end)
  exten => h,2,MeetMeAdmin(${ROOMSELECT},K)       ; in case we went on-hook before caller
  exten => h,3(end),NoOp(ROOMSELECT=${ROOMSELECT})

  [vm-meet-exit]
  ; -- Exit context for the VM caller (for pressing #) --
  ; This way we can distinguish between own # and being kicked by MeetMeAdmin!
  exten => #,1,NoOp(=== The VM caller pressed: # ===)
  exten => #,2,Hangup

  [macro-vm-meetme]
  ; -- This is the main macro for 'voicemail live' handling the caller --
  ; This is where the logic is and where the intial PSTN caller is treated
  ; Note: In addition we also need a 'h' extension in the orginal (default?) context in case caller hangs up early
  ;
  ;   ${ARG1} - Device(s) to ring (TARGET)
  ;   ${ARG2} - Extension
  ;   ${ARG3} - Mailbox
  ;
  ; Required contexts: [vm-meet-listener] and [vm-meet-join] and [vm-meet-exit]
  ;
  ; -- We are unavailable - don't come here if we are busy! --
  exten => s,1,Set(TIMEOUT(absolute)=199)         ; need to synchronize this with max vm rec
  exten => s,2,Set(TARGET=${ARG1})                ; we read this in AGI
  exten => s,3,Set(ORIGVAL=${ARG2})           ; we need this later for the 'h' exten and MeetMeAdmin kick
  exten => s,4,ChanIsAvail(${ARG1},j)             ; only works for SIP peers?! Priority jumping is ugly.
  exten => s,n,Set(ORIGLANG=CHANNEL(language))   ; preserve the original language setting
  exten => s,n,MeetMeCount(99${ARG2}|mcount)
  exten => s,n,GotoIf($[${mcount} > 0]?vmpure)         ; do we already have someone on VM recording?
  exten => s,n,AGI(macro-vm-listen.agi|${ORIGVAL})  ; put the VM owner into MeetMe for Intercom
  exten => s,n,Wait(.1)
  exten => s,n,AGI(macro-vm-record.agi|${ORIGVAL})   ; put the VM record app into MeetMe
  exten => s,n,Playback(beep)                    ; play short sound so caller knows we answered
  ; at this point we sometimes get a delay with silence due to slow call-setup work of Asterisk
  ; Note: This was written for Asterisk 1.4 - use LANGUAGE() instead of CHANNEL(language) in Asterisk 1.2
  exten => s,n,Set(CHANNEL(language)=mm)                ; we have to avoid "You have been kicked..."!
                                                  ; replace conf-kicked.gsm with plain silence
  exten => s,n,Wait(1)    ; give the AGI scripts and SIP/intercom (TARGET) some more time to get into MeetMe
  exten => s,n,MeetMeCount(99${ARG2}|mcount)
  exten => s,n,GotoIf($[${mcount} = 0]?vmpure)   ; check again: if room is empty then something went wrong
  exten => s,n,Set(MEETME_EXIT_CONTEXT=vm-meet-exit)  ; only in case the caller presses #
  ; now put the caller into MeetMe together with voicemail and the silent listener
  exten => s,n,MeetMe(99${ARG2},dAXq)            ; Option X works after applying patch 5773
  ; we were kicked by the listener pressing # so now let's get into a two-way conversation room
  exten => s,n,Wait(.5)
  exten => s,n,MeetMeCount(98${ARG2}|mcount)    ; we arrive here after 1) kick or 2) # press
  exten => s,n,GotoIf($[${mcount} > 1]?meetme3)
  exten => s,n,Playback(beep)
  exten => s,n,MeetMe(98${ARG2}|dqxM)  ; Room for conversation. The listener will enter first and is marked
  exten => s,n,Hangup
  exten => s,n(meetme3),MeetMeCount(97${ARG2}|mcount)
  exten => s,n,GotoIf($[${mcount} > 1]?vmpure)       ; we tried twice, so no meetme, now pure vm
  exten => s,n,Playback(beep)
  exten => s,n,MeetMe(97${ARG2}|dqxM)
  exten => s,n,HangUp
  ; use pure voicemail (no fancy meetme conference)
  exten => s,n(vmpure),Voicemail(${ARG3}|u)             ; we already have another VM-MeetMe active
  exten => s,n,Hangup

  exten => s,105,Goto(vmpure)                        ; ChanIsAvail gave a negative result (came here by 4 + 101)

  exten => h,1,NoOp(-- Call hung up in the middle of macro execution - ORIGVAL: ${ORIGVAL} --)
  exten => h,n,GotoIf($["${ORIGVAL}" = ""]?cont)  ; a VM caller would have this set to 'vm-meet-exit'
  exten => h,n,MeetMeAdmin(99${ORIGVAL},K)        ; the VM caller hung up, now kill the conference
  exten => h,n(cont),NoOp

  [default]
  ; === Join voicemail to meetme (for macro-vm-meetme) ===
  ; NOTE: Adjust macro-vm-record.agi accordingly if you change the '8600' prefix here
  exten => _8600X.,1,Set(CALLERID(name)=${ORIGCIDNAME}) ; needed for the e-mail notification
  exten => _8600X.,2,Set(CALLERID(number)=${ORIGCIDNUM})
  exten => _8600X.,3,VoiceMail(${EXTEN:4}|u)
  exten => _8600X.,4,Hangup

  ; Finally here is the extension for our own phone (SIP/myPeer); send caller to voicemail if we are not available
  exten => 1234,1,Dial(SIP/myPeer,20,t)
  ; now start Voicemail Live on the speakerphone of SIP/myPeer
  exten => 1234,2,Macro(vm-meetme,SIP/myPeer,${EXTEN},81234)  ; we are unavailable
  exten => 1234,3,Hangup
  exten => 1234,102,Voicemail(81234|b)
  exten => 1234,103,HangUp

  exten => h,1,NoOp(-- Call hung up in the default context - ORIGVAL: ${ORIGVAL} --)
  exten => h,n,GotoIf($["${ORIGVAL}" = ""]?cont)
  exten => h,n,MeetMeAdmin(99${ORIGVAL},K)
  exten => h,n(cont),NoOp





AGI scripts (PHP)

Place these AGI scripts into /var/lib/asterisk/agi-bin/ and make them executable ('chmod u+x macro-vm-*.agi').
Note: On Debian you will need to have 'php-cli' installed for this. Of course next to any other language can do the job as well, you are not bound to PHP.

macro-vm-listen.agi



  #!/usr/bin/php -q
  <?php

  ob_implicit_flush(true);
  set_time_limit(5);
  $in = fopen("php://stdin","r");

  // toggle debugging output (more verbose)
  $debug = false;
  //$debug = true;
 
 function __read__() {
   global $in, $debug;
   $input = str_replace("\n", "", fgets($in, 4096));
   if ($debug) echo "VERBOSE \"read: $input\"\n";
   return $input;
 }

 function __write__($line) {
    global $debug;
    if ($debug) echo "VERBOSE \"write: $line\"\n";
    print $line."\n";
 }

 //read the standard agi variables
 while (!feof($in)) {
        $temp = str_replace("\n","",fgets($in,4096));
        $s = split(":",$temp);
        $agi[str_replace("agi_","",$s[0])] = trim($s[1]);
        if (($temp == "") || ($temp == "\n")) {
                break;
        }
 }

  //get the variable 'ORIGVAL' that was passed as command line argument to this script
  $origval = $argv[1];

  //get the variables and strip off all the extra stuff around
  __write__~/np~("GET VARIABLE TARGET");
  $res = substr(strrchr(~np~__read__(),"("),1,-1);
  __write__~/np~("GET VARIABLE ARG2");
  $arg2 = substr(strrchr(~np~__read__(),"("),1,-1);

  $cf = fopen("/tmp/cb".$agi["callerid"],"w+");
  fputs($cf,"Channel: Local/listen@vm-meet-listener/n\n");
  fputs($cf,"Set: _ORIGCIDNAME=".$agi["calleridname"]."\n");
  fputs($cf,"Set: _ORIGCIDNUM=".$agi["callerid"]."\n");
  fputs($cf,"Set: CALLERID(name)=".$agi["calleridname"]."\n");
  fputs($cf,"Set: CALLERID(number)=".$agi["callerid"]."\n");
  fputs($cf,"Set: _TARGET=".$res."\n");
  fputs($cf,"Set: _ROOMNO=".$arg2."\n");
  fputs($cf,"Set: _ORIGVAL=".$origval."\n");
  fputs($cf,"MaxRetries: 0\n");
  fputs($cf,"RetryTime: 10\n");
  // --- We are the first so we create the dynamic conference ---
  fputs($cf,"Context: vm-meet-join\n");
  fputs($cf,"Extension: join\n");
  fputs($cf,"Priority: 1\n");

  //Now move (!) the file to the outgoing dir AFTER we closed it
  fclose($cf);
  exec("mv /tmp/cb".$agi["callerid"]." /var/spool/asterisk/outgoing");

  fclose($in);
  ?>



macro-vm-record.agi


  1. !/usr/bin/php -q
<?php

ob_implicit_flush(true);
set_time_limit(5);
$in = fopen("php://stdin","r");

// toggle debugging output (more verbose)
$debug = false;
//$debug = true;

function __read__() {
global $in, $debug;
$input = str_replace("\n", "", fgets($in, 4096));
if ($debug) echo "VERBOSE \"read: $input\"\n";
return $input;
}

function __write__($line) {
global $debug;
if ($debug) echo "VERBOSE \"write: $line\"\n";
print $line."\n";
}

//read the standard agi variables
while (!feof($in)) {
$temp = str_replace("\n","",fgets($in,4096));
$s = split(":",$temp);
$agi[str_replace("agi_","",$s[0])] = trim($s[1]);
if (($temp == "") || ($temp == "\n")) {
break;
}
}

//get the variable 'ORIGVAL' that was passed as command line argument to this script
$origval = $argv[1];

//get the variables and strip off all the extra stuff around
__write__("GET VARIABLE ARG2");
$arg2 = substr(strrchr(__read__(),"("),1,-1);
__write__("GET VARIABLE ARG3");
$arg3 = substr(strrchr(__read__(),"("),1,-1);

$cf = fopen("/tmp/cb2_".$agi["callerid"],"w+");
fputs($cf,"Channel: Local/8600".$arg3."@default/n\n");
fputs($cf,"Set: CALLERID(name)=".$agi["calleridname"]."\n");
fputs($cf,"Set: CALLERID(number)=".$agi["callerid"]."\n");
fputs($cf,"Set: _ORIGCIDNAME=".$agi["calleridname"]."\n");
fputs($cf,"Set: _ORIGCIDNUM=".$agi["callerid"]."\n");
fputs($cf,"Set: _ORIGVAL=".$origval."\n")
fputs($cf,"MaxRetries: 0\n");
fputs($cf,"RetryTime: 10\n");
fputs($cf,"Application: MeetMe\n");
fputs($cf,"Data: 99".$arg2."|dpqx\n");

//Now move (!) the file to the outgoing dir AFTER we closed it
fclose($cf);
exec("mv /tmp/cb2_".$agi["callerid"]." /var/spool/asterisk/outgoing/");

fclose($in);
?>




See also



Go back to Asterisk tips and tricks

Voicemail Live

Answering machine mimic: Listen while caller is leaving voicemail for you; with pick-up option
Posted by Philipp von Klitzing in Dec. 2005, updated in Apr. 2010

Idea

You'd like to have the PBX voicemail act just like your answering machine at home? It can be done!
First of all you'd probably want to listen in on the new call while the caller dictates her message to the system. Then, if you wish, press # to stop the recording and connect to the caller. All this is most useful if your phone comes with auto-answer/intercom support and a decent speaker.

Concept

  • ideally you have a multi-line speaker phone that allows to configure one of the lines for auto-answer/intercom (SNOM or the like); the speaker phone (listener) is muted in that conference
  • place caller, voicemail and speaker phone into a first dynamic MeetMe conference with the help of AGI-generated .call files
  • dissolve that first MeetMe upon a # key press by the callee on the speaker phone
  • now engage in a second dynamic MeetMe to begin a two-way conversation; we allow for a maximum of two simultaneous instances of this second MeetMe

Potential enhancements

Note: For testing purposes the freely available (for private use) SNOM softphone proved to be a useful tool

  1. Use app_bridge (or ChannelRedirect ?) instead of 2nd MeetMe for duplex talking - bug/patch 5841 - similar to Asterisk n-way call HOWTO
  2. Consider ChanSpy instead of the 1st MeetMe (for listening in)
  3. Instead of jumping into a new MeetMe room we could stay inside and instead unmute ourselves (get out, unmute, get in again); possibly the voicemail recording leg can be hung up using "soft hangup", but we will first have to obtain the channel name, or we kick only that specific user from the conference (but: how to correctly determine that user?) - Workaround: Use option 'N' of MeetMeAdmin to at least silence the Voicemail application.
  4. Insert an astdb (DBGet/Put) switch for "Voicemail Live" on/off
  5. Use 'dE' instead of just 'd' to create a free dynamic conference and then do away with MeetMeCount
  6. Find out why we sometimes get the VM prompts rather late?! Possible cause is CPU load, should be low (check with 'top')
  7. Dedicate a SNOM button of type "DTMF" to issue the # and label it "voicemail pick-up"
  8. Use the SNOM "URL Action" for Off-Hook to talk to the vm caller by simply lifting the handset (no need to press #)
  9. Rewrite the dialplan part without priority jumping
  10. Play the VM intro to the caller _before_ entering the MeetMe room so that the listener (=VM box owner) does not always have to listen to it
  11. Fix me: If the caller hangs up before having entered MeetMe, then the listener and voicemail will remain in MeetMe until time out . Cause: the x option of MeetMe first of all needs the marked user to enter before it gets a chance to close the conference. Solutions:
    1. use ChanSpy instead of MeetMe (but then owner can't exit by pressing #), or
    2. let a 4th fake & marked user (option A) quickly enter and leave the MeetMe room after caller hangup (this caller's presence must then overlap with the entry of the caller into MeetMe; we would need to stay below vm config for minimum message duration), or
    3. FIXED: Set a channel variable as flag, and then use the h extension to call MeetMeAdmin on hangup and kick those that remain in MeetMe (downsides: a channel variable is probably already destroyed when we reach the h extension, but maybe we can address this with a global variable? Also employing the h extension might mess up our CDRs a little, and this h extension will be called by any closing call, not just our VM calls). This is the best way to go, probably with passing arguments to the AGI scripts on the command line (see 'ORIGVAL' below)
  12. consider to introduce the 'w' flag for the listener and set up special music-on-hold that plays pick-up instructions for the owner/listener
  13. check if $agi["callerid"] gives us trouble when a Caller ID Name is present and we thus have a space in the filename - we should better use the unique ID of the call to determine the file name for the .call files!



extensions.conf

Note: You'll most probably need priorityjumping=yes in the [general] section if you are using Asterik v1.2.x. If you don't like that then you'll need to rework the parts where jumpts to n+101 are performed, e.g. look for Dial() and ChanIsAvail()


  [vm-meet-listener]
  ; We need this (and the Local channel) in order to set the Caller ID correctly
  ; Bridges the speakerphone, which is in auto-answer mode, with vm-meet-join
  ; Used by macro-vm-meetme (initiated by macro-vm-listen.agi)
  exten => listen,1,Set(TIMEOUT(absolute)=200)  ; timeout should be slightly larger than max. VM message length
  ; if you have a SNOM then EITHER set the header below, OR configure the phone's line to auto-answer
  ;exten => listen,2,SIPAddHeader(Call-Info: <sip:domain>\;answer-after=0)       ; try SNOM auto-answer, add YOUR domain
  exten => listen,2,NoOp
  exten => listen,n,Set(CALLERID(name)=${ORIGCIDNAME})
  exten => listen,n,Set(CALLERID(num)=${ORIGCIDNUM})
  ; you could add an additional availability check with ChanIsAvail() or DEVICE_STATE() before starting the dial
  exten => listen,n,Dial(${TARGET},4)    ; give intercom/auto-answer 4 sec to answer
  exten => listen,n,Hangup  

  exten => t,1,NoOp(=== The auto-answer setup of ${TARGET} might not be correctly configured ===)
  exten => t,2,HangUp

  exten => h,1,NoOp(-- Call hung up in the middle of our Voicemail Live context - ORIGVAL: ${ORIGVAL} --)
  exten => h,n,GotoIf($["${ORIGVAL}" = ""]?cont)  ; a VM caller would have this set to 'vm-meet-exit'
  exten => h,n,MeetMeAdmin(99${ORIGVAL},K)        ; the VM caller hung up, now kill the conference
  exten => h,n(cont),NoOp  ; if desired do additional cleaning up at this point

  [vm-meet-join]
  ; -- Dialplan logic for the callee (aka 'silent listener' aka 'vm-owner') --
  ; This bridges with vm-meet-listener above
  ; Press # to exit the voicemail meetme room and kick the calling user
  ; OPTIONAL: Dedicate a "DTMF" type SNOM button to send #
  ; REQUIRES: Language directory "mm" with silenced "conf-kicked.gsm" soundfile (short!)
  ;        e.g. [root@sounds]# cp silence/1.gsm mm/conf-kicked.gsm
  ; REQUIRES: vm-instructions-short.gsm (2nd half of vm-instructions.gsm)
  ; NOTE: The two bugs/patches mentioned below are not necessary for Asterisk v1.2.1 or later
  ; NOTE: Use [http://bugs.digium.com/view.php?id=5773|bug/patch 5773] to fix MeetMe X option in Asterisk v1.2.0 (ref. bug 5631)
  ; NOTE: Use [http://bugs.digium.com/view.php?id=5810|bug/patch 5810] to pass variables from .call files to Local channels in Asterisk v1.2.0
  ; NOTE: We can't transfer from within 2nd MeetMe
  exten => join,1,Set(TIMEOUT(absolute)=200)
  ; The playback statement also ensures that we will enter the MeetMe room after the caller
  exten => join,n,Playback(vm-instructions-short) ; play "#-pickup" instructions for the listener
  exten => join,n,Wait(.5)
  exten => join,n,MeetMeCount(99${ROOMNO}|mcount)  ; did the caller hang up already?
  exten => join,n,GotoIf($[${mcount} > 1]?end)
  exten => join,n,MeetMe(99${ROOMNO}|dmqpx)       ; due to playback we enter after caller (we are muted)
  exten => join,n,MeetMeAdmin(99${ROOMNO},K)      ; Kick all users after exiting MeetMe by pressing #
  ; now establish another MeetMe where we are not muted and can therefore have a two-way talk with the caller
  exten => join,n,MeetMeCount(98${ROOMNO}|mcount)
  exten => join,n,GotoIf($[${mcount} > 1]?third)  ; check if our two-way conversation room is already in use
  exten => join,n,Set(ROOMSELECT=98${ROOMNO})     ; remember the chosen room - we are the first to enter
  exten => join,n,MeetMe(98${ROOMNO}|dqA)       ; Consider app_bridge instead (Asterisk 1.6?)
  exten => join,n,MeetMeAdmin(98${ROOMNO},K)     ; Kick with # to make sure the room is closed
  exten => join,n,Hangup
  ; the first two-way room is still occupied by a previous caller so let's move on to the second one
  exten => join,n(third),MeetMeCount(97${ROOMNO}|mcount)
  exten => join,n,GotoIf($[${mcount} > 1]?end)
  exten => join,n,Set(ROOMSELECT=97${ROOMNO})
  exten => join,n,MeetMe(97${ROOMNO}|dqA)       ; Consider app_bridge instead (Asterisk 1.6?)
  exten => join,n,MeetMeAdmin(97${ROOMNO},K)     ; Kick with # to make sure its closed
  exten => join,n(end),Hangup                         ; we dont allow more than 2 vm callers

  ; Macros need their own h extension!
  exten => h,1,GotoIf($["${ROOMSELECT}" = ""]?end)
  exten => h,2,MeetMeAdmin(${ROOMSELECT},K)       ; in case we went on-hook before caller
  exten => h,3(end),NoOp(ROOMSELECT=${ROOMSELECT})

  [vm-meet-exit]
  ; -- Exit context for the VM caller (for pressing #) --
  ; This way we can distinguish between own # and being kicked by MeetMeAdmin!
  exten => #,1,NoOp(=== The VM caller pressed: # ===)
  exten => #,2,Hangup

  [macro-vm-meetme]
  ; -- This is the main macro for 'voicemail live' handling the caller --
  ; This is where the logic is and where the intial PSTN caller is treated
  ; Note: In addition we also need a 'h' extension in the orginal (default?) context in case caller hangs up early
  ;
  ;   ${ARG1} - Device(s) to ring (TARGET)
  ;   ${ARG2} - Extension
  ;   ${ARG3} - Mailbox
  ;
  ; Required contexts: [vm-meet-listener] and [vm-meet-join] and [vm-meet-exit]
  ;
  ; -- We are unavailable - don't come here if we are busy! --
  exten => s,1,Set(TIMEOUT(absolute)=199)         ; need to synchronize this with max vm rec
  exten => s,2,Set(TARGET=${ARG1})                ; we read this in AGI
  exten => s,3,Set(ORIGVAL=${ARG2})           ; we need this later for the 'h' exten and MeetMeAdmin kick
  exten => s,4,ChanIsAvail(${ARG1},j)             ; only works for SIP peers?! Priority jumping is ugly.
  exten => s,n,Set(ORIGLANG=CHANNEL(language))   ; preserve the original language setting
  exten => s,n,MeetMeCount(99${ARG2}|mcount)
  exten => s,n,GotoIf($[${mcount} > 0]?vmpure)         ; do we already have someone on VM recording?
  exten => s,n,AGI(macro-vm-listen.agi|${ORIGVAL})  ; put the VM owner into MeetMe for Intercom
  exten => s,n,Wait(.1)
  exten => s,n,AGI(macro-vm-record.agi|${ORIGVAL})   ; put the VM record app into MeetMe
  exten => s,n,Playback(beep)                    ; play short sound so caller knows we answered
  ; at this point we sometimes get a delay with silence due to slow call-setup work of Asterisk
  ; Note: This was written for Asterisk 1.4 - use LANGUAGE() instead of CHANNEL(language) in Asterisk 1.2
  exten => s,n,Set(CHANNEL(language)=mm)                ; we have to avoid "You have been kicked..."!
                                                  ; replace conf-kicked.gsm with plain silence
  exten => s,n,Wait(1)    ; give the AGI scripts and SIP/intercom (TARGET) some more time to get into MeetMe
  exten => s,n,MeetMeCount(99${ARG2}|mcount)
  exten => s,n,GotoIf($[${mcount} = 0]?vmpure)   ; check again: if room is empty then something went wrong
  exten => s,n,Set(MEETME_EXIT_CONTEXT=vm-meet-exit)  ; only in case the caller presses #
  ; now put the caller into MeetMe together with voicemail and the silent listener
  exten => s,n,MeetMe(99${ARG2},dAXq)            ; Option X works after applying patch 5773
  ; we were kicked by the listener pressing # so now let's get into a two-way conversation room
  exten => s,n,Wait(.5)
  exten => s,n,MeetMeCount(98${ARG2}|mcount)    ; we arrive here after 1) kick or 2) # press
  exten => s,n,GotoIf($[${mcount} > 1]?meetme3)
  exten => s,n,Playback(beep)
  exten => s,n,MeetMe(98${ARG2}|dqxM)  ; Room for conversation. The listener will enter first and is marked
  exten => s,n,Hangup
  exten => s,n(meetme3),MeetMeCount(97${ARG2}|mcount)
  exten => s,n,GotoIf($[${mcount} > 1]?vmpure)       ; we tried twice, so no meetme, now pure vm
  exten => s,n,Playback(beep)
  exten => s,n,MeetMe(97${ARG2}|dqxM)
  exten => s,n,HangUp
  ; use pure voicemail (no fancy meetme conference)
  exten => s,n(vmpure),Voicemail(${ARG3}|u)             ; we already have another VM-MeetMe active
  exten => s,n,Hangup

  exten => s,105,Goto(vmpure)                        ; ChanIsAvail gave a negative result (came here by 4 + 101)

  exten => h,1,NoOp(-- Call hung up in the middle of macro execution - ORIGVAL: ${ORIGVAL} --)
  exten => h,n,GotoIf($["${ORIGVAL}" = ""]?cont)  ; a VM caller would have this set to 'vm-meet-exit'
  exten => h,n,MeetMeAdmin(99${ORIGVAL},K)        ; the VM caller hung up, now kill the conference
  exten => h,n(cont),NoOp

  [default]
  ; === Join voicemail to meetme (for macro-vm-meetme) ===
  ; NOTE: Adjust macro-vm-record.agi accordingly if you change the '8600' prefix here
  exten => _8600X.,1,Set(CALLERID(name)=${ORIGCIDNAME}) ; needed for the e-mail notification
  exten => _8600X.,2,Set(CALLERID(number)=${ORIGCIDNUM})
  exten => _8600X.,3,VoiceMail(${EXTEN:4}|u)
  exten => _8600X.,4,Hangup

  ; Finally here is the extension for our own phone (SIP/myPeer); send caller to voicemail if we are not available
  exten => 1234,1,Dial(SIP/myPeer,20,t)
  ; now start Voicemail Live on the speakerphone of SIP/myPeer
  exten => 1234,2,Macro(vm-meetme,SIP/myPeer,${EXTEN},81234)  ; we are unavailable
  exten => 1234,3,Hangup
  exten => 1234,102,Voicemail(81234|b)
  exten => 1234,103,HangUp

  exten => h,1,NoOp(-- Call hung up in the default context - ORIGVAL: ${ORIGVAL} --)
  exten => h,n,GotoIf($["${ORIGVAL}" = ""]?cont)
  exten => h,n,MeetMeAdmin(99${ORIGVAL},K)
  exten => h,n(cont),NoOp





AGI scripts (PHP)

Place these AGI scripts into /var/lib/asterisk/agi-bin/ and make them executable ('chmod u+x macro-vm-*.agi').
Note: On Debian you will need to have 'php-cli' installed for this. Of course next to any other language can do the job as well, you are not bound to PHP.

macro-vm-listen.agi



  #!/usr/bin/php -q
  <?php

  ob_implicit_flush(true);
  set_time_limit(5);
  $in = fopen("php://stdin","r");

  // toggle debugging output (more verbose)
  $debug = false;
  //$debug = true;
 
 function __read__() {
   global $in, $debug;
   $input = str_replace("\n", "", fgets($in, 4096));
   if ($debug) echo "VERBOSE \"read: $input\"\n";
   return $input;
 }

 function __write__($line) {
    global $debug;
    if ($debug) echo "VERBOSE \"write: $line\"\n";
    print $line."\n";
 }

 //read the standard agi variables
 while (!feof($in)) {
        $temp = str_replace("\n","",fgets($in,4096));
        $s = split(":",$temp);
        $agi[str_replace("agi_","",$s[0])] = trim($s[1]);
        if (($temp == "") || ($temp == "\n")) {
                break;
        }
 }

  //get the variable 'ORIGVAL' that was passed as command line argument to this script
  $origval = $argv[1];

  //get the variables and strip off all the extra stuff around
  __write__~/np~("GET VARIABLE TARGET");
  $res = substr(strrchr(~np~__read__(),"("),1,-1);
  __write__~/np~("GET VARIABLE ARG2");
  $arg2 = substr(strrchr(~np~__read__(),"("),1,-1);

  $cf = fopen("/tmp/cb".$agi["callerid"],"w+");
  fputs($cf,"Channel: Local/listen@vm-meet-listener/n\n");
  fputs($cf,"Set: _ORIGCIDNAME=".$agi["calleridname"]."\n");
  fputs($cf,"Set: _ORIGCIDNUM=".$agi["callerid"]."\n");
  fputs($cf,"Set: CALLERID(name)=".$agi["calleridname"]."\n");
  fputs($cf,"Set: CALLERID(number)=".$agi["callerid"]."\n");
  fputs($cf,"Set: _TARGET=".$res."\n");
  fputs($cf,"Set: _ROOMNO=".$arg2."\n");
  fputs($cf,"Set: _ORIGVAL=".$origval."\n");
  fputs($cf,"MaxRetries: 0\n");
  fputs($cf,"RetryTime: 10\n");
  // --- We are the first so we create the dynamic conference ---
  fputs($cf,"Context: vm-meet-join\n");
  fputs($cf,"Extension: join\n");
  fputs($cf,"Priority: 1\n");

  //Now move (!) the file to the outgoing dir AFTER we closed it
  fclose($cf);
  exec("mv /tmp/cb".$agi["callerid"]." /var/spool/asterisk/outgoing");

  fclose($in);
  ?>



macro-vm-record.agi


  1. !/usr/bin/php -q
<?php

ob_implicit_flush(true);
set_time_limit(5);
$in = fopen("php://stdin","r");

// toggle debugging output (more verbose)
$debug = false;
//$debug = true;

function __read__() {
global $in, $debug;
$input = str_replace("\n", "", fgets($in, 4096));
if ($debug) echo "VERBOSE \"read: $input\"\n";
return $input;
}

function __write__($line) {
global $debug;
if ($debug) echo "VERBOSE \"write: $line\"\n";
print $line."\n";
}

//read the standard agi variables
while (!feof($in)) {
$temp = str_replace("\n","",fgets($in,4096));
$s = split(":",$temp);
$agi[str_replace("agi_","",$s[0])] = trim($s[1]);
if (($temp == "") || ($temp == "\n")) {
break;
}
}

//get the variable 'ORIGVAL' that was passed as command line argument to this script
$origval = $argv[1];

//get the variables and strip off all the extra stuff around
__write__("GET VARIABLE ARG2");
$arg2 = substr(strrchr(__read__(),"("),1,-1);
__write__("GET VARIABLE ARG3");
$arg3 = substr(strrchr(__read__(),"("),1,-1);

$cf = fopen("/tmp/cb2_".$agi["callerid"],"w+");
fputs($cf,"Channel: Local/8600".$arg3."@default/n\n");
fputs($cf,"Set: CALLERID(name)=".$agi["calleridname"]."\n");
fputs($cf,"Set: CALLERID(number)=".$agi["callerid"]."\n");
fputs($cf,"Set: _ORIGCIDNAME=".$agi["calleridname"]."\n");
fputs($cf,"Set: _ORIGCIDNUM=".$agi["callerid"]."\n");
fputs($cf,"Set: _ORIGVAL=".$origval."\n")
fputs($cf,"MaxRetries: 0\n");
fputs($cf,"RetryTime: 10\n");
fputs($cf,"Application: MeetMe\n");
fputs($cf,"Data: 99".$arg2."|dpqx\n");

//Now move (!) the file to the outgoing dir AFTER we closed it
fclose($cf);
exec("mv /tmp/cb2_".$agi["callerid"]." /var/spool/asterisk/outgoing/");

fclose($in);
?>




See also



Go back to Asterisk tips and tricks

Created by: JustRumours, Last modification: Thu 24 of Jun, 2010 (21:30 UTC)
Please update this page with new information, just login and click on the "Edit" or "Discussion" tab. Get a free login here: Register Thanks! - Find us on Google+