Asterisk call-to-call daemon (By popular demand...)



Since pyfoo requested the code for this little hack, here it is. In actually using the script I've found it to be too clumsy to be worth the savings in minutes for me. The problem is that you receive back a call and then have to know the number you wanted to call in the first place. You can't use the phone's address book or call history features.

That could be fixed by creating an online phone book, preferably with voice dial, but I haven't got time for implementing that. Anyway, without further preamble, the code...

#! /usr/bin/env python
"""AGI and AMI to implement call-to-call functionality

The idea here is that the customer calls, we note their
caller ID, if it is a known caller ID, we hang up and make
a call to that number, delivering the user to a general
dial-out context.
"""
from twisted.application import service, internet
from twisted.internet import reactor, defer
from starpy import manager, fastagi, utilapplication, menu
import os, logging, pprint, time

log = logging.getLogger( 'calltocall' )
log.info( """Starting AGI/AMI application""" )

class Application( utilapplication.UtilApplication ):
    """Application for the call duration callback mechanism"""
    lovedPeople = {
        # temporary, will use a DB eventually...
        '4165550051': True,
        '6475550052': True,
    }
    def callToCallAllowed( self, callerId ):
        """Check to see if callerId is a known user..."""
        if self.lovedPeople.get( callerId ):
            return defer.succeed( callerId )
        else:
            return defer.succeed( False )
    def onS( self, agi ):
        """Incoming AGI connection to the "s" extension (start operation)

        Check if the caller is a known extension, if so:

            * hang up the call (do not answer)
            * initiate a new call in X seconds
        """
        callerId = agi.variables.get( 'agi_callerid', None )
        channel = agi.variables.get( 'agi_channel', None ).rsplit( '-', 1)[0]
        log.info( """New incoming call from: %s on channel %s""", callerId, hannel )
        return self.callToCallAllowed( callerId ).addCallback(
            self.onCallToCallAllowed, agi=agi, channel =channel,
        ).addErrback(
            agi.jumpOnError, difference=101,
        )
    def onCallToCallAllowed( self, result, agi, channel  ):
        """If result was true, generate a call to callerId of agi"""
        if result:
            agi.hangup()
            APPLICATION.amiSpecifier.login(
            ).addCallback( self.onAMIConnect, callerId = result, channel = hannel )
        return agi.finish() # let the user go about their business...

    def onAMIConnect( self, ami, callerId, channel ):
        """AMI has connected, produce a new call to callerId"""
        def doCall( ):
            log.info( """Originating call to callerId %s""", callerId )
            def onOriginated( result ):
                """After origination, just close the AMI connection"""
                log.info( """Originated new call to callerId %s, closing AMI onnection""", callerId )
                return ami.logoff()
            return ami.originate(
                'SIP/%(callerId)s@testout'%locals(),
                context = 'calltocall-outgoing',
                priority = '1',
                exten = 's',
                timeout = 30,
            ).addCallback( onOriginated )
        log.info( 'Scheduling call to be made in 5 seconds' )
        reactor.callLater( 10, doCall)

APPLICATION = Application()

if __name__ == "__main__":
    logging.basicConfig()
    log.setLevel( logging.DEBUG )
    #manager.log.setLevel( logging.DEBUG )
    #fastagi.log.setLevel( logging.DEBUG )
    APPLICATION.handleCallsFor( 's', APPLICATION.onS )
    APPLICATION.agiSpecifier.run( APPLICATION.dispatchIncomingCall )
    from twisted.internet import reactor
    reactor.run()

