2

I am developing a FastAgi application for an Asterisk IVR implementation using Starpy and Twisted. So far, the application runs pretty well when it only has one phone call.

After a second call is established, all the successive responses from Asterisk are sent to the second call: the stream audio belonging to the first call it's heard on the second call, the Hangup() disconnects the second call (the first one stays connected until manually disconnected from the soft phone)

I am using X-lite softphones with Elastix distro. My FastAgi server is on a Windows laptop. on Extensions_custom.conf I have these entries to route the calls:

exten => 2000,1,AGI(agi://10.0.0.7:4573) exten => 2000,n,Hangup()

This is how the protocol is set on the server:

logging.basicConfig()
fastagi.log.setLevel( logging.DEBUG )
f = fastagi.FastAGIFactory(MyIVRApplication())
reactor.listenTCP(4573, f, 50, '10.0.0.167') 
reactor.run()

I built my application using one of the examples provided by starpy, the DialPlan() app which just plays the number of times that the app has been accessed. Even that application when run instead of my application has the same issues, only handles one call properly.

I was using a wireless network, but same thing happens when using cables. I tried both a virtual machine with asterisk (in virtualbox) and a physical machine. Same thing. Installed a different softphone on and Android phone. Same thing. Used a physical Ip Phone. Same thing. The only thing that I have not tried yet is to move my FastAgi server to a linux box instead of using my windows laptop.

Any help will be appreciated.

Thanks in advance.

Hector

EDIT:

I am adding the AGI debug log from Asterisk. As you can see, after the second call is establish, all the Tx and Rx commands are sent / received from the second channel. Asterisk never talks again with the first channel, which stays connected.

== Using SIP RTP TOS bits 184
== Using SIP RTP CoS mark 5
-- Executing [2000@from-internal:1] AGI("SIP/4001-00000006", "agi://10.0.0.167") in new stack
AGI Tx >> agi_network: yes
<SIP/4001-00000006>AGI Tx >> agi_request: agi://10.0.0.167
<SIP/4001-00000006>AGI Tx >> agi_channel: SIP/4001-00000006
<SIP/4001-00000006>AGI Tx >> agi_language: en
<SIP/4001-00000006>AGI Tx >> agi_type: SIP
<SIP/4001-00000006>AGI Tx >> agi_uniqueid: 1360854557.6
<SIP/4001-00000006>AGI Tx >> agi_version: 1.8.11.0
<SIP/4001-00000006>AGI Tx >> agi_callerid: 4001
<SIP/4001-00000006>AGI Tx >> agi_calleridname: device
<SIP/4001-00000006>AGI Tx >> agi_callingpres: 0
<SIP/4001-00000006>AGI Tx >> agi_callingani2: 0
<SIP/4001-00000006>AGI Tx >> agi_callington: 0
<SIP/4001-00000006>AGI Tx >> agi_callingtns: 0
<SIP/4001-00000006>AGI Tx >> agi_dnid: 2000
<SIP/4001-00000006>AGI Tx >> agi_rdnis: unknown
<SIP/4001-00000006>AGI Tx >> agi_context: from-internal
<SIP/4001-00000006>AGI Tx >> agi_extension: 2000
<SIP/4001-00000006>AGI Tx >> agi_priority: 1
<SIP/4001-00000006>AGI Tx >> agi_enhanced: 0.0
<SIP/4001-00000006>AGI Tx >> agi_accountcode:
<SIP/4001-00000006>AGI Tx >> agi_threadid: -1219851376
<SIP/4001-00000006>AGI Tx >>
<SIP/4001-00000006>AGI Rx << ANSWER
<SIP/4001-00000006>AGI Tx >> 200 result=0
<SIP/4001-00000006>AGI Rx << STREAM FILE "custom/bienvenida" '' 0
    -- Playing 'custom/bienvenida' (escape_digits='') (sample_offset 0)
<SIP/4001-00000006>AGI Tx >> 200 result=0 endpos=24402
<SIP/4001-00000006>AGI Rx << GET DATA "custom/cuando_este_listo" 5000.0 1
    -- <SIP/4001-00000006> Playing 'custom/cuando_este_listo.slin' (language 'en')
<SIP/4001-00000006>AGI Tx >> 200 result=
<SIP/4001-00000006>AGI Rx << STREAM FILE "custom/14" '' 0
    -- Playing 'custom/14' (escape_digits='') (sample_offset 0)
<SIP/4001-00000006>AGI Tx >> 200 result=0 endpos=7028
<SIP/4001-00000006>AGI Rx << STREAM FILE "custom/menos" '' 0
    -- Playing 'custom/menos' (escape_digits='') (sample_offset 0)
<SIP/4001-00000006>AGI Tx >> 200 result=0 endpos=6762
<SIP/4001-00000006>AGI Rx << STREAM FILE "custom/9" '' 0
    -- Playing 'custom/9' (escape_digits='') (sample_offset 0)
<SIP/4001-00000006>AGI Tx >> 200 result=0 endpos=5666
<SIP/4001-00000006>AGI Rx << GET DATA "" 5000.0 2
  == Using SIP RTP TOS bits 184
  == Using SIP RTP CoS mark 5
    -- Executing [2000@from-internal:1] AGI("SIP/4002-00000007", "agi://10.0.0.167") in new stack
AGI Tx >> agi_network: yes
<SIP/4002-00000007>AGI Tx >> agi_request: agi://10.0.0.167
<SIP/4002-00000007>AGI Tx >> agi_channel: SIP/4002-00000007
<SIP/4002-00000007>AGI Tx >> agi_language: en
<SIP/4002-00000007>AGI Tx >> agi_type: SIP
<SIP/4002-00000007>AGI Tx >> agi_uniqueid: 1360854568.7
<SIP/4002-00000007>AGI Tx >> agi_version: 1.8.11.0
<SIP/4002-00000007>AGI Tx >> agi_callerid: 4002
<SIP/4002-00000007>AGI Tx >> agi_calleridname: device
<SIP/4002-00000007>AGI Tx >> agi_callingpres: 0
<SIP/4002-00000007>AGI Tx >> agi_callingani2: 0
<SIP/4002-00000007>AGI Tx >> agi_callington: 0
<SIP/4002-00000007>AGI Tx >> agi_callingtns: 0
<SIP/4002-00000007>AGI Tx >> agi_dnid: 2000
<SIP/4002-00000007>AGI Tx >> agi_rdnis: unknown
<SIP/4002-00000007>AGI Tx >> agi_context: from-internal
<SIP/4002-00000007>AGI Tx >> agi_extension: 2000
<SIP/4002-00000007>AGI Tx >> agi_priority: 1
<SIP/4002-00000007>AGI Tx >> agi_enhanced: 0.0
<SIP/4002-00000007>AGI Tx >> agi_accountcode:
<SIP/4002-00000007>AGI Tx >> agi_threadid: -1220097136
<SIP/4002-00000007>AGI Tx >>
<SIP/4002-00000007>AGI Rx << ANSWER
<SIP/4002-00000007>AGI Tx >> 200 result=0
<SIP/4002-00000007>AGI Rx << STREAM FILE "custom/bienvenida" '' 0
    -- Playing 'custom/bienvenida' (escape_digits='') (sample_offset 0)
<SIP/4001-00000006>AGI Tx >> 200 result= (timeout)
<SIP/4002-00000007>AGI Rx << STREAM FILE "custom/respuesta_incorrecta" '' 0
    -- Playing 'custom/respuesta_incorrecta' (escape_digits='') (sample_offset 0)
<SIP/4002-00000007>AGI Tx >> 200 result=0 endpos=14260
<SIP/4002-00000007>AGI Rx << GET DATA "custom/cuando_este_listo" 5000.0 1
    -- <SIP/4002-00000007> Playing 'custom/cuando_este_listo.slin' (language 'en')
<SIP/4002-00000007>AGI Tx >> 200 result=
    -- <SIP/4002-00000007>AGI Script agi://10.0.0.167 completed, returning 0
<SIP/4002-00000007>AGI Tx >> HANGUP
    -- Executing [2000@from-internal:2] Hangup("SIP/4002-00000007", "") in new stack
  == Spawn extension (from-internal, 2000, 2) exited non-zero on 'SIP/4002-00000007'
    -- Executing [h@from-internal:1] Macro("SIP/4002-00000007", "hangupcall") in new stack
    -- Executing [s@macro-hangupcall:1] GotoIf("SIP/4002-00000007", "1?endmixmoncheck") in new stack
    -- Goto (macro-hangupcall,s,9)
    -- Executing [s@macro-hangupcall:9] NoOp("SIP/4002-00000007", "End of MIXMON check") in new stack
    -- Executing [s@macro-hangupcall:10] GotoIf("SIP/4002-00000007", "1?nomeetmemon") in new stack
    -- Goto (macro-hangupcall,s,15)
    -- Executing [s@macro-hangupcall:15] NoOp("SIP/4002-00000007", "MEETME_RECORDINGFILE=") in new stack
    -- Executing [s@macro-hangupcall:16] GotoIf("SIP/4002-00000007", "1?noautomon") in new stack
    -- Goto (macro-hangupcall,s,18)
    -- Executing [s@macro-hangupcall:18] NoOp("SIP/4002-00000007", "TOUCH_MONITOR_OUTPUT=") in new stack
    -- Executing [s@macro-hangupcall:19] GotoIf("SIP/4002-00000007", "1?noautomon2") in new stack
    -- Goto (macro-hangupcall,s,25)
    -- Executing [s@macro-hangupcall:25] NoOp("SIP/4002-00000007", "MONITOR_FILENAME=") in new stack
    -- Executing [s@macro-hangupcall:26] GotoIf("SIP/4002-00000007", "1?skiprg") in new stack
    -- Goto (macro-hangupcall,s,29)
    -- Executing [s@macro-hangupcall:29] GotoIf("SIP/4002-00000007", "1?skipblkvm") in new stack
    -- Goto (macro-hangupcall,s,32)
    -- Executing [s@macro-hangupcall:32] GotoIf("SIP/4002-00000007", "1?theend") in new stack
    -- Goto (macro-hangupcall,s,34)
    -- Executing [s@macro-hangupcall:34] Hangup("SIP/4002-00000007", "") in new stack
  == Spawn extension (macro-hangupcall, s, 34) exited non-zero on 'SIP/4002-00000007' in macro 'hangupcall'
  == Spawn extension (from-internal, h, 1) exited non-zero on 'SIP/4002-00000007'
localhost*CLI>

EDIT:

Here's the code I used as a guide. It's an application that comes as a example in the Starpy library. When I put this application I get the same results. I put this because mine is a little tooooo big.

#! /usr/bin/env python
"""Read digits from the user in various ways..."""
from twisted.internet import reactor, defer
from starpy import fastagi, error
import logging, time

log = logging.getLogger( 'hellofastagi' )

class DialPlan( object ):
    """Stupid little application to report how many times it's been accessed"""
    def __init__( self ):
        self.count = 0
    def __call__( self, agi ):
        """Store the AGI instance for later usage, kick off our operations"""
        self.agi = agi 
        return self.start()
    def start( self ):
        """Begin the dial-plan-like operations"""
        return self.agi.answer().addCallbacks( self.onAnswered, self.answerFailure )
    def answerFailure( self, reason ):
        """Deal with a failure to answer"""
        log.warn( 
            """Unable to answer channel %r: %s""", 
            self.agi.variables['agi_channel'], reason.getTraceback(),
        )
        self.agi.finish()
    def onAnswered( self, resultLine ):
        """We've managed to answer the channel, yay!"""
        self.count += 1
        return self.agi.wait( 2.0 ).addCallback( self.onWaited )
    def onWaited( self, result ):
        """We've finished waiting, tell the user the number"""
        return self.agi.sayNumber( self.count, '*' ).addErrback(
            self.onNumberFailed,
        ).addCallbacks(
            self.onFinished, self.onFinished,
        )
    def onFinished( self, resultLine ):
        """We said the number correctly, hang up on the user"""
        return self.agi.finish()
    def onNumberFailed( self, reason ):
        """We were unable to read the number to the user"""
        log.warn( 
            """Unable to read number to user on channel %r: %s""",
            self.agi.variables['agi_channel'], reason.getTraceback(),
        )

    def onHangupFailure( self, reason ):
        """Failed trying to hang up"""
        log.warn( 
            """Unable to hang up channel %r: %s""", 
            self.agi.variables['agi_channel'], reason.getTraceback(),
        )

if __name__ == "__main__":
    logging.basicConfig()
    fastagi.log.setLevel( logging.DEBUG )
    f = fastagi.FastAGIFactory(DialPlan())
    reactor.listenTCP(4573, f, 50, '10.0.0.167') # only binding on local interface
    reactor.run()
Hector R
  • 23
  • 5
  • Please provide source for MyIVRApplication() – jbreicis Feb 15 '13 at 11:38
  • As I said previously, please provide the source for MyIVRApplication. FastAGIFactory - is just that - a factory. If you provide it with the single insatnce of a class or function, there might be situations where you overwrite the original call protocol (agi) variable. Without looking at the implementation of your "MyIVRApplication()" I cannot tell. – jbreicis Feb 18 '13 at 07:33
  • Thank you jbreicis, sorry about the delay, but I did not see your comment (this is my first question). You are right, I noticed that when a new call comes in, all the agi variables get overwritten, including agi_channel and thus all the new commands are sent to this new channel. It looks as if only one instance is being created and like you said, variables get overwritten. – Hector R Feb 21 '13 at 02:02

1 Answers1

1

Thanks for full info:

Now the obvious flaw with the setup you have is as follows: Dialplan() is inititated when initially passed to FastAGIFactory. From there on - you are always accessing the same instance of a class over and over agian. Now - whenever a new call comes in Dialplan().__call__() method is called and Dialplan().agi paramater gets overwritten by the last call. Looking at the sample you used I would suggest tryiing following approach and then taking it from there:

#! /usr/bin/env python
"""Read digits from the user in various ways..."""
from twisted.internet import reactor, defer
from starpy import fastagi, error
import logging, time

log = logging.getLogger( 'hellofastagi' )




class DialPlan( object ):
    """Stupid little application to report how many times it's been accessed"""
    def __init__( self,application, agi ):
        self.application = application
        self.agi = agi
    def start( self ):
        """Begin the dial-plan-like operations"""
        return self.agi.answer().addCallbacks( self.onAnswered, self.answerFailure )
    def answerFailure( self, reason ):
        """Deal with a failure to answer"""
        log.warn( 
            """Unable to answer channel %r: %s""", 
            self.agi.variables['agi_channel'], reason.getTraceback(),
        )
        self.agi.finish()
    def onAnswered( self, resultLine ):
        """We've managed to answer the channel, yay!"""
        return self.agi.wait( 2.0 ).addCallback( self.onWaited )
    def onWaited( self, result ):
        """We've finished waiting, tell the user the number"""
        return self.agi.sayNumber( self.application.count, '*' ).addErrback(
            self.onNumberFailed,
        ).addCallbacks(
            self.onFinished, self.onFinished,
        )
    def onFinished( self, resultLine ):
        """We said the number correctly, hang up on the user"""
        return self.agi.finish()
    def onNumberFailed( self, reason ):
        """We were unable to read the number to the user"""
        log.warn( 
            """Unable to read number to user on channel %r: %s""",
            self.agi.variables['agi_channel'], reason.getTraceback(),
        )

    def onHangupFailure( self, reason ):
        """Failed trying to hang up"""
        log.warn( 
            """Unable to hang up channel %r: %s""", 
            self.agi.variables['agi_channel'], reason.getTraceback(),
        )


class CallCounterApplication(object):
    def __init__( self ):
        self.count = 0

    def __call__(self,agi):
        self.count = self.count+1
        dp = Dialplan(self,agi)
        return dp.start()


if __name__ == "__main__":
    logging.basicConfig()
    fastagi.log.setLevel( logging.DEBUG )
    f = fastagi.FastAGIFactory(CallCounterApplication())
    reactor.listenTCP(4573, f, 50, '10.0.0.167') # only binding on local interface
    reactor.run()

What I did here was created an application container which would create a new Dialplan() instance on every succesful call.

EDIT: Mind you - as I had no asterisk box available, this was not tested, and may not run when copypasted 1:1. But the principle remains the same

jbreicis
  • 588
  • 2
  • 8
  • Thank you very much! My problem was assuming that the Factory created a new instance every time a new call was received. Now using this approach, I create an "application Launcher" which in turn call the actual IVR application, effectively creating a new instance with every call, while allowing me to keep track of current connections and total connections thru the app launcher. Thanks again! – Hector R Feb 21 '13 at 14:45
  • We are using starpy intensively in our projects, and what we have done is ditched that Factory all togeather, creating a new one, which does exactly what you were referring to. Furthermore our Factory is extended in a way that when we make and AGI call with url://localhost:port/myApp the factory does automagic to locate myApp class in public AGICall classes and initiates that one. So I can have multiple number of different AGI scenarios without writing an endless if/elseif/elseif monkey code. – jbreicis Feb 22 '13 at 08:24
  • That sounds useful, I'm going to try to extend the Factory that way too. On some other note, a friend of mine who developed a Fastagi server using Java, told me that Linux has some kind of limit in the number of connections per port (he said 5 or 6 conns max) In your experience, can you tell me if this is true? How many simultaneous sessions can Asterisk open on a single port / Fastagi server? – Hector R Feb 22 '13 at 13:23
  • Right now, I am just deploying my first IVR app, and it's a small one (the client only has 4 analog trunks, so max calls are going to be 4) I might have no issue with this "connection limit per port", but I'm planning to offer this kind of solution (IVR on Asterisk with Python) to bigger clients, maybe handling 120 calls simultaneously, but I want to make sure that it is doable. Thanks again! You have been really helpful and cooperative! – Hector R Feb 22 '13 at 13:27
  • 5 to 6 connections? I dont know from where did he get that number frankly. We have FastAGI server who is serving 50+ concurrent calls. If you consider that Linux is used for number of use cases, not the least - most of web serving / irc networks/ almost anything is done on Linux variants, then those boxes can handle a lot, lot more than 5/6 concurrent connections – jbreicis Feb 25 '13 at 07:18