O'Reilly Emerging Telephony

oreilly.comSafari Books Online.Conferences.
advertisement
MySQL Conference and Expo April 14-17, 2008, Santa Clara, CA
AddThis Social Bookmark Button

Print Subscribe to Telephony Subscribe to Newsletters

Fast Prototyping of Telephony Applications with YATE
Pages: 1, 2, 3

Yet Another YATE Python Module (YAYPM)

YAYPM is a Python external protocol client. Its API is built around one concept--Deferred--that comes from the Twisted framework (the Twisted project is very well-documented. Check out the Twisted home page for in-depth explanations of Deferred and its asynchronous programming model). For now, it is enough to say that Deferred is a promise to carry out some process when a specified event occurs. The process that executes when that event occurs is determined by success and failure callback functions. YAYPM allows you to create Deferreds that fire when specific YATE messages are delivered or sent to and from YATE.



A Trivial Example

Let's start by writing a toy IVR application that will echo DTMF events to the caller. YateClientProtocolFactory will create a TCPDispatcher instance and instruct it to start the route function upon successful connection to the YATE server:

f = TCPDispatcherFactory(route)
reactor.connectTCP("localhost", 5039, f)

The route function registers the on_route function as a handler for call.route messages whose called attribute is ivr:

def route(yate):
    def on_route(route):
        ...

    yate.onmsg("call.route",
        lambda m : m["called"] == "ivr").addCallback(on_route)

The onmsg method of a YateClientProtocol object creates Deferreds whose callbacks will be fired when a message comes from YATE:

def onmsg(self, name, guard = lambda _: True, until = None, autoreturn = False):

Where:

name
    The name of YATE message to fire Deferred on. 
guard
    One argument Boolean function that defines additional conditions
    that message must meet in order to trigger the Deferred.
until
    A program that calls a function typically does not want to wait
    for an event forever.  The until parameter allows one to specify
    another Deferred which, when fired, will cancel the wait.
autoreturn
    Kind of syntactic sugar--when set to true before firing. 
callbacks
    Message will be returned automatically.

So, for example:

...
d = yate.onmsg("call.route",
    lambda m: m["called"] == "ivr")
...

creates a Deferred that will fire its callback when the script receives call.route messages whose called attribute is set to ivr, and:

...
end = yate.onmsg("chan.hangup",
    lambda m : m["id"] == "SIP/1", autoreturn = True)

d = yate.onmsg("call.answered",
    lambda m : m["targetid"] == "SIP/1", until = end)
...