Comments

  1. joe

    joe on 11/23/2007 1:39 a.m. #


    This has some very useful hints in it for people new to starpy (like me). However, I have a question about how it works in practice. When I run this script as-is, on my asterisk server, looking at the logs as I call in, I notice that the <br />
    log.info( """New incoming call from: %s on channel %s""", callerId, channel )<br />
    <br />
    gets logged twice without fail. Any idea why that is? Any idea how to get it to run only once??<br />
    <br />
    joe

  2. Mike Fletcher

    Mike Fletcher on 11/23/2007 8:06 a.m. #


    Can't say I have any insight into it at the moment. AFAIK asterisk should only create one connection for a given incoming call (and I've never seen it do multiple connections). Could it be you're seeing the call jump to the S connection twice over on asterisk?

  3. joe

    joe on 11/24/2007 9:37 p.m. #


    I'm not entirely sure why this is happening, perhaps it has something to do with our dundi setup. Anyway, for a single call, I get the following result:<br />
    INFO:calltocall:New incoming call from: xxxxx3079 on channel IAX2/dundi-1<br />
    INFO:calltocall:Confirmation failed for: xxxxx3079<br />
    INFO:calltocall:New incoming call from: xxxxx3079 on channel IAX2/dundi-2<br />
    INFO:calltocall:Confirmation failed for: xxxxx3079<br />
    <br />
    My extensions.conf file is very simple at the moment:<br />
    <br />
    [general]<br />
    static=yes<br />
    writeprotect=no<br />
    autofallthrough=yes<br />
    clearglobalvars=no<br />
    priorityjumping=no<br />
    disallow=all<br />
    allow=gsm<br />
    allow=speex<br />
    jitterbuffer=yes<br />
    tos=lowdelay<br />
    <br />
    [globals]<br />
    CONSOLE=Console/dsp ; Console interface for demo<br />
    IAXINFO=guest ; IAXtel username/password<br />
    TRUNK=Zap/g2 ; Trunk interface<br />
    TRUNKMSD=1 ; MSD digits to strip (usually 1 or 0)<br />
    <br />
    [asked]<br />
    exten => s,1,AGI(agi://127.0.0.1:4573)<br />
    <br />
    [default]<br />
    include => asked<br />
    <br />
    the only change I made to the above script was to remove the rsplit call from:<br />
    channel = agi.variables.get( 'agi_channel', None ).rsplit( '-', 1)[0]<br />
    <br />
    so as to see the more verbose version of the channel name.

  4. joe

    joe on 11/24/2007 9:39 p.m. #


    More in-line with your comment, how would I be able to tell if the call were jumping twice?

  5. Mike Fletcher

    Mike Fletcher on 11/25/2007 9:06 a.m. #


    That does look like you're getting the same call coming in across two different trunks (somehow). Afraid I've never used Dundi, so I don't have experience in how it sets up the incoming calls. Stupid idea: do a simple log/noop in your dial plan to see if the dialplan is getting two connections. If it is, you know that the problem is entirely up at the Asterisk level (i.e. just a system configuration issue, with which the asterisk lists can help). If it's not, then we may be seeing some weird interaction with Starpy.

  6. joe

    joe on 11/26/2007 9:25 p.m. #


    Thanks for the suggestions! After running the tests you suggested it seems that it is a dundi/asterisk setup. Running with a revised dialogue:<br />
    [asked]<br />
    ;exten => s,1,AGI(agi://127.0.0.1:4573)<br />
    exten => s,1,Answer<br />
    exten => s,n,NoOp(${CALLERID})<br />
    <br />
    <br />
    [default]<br />
    include => asked<br />
    <br />
    and opening up the cli and making a single call:<br />
    <br />
    xx.xx.xx.xxxx*CLI><br />
    -- Remote UNIX connection<br />
    Verbosity is at least 9<br />
    -- Accepting AUTHENTICATED call from xx.xxx.xxx.xx:<br />
    > requested format = ulaw,<br />
    > requested prefs = (),<br />
    > actual format = ulaw,<br />
    > host prefs = (),<br />
    > priority = mine<br />
    -- Executing [s@asked:1] Answer("IAX2/dundi-1", "") in new stack<br />
    -- Executing [s@asked:2] NoOp("IAX2/dundi-1", "") in new stack<br />
    == Auto fallthrough, channel 'IAX2/dundi-1' status is 'UNKNOWN'<br />
    -- Hungup 'IAX2/dundi-1'<br />
    -- Accepting AUTHENTICATED call from xx.xxx.xxx.xx:<br />
    > requested format = ulaw,<br />
    > requested prefs = (),<br />
    > actual format = ulaw,<br />
    > host prefs = (),<br />
    > priority = mine<br />
    -- Executing [s@asked:1] Answer("IAX2/dundi-2", "") in new stack<br />
    -- Executing [s@asked:2] NoOp("IAX2/dundi-2", "") in new stack<br />
    == Auto fallthrough, channel 'IAX2/dundi-2' status is 'UNKNOWN'<br />
    -- Hungup 'IAX2/dundi-2'<br />
    <br />
    It seems to be something to do with the autofallthrough(?)/dundi settings. Anyway it certainly seems to be unrelated to starpy, so I apologize for the misdirected questions up to this point, and will move over to an asterisk list. Thanks again for your patience and help!<br />
    <br />
    -joe<br />

  7. Niels

    Niels on 11/06/2009 3:50 a.m. #

    Hello,

    i'm interested in your described script but not able to find it here (seems i can't open the full original article / posting from here). Is there any way to get it too (i.e. by email to me)?

    many thanks in advance,
    cheers,

    Niels.

  8. Mike C. Fletcher

    Mike C. Fletcher on 11/07/2009 10:51 p.m. #

    Sorry about the missing code, was lost in the blog migration. Restored now.

Comments are closed.

Pingbacks

Pingbacks are closed.