Question

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()
Was it helpful?

Solution

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

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top