creates a Deferred that will wait for a call.answered message with targetid set to SIP/1 (which is the YATE channel's ID) until call termination, i.e., the arrival of a chan.hangup message with id set to SIP/1.

Now, let us look at the on_route handler. After telling the incoming call to connect to DumbChannel by returning the call.route message with dumb/, it registers a watch for call.execute that comes from the same channel as call.route. The difference between onwatch and onmsg is that a Deferred created with onwatch fires after some other handler performs the actual processing of the message. In this case, the dumbchan module will process the call.execute message. Then, since the script is only interested in the fact that somebody processed the call.execute, the onwatch Deferred will fire:

    def on_route(route):
        yate.ret(route, True, "dumb/")

        def on_execute(execute):
            ...

        execute = yate.onwatch("call.execute",
            lambda m : m["id"] == route["id"])
        execute.addCallback(on_execute)
        yate.onmsg("call.route").addCallback(on_route)

Since Deferreds are a one-shot deal, in the end, another Deferred for call.route must be created in order to handle another call in the future.

The on_execute function sends call.answered message to confirm that a call has been answered and connected to DumbChannel.

DumbChannel provides CallEndpoints that have empty DataEndpoints. This allows other modules to attach their data sources and consumers to it. The on_dtmf function uses this feature by attaching a wave file DataSource on every dtmf event:

        def on_execute(execute):
            yate.msg("call.answered",
                     {"id": execute["targetid"],
                      "targetid": execute["id"]}).enqueue()
            print "Call %s answered." % callid
            def on_dtmf(dtmf):
                print "Dtmf %s received." % dtmf["text"]
                yate.msg("chan.masquerade",
                    {"message" : "chan.attach",                    
                     "id": dtmf["targetid"],
                     "source": "wave/play/./sounds/digits/pl/%s.gsm" % \
                     dtmf["text"]}).enqueue()
                yate.onmsg("chan.dtmf",
                    lambda m : m["id"] == dtmf["id"]).addCallback(on_dtmf)
                dtmf.ret(True)
            dtmf = yate.onmsg("chan.dtmf",
                lambda m : m["id"] == execute["id"])
            dtmf.addCallback(on_dtmf)

The use of chan.masquerade above requires further explanation. In response to chan.masquerade, the receiving channel (the dumb channel, in this case) inserts its CallEndPoint and resends the message specified in the message attribute. chan.masquerade is a bit of a trick that allows you to manipulate native endpoints without actually touching them.

Flow

Although Deferreds provide a cleaner asynchronous programming model than bare event loops, programs composed of a large number of small callbacks are hard to read and write. The YAYPM flow module uses Python generators to connect callbacks of different Deferreds into a single control flow:

def doSomething():
    ...
    yield yate.onmsg("chan.dtmf", lambda m : m["id"] == callid)
    dtmf = flow.getResult()
    #do something with dtmf
    ...

The keyword yield turns doSomething into a generator. yielding a Deferred will suspend the generator's execution until the Deferred is triggered. Then the generator resumes execution and flow.getResult delivers the return value of the callback that was yielded. In the case of an errback being triggered, flow.getResult will raise an exception. To run this new flow generator, you must use flow.go. It returns a Deferred that is fired when the generator returns:

    ...
    yield go(doSomething())
    print getResult()
    ...

Sidenote

Using generators in this manner, as co-routines, is probably against generator designers' intentions. This will change as of Python version 2.5, when it will be possible to access return values when execution is resumed:

      ...
      dtmf = yield yate.onmsg(
          "chan.dtmf", lambda m : m["id"] == callid)
      ...

It will also remove the ugly global variable that is hidden below getResult.

Pages: 1, 2, 3

Next Pagearrow




Search Emerging Telephony

Search

Tagged Articles

Post to del.icio.us

This article has been tagged:

voip

Articles that share the tag voip:

Asterisk: A Bare-Bones VoIP Example (110 tags)

VoIP and POTS Integration with Asterisk (47 tags)

The PBX Is Dead; Long Live VoIP (42 tags)

Building Your Own Teleconference System with Asterisk and Gizmo (28 tags)

Top Ten Questions People Ask About Switching to Internet Telephones (23 tags)

View All

programming

Articles that share the tag programming:

Rolling with Ruby on Rails (1374 tags)

Very Dynamic Web Interfaces (279 tags)

Ajax on Rails (231 tags)

Understanding MVC in PHP (202 tags)

A Simpler Ajax Path (186 tags)

View All

telephony

Articles that share the tag telephony:

Asterisk: A Bare-Bones VoIP Example (36 tags)

The PBX Is Dead; Long Live VoIP (12 tags)

Top Ten Questions People Ask About Switching to Internet Telephones (9 tags)

Building Advanced Telecom Apps on a Shoestring (8 tags)

What Is Skype (4 tags)

View All

Sponsored Resources

  • Inside Lightroom
Advertisement
Sign up today to receive special discounts,
product alerts, and news from O'Reilly.
Privacy Policy >
View Sample Newsletter >
  • Youtube
  • http://www.youtube.com/OreillyMedia
  • Twitter
  • Subscribe
  • View All RSS Feeds >
O'Reilly Media

800-889-8969 or 707-827-7019
Monday-Friday 7:30am-5pm PT
©2011, O'Reilly Media, Inc.
All trademarks and registered trademarks appearing on oreilly.com are the property of their respective owners.
  • About O'Reilly
  • Academic Solutions
  • Contacts
  • Customer Service
  • Careers
  • Press Room
  • Privacy Policy
  • Terms of Service
  • Writing for O'Reilly
  • Community
  • Authors
  • Forums
  • Membership
  • Newsletters
  • RSS Feeds
  • User Groups
  • Partner Sites
  • makezine.com
  • makerfaire.com
  • craftzine.com
  • igniteshow.com
  • PayPal Developer Zone
  • O'Reilly Insights on Forbes.com