[Subversion] / FreeSwytch / README.txt  

View of /FreeSwytch/README.txt

Parent Directory | Revision Log
Revision: 2505 - (download)
Sun Mar 2 01:54:37 2008 UTC (16 years, 1 month ago) by pje
File size: 21843 byte(s)
Add channel objects, and attributes based on event fields.
=================================================
Telephony Monitoring and Control using FreeSwytch
=================================================

The ``freeswytch`` package provides a `Trellis`_-based interface to the
FreeSWITCH "event socket" API (`mod_event_socket`_), allowing you to monitor,
route, and control regular and conference calls, and implement telephony
applications.  It optionally supports using a Twisted ``reactor`` to connect to,
or listen to connections from, a FreeSWITCH instance.  (But you can implement
your own socket or other communications protocol(s), if you so desire.)

Please consult the complete `FreeSwytch developer's guide`_ for more details.
Questions, comments, and bug reports for this package should be directed to the
`PEAK mailing list`_.

.. _mod_event_socket: http://wiki.freeswitch.org/wiki/Event_Socket
.. _Trellis: http://pypi.python.org/pypi/Trellis
.. _Trellis tutorial: http://peak.telecommunity.com/DevCenter/Trellis

.. _FreeSwytch developer's guide: http://peak.telecommunity.com/DevCenter/FreeSwytch#toc
.. _PEAK mailing list: http://www.eby-sarna.com/mailman/listinfo/peak/

.. _toc:
.. contents: **Table of Contents**


-----------------
Developer's Guide
-----------------

Note: This guide assumes you're already familiar with the basics of using
`Trellis`_ objects, setting up observers, etc.  So you may want to review the
`Trellis tutorial`_ before continuing::

    >>> from peak.events import trellis

In addition, you should be at least somewhat familiar with FreeSWITCH's
`mod_event_socket`_ module, and the basic ideas of FreeSWITCH channels, events,
API commands, etc.


``Switch`` Objects
==================

A ``Switch`` object represents a connection to a FreeSWITCH instance::

    >>> from freeswytch import Switch

But you don't actually need to have an active connection to work with one::

    >>> switch = Switch()

This is especially handy for testing purposes.  You can simply observe the
Switch's ``transmit`` pipe to obtain strings that should be sent to the
FreeSWITCH instance::

    >>> @trellis.ObserverCell
    ... def show_output():
    ...     for data in switch.transmit:
    ...         while data:
    ...             print "transmit:", repr(data[:60])
    ...             data = data[60:]

    >>> def show_reply(reply):
    ...     print "got reply:", reply

    >>> switch.command('help', show_reply)
    transmit: 'help\n\n'

And you can use the Switch's ``receivedBytes()`` method to simulate incoming
data::

    >>> switch.receivedBytes("""Content-Type: command/reply
    ... Reply-Text: -ERR command not found\n\n""")
    got reply: -ERR command not found


Commands, Callbacks, and Messages
---------------------------------

When commands are transmitted, any callback associated with the command is
queued in the switch's ``.commands_sent`` list::

    >>> switch.command('exit', show_reply)
    transmit: 'exit\n\n'

    >>> switch.commands_sent
    [<function show_reply at ...>]

These callbacks will remain queued, until they are removed by the arrival of a
corresponding reply message.  ``Switch`` instances automatically parse incoming
bytes (from ``receivedBytes()`` calls) into FreeSWITCH messages, and pass these
messages to the switch's ``receivedMessage()`` method.  You can also call this
method directly, and the result is the same::

    >>> switch.receivedMessage('api/response', {}, '+OK')
    got reply: +OK

    >>> switch.commands_sent
    []

(Note: when you are using this library normally, the FreeSWITCH event server
will automatically reply to each command you send.  However, since our example
switch isn't actually connected to a server, we'll be manually clearing these
callbacks throughout the guide examples, using the ``switch.callback(result)``
method.  Please keep in mind that you should NOT do this in application code!
It's strictly for testing/demonstration purposes.)


Message Processing (``receivedMessage()``)
------------------------------------------

The ``receivedMessage()`` method takes 1 to 3 parameters: the content-type of
the message, an optional dictionary of header values, and an optional body
string (defaulting to empty).  The content-type is looked up in a handler
dictionary, and an appropriate action taken.  For example, the
``command/reply`` content type invokes the switch's ``.callback()`` method on
the ``Reply-Text`` header of the message::

    >>> switch.command('help', show_reply)
    transmit: 'help\n\n'

    >>> switch.callback('-ERR command not found')
    got reply: -ERR command not found

(The ``api/response`` content-type is similar, except that the message body is
passed to ``.callback()`` instead.)

Other content types are interpreted as follows:

``auth/request``
    The switch's ``.auth_requested`` flag is set, triggering an authentication
    sequence.  See the `Authentication and Credentials`_ section below for more
    details.

``text/event-plain``
    The message's body is parsed as a FreeSWITCH event, and added to the
    switch's ``.events`` pipe.  See the `Events and Event Data`_ section below
    for more details.

``log/data``
    The message's body is sent to the switch's ``.log_data`` pipe.  See the
    `FreeSWITCH Log Data`_ section below for more details.

If a message's content-type isn't recognized, it results in a 3-item tuple
being sent to the switch's ``.messages`` pipe::

    >>> @trellis.ObserverCell
    ... def print_messages():
    ...     for msg in switch.messages:
    ...         print "message:", msg

    >>> switch.receivedMessage('unknown/wtf?', {'A':'b'}, 'c')
    message: ('unknown/wtf?', {'A': 'b'}, 'c')


Commands, Headers, and Asynchronous API Calls
---------------------------------------------

The ``.command()`` method transmits a command message to FreeSWITCH, accepting
both an optional callback function for the reply, and an arbitrary number of
extra message headers.  In this example, we'll send a multi-header message,
without using a callback (i.e., using ``None`` as the callback parameter)::

    >>> switch.command("SendMsg 123-456-789", None,
    ...     ('call-command', 'execute'),
    ...     ('execute-app-name', 'transfer'),
    ...     ('execute-app-arg', 'conf217')
    ... )
    transmit: 'SendMsg 123-456-789\ncall-command: execute\nexecute-app-name: '
    transmit: 'transfer\nexecute-app-arg: conf217\n\n'

    >>> switch.callback('+OK')  # clear the pending command

The ``.api()`` method transmits a ``bgapi`` command message -- a background
(i.e. asynchronous) API call.  The switch's ``.background_jobs`` dictionary
tracks outstanding job callbacks by UUID, so that they can fire if/when a
response event is received::

    >>> def process_list(result):
    ...     print "conference list:"
    ...     print result

    >>> switch.api('conference conf217 list', process_list)
    transmit: 'bgapi conference conf217 list\n\n'

    >>> switch.background_jobs
    {}

    >>> switch.callback('+OK Job-UUID: 12345-6789-101112')
    >>> switch.background_jobs
    {'12345-6789-101112': <function process_list at ...>}

    >>> switch.events.append({
    ...     'Event-Name': 'BACKGROUND_JOB',
    ...     'Job-UUID': '12345-6789-101112',
    ...     'body': 'sample data',
    ... })
    conference list:
    sample data

    >>> switch.background_jobs
    {}

Note, by the way, that none of these operations provide any timeout facilities,
so you may want to use Trellis time operators in your rules if you want to take
action in the event you don't receive a callback within a certain amount of
time.


Authentication and Credentials
------------------------------

When a switch instance is first created, its ``.is_ready`` attribute is false,
meaning that authentication hasn't occurred yet::

    >>> Switch().is_ready
    False

You should wait until ``.is_ready`` is true, before sending any commands.  (You
can, however, change the events to be monitored or the desired log level; the
switch will not send out commands to change them while not in the ``is_ready``
state.)

Whenever a message with content type ``auth/request`` is received from
FreeSWITCH (which normally happens immediately upon connection), the switch's
``.auth_requested`` attribute is temporarily set to true, causing the switch's
``.credential`` to be transmitted in an ``auth`` command to the server::

    >>> @trellis.ObserverCell
    ... def print_auth():
    ...     if switch.auth_requested:
    ...         print "auth_requested"

    >>> switch.credential = 'super-sekret-password'

    >>> switch.receivedMessage('auth/request')
    auth_requested
    transmit: 'auth super-sekret-password\n\n'

    >>> switch.is_ready
    False

If authentication fails, the response will show up in the switch's ``.errors``
pipe, and ``.is_ready`` will remain false::

    >>> @trellis.ObserverCell
    ... def print_errors():
    ...     for e in switch.errors:
    ...         print "error:", e

    >>> switch.receivedMessage('command/reply',
    ...     {'Reply-Text': '-ERR Invalid password'}
    ... )
    error: -ERR Invalid password

    >>> switch.is_ready
    False

But if the authentication is successful, the switch will reinitialize itself by
activating event monitoring (just ``BACKGROUND_JOB`` events by default), by
transmitting any set log level, and setting ``is_ready`` to true::

    >>> switch.auth_requested = True
    auth_requested
    transmit: 'auth super-sekret-password\n\n'

    >>> switch.receivedMessage('command/reply', {'Reply-Text': '+OK accepted'})
    transmit: 'nolog\n\n'
    transmit: 'noevents\n\n'
    transmit: 'event plain BACKGROUND_JOB\n\n'

    >>> switch.callback('')     # reply to the three commands just sent
    >>> switch.callback('')
    >>> switch.callback('')

    >>> switch.is_ready
    True

Now, the switch instance is ready to accept commands, and to monitor or
manipulate channels, conferences, etc.


Events and Event Data
---------------------

By default, switch objects only monitor ``BACKGROUND_JOB`` events, and the
``.watch_events`` set will be empty::

    >>> switch.watch_events
    Set([])

You can add or remove event types from this set, as you wish, and the switch
will change the FreeSWITCH event settings accordingly::

    >>> switch.watch_events.add('ALL')
    transmit: 'noevents\n\n'
    transmit: 'event plain ALL\n\n'

    >>> switch.callback('')     # clear the two commands just issued
    >>> switch.callback('')


When FreeSWITCH sends an event to the switch object, it arrives as a message of
content-type ``text/event-plain``.  Switch objects turn these messages into
dictionaries, which are then broadcast to the switch object's ``.events`` pipe
for other objects to observe and respond to::

    >>> @trellis.ObserverCell
    ... def print_events():
    ...     for evt in switch.events:
    ...         print "event:", evt

    >>> switch.receivedBytes("""\
    ... Content-Length: 497
    ... Content-Type: text/event-plain
    ...
    ... Channel-State: CS_INIT
    ... Channel-State-Number: 1
    ... Channel-Name: DingaLing/1000
    ... Unique-ID: 7401d446-578f-4e87-a400-390e3f3583cf
    ... variable_dl_from_host: gmail.com
    ... variable_dl_from_user: dirtsimple
    ... Event-Name: CHANNEL_CREATE
    ... Core-UUID: 8fa959a0-7e83-41fa-837c-707be5066f90
    ... Event-Date-Local: 2007-03-11%2016%3A30%3A23
    ... Event-Date-GMT: Sun,%2011%20Mar%202007%2020%3A30%3A23%20GMT
    ... Event-Calling-File: switch_channel.c
    ... Event-Calling-Function: switch_channel_set_caller_profile
    ... Event-Calling-Line-Number: 771\n\n""")
    event: {'Channel-State': 'CS_INIT',
            'Event-Date-Local': '2007-03-11 16:30:23',
            'Event-Calling-Line-Number': '771',
            'Core-UUID': '8fa959a0-7e83-41fa-837c-707be5066f90',
            'Event-Date-GMT': 'Sun, 11 Mar 2007 20:30:23 GMT',
            'Event-Name': 'CHANNEL_CREATE',
            'variable_dl_from_user': 'dirtsimple',
            'variable_dl_from_host': 'gmail.com',
            'Unique-ID': '7401d446-578f-4e87-a400-390e3f3583cf',
            'Event-Calling-File': 'switch_channel.c',
            'Channel-State-Number': '1',
            'Event-Calling-Function': 'switch_channel_set_caller_profile',
            'Channel-Name': 'DingaLing/1000'}

If the event has a body, it's placed in the dictionary under the key ``body``::

    >>> switch.receivedBytes("""\
    ... Content-Length: 378
    ... Content-Type: text/event-plain
    ...
    ... Job-UUID: c8e05157-e695-4918-a5bb-14d9b72ddcd5
    ... Event-Name: BACKGROUND_JOB
    ... Core-UUID: 481c09c6-e972-4a0c-9be6-6591fc63ac46
    ... Event-Date-Local: 2008-03-01%2013%3A42%3A06
    ... Event-Date-GMT: Sat,%2001%20Mar%202008%2018%3A42%3A06%20GMT
    ... Event-Calling-File: mod_event_socket.c
    ... Event-Calling-Function: api_exec
    ... Event-Calling-Line-Number: 555
    ... Content-Length: 29
    ...
    ... Conference conf217 not found\n""")
    event: {'body': 'Conference conf217 not found\n',
            'Content-Length': '29',
            'Event-Date-Local': '2008-03-01 13:42:06',
            'Event-Calling-Line-Number': '555',
            'Core-UUID': '481c09c6-e972-4a0c-9be6-6591fc63ac46',
            'Event-Date-GMT': 'Sat, 01 Mar 2008 18:42:06 GMT',
            'Event-Name': 'BACKGROUND_JOB',
            'Job-UUID': 'c8e05157-e695-4918-a5bb-14d9b72ddcd5',
            'Event-Calling-File': 'mod_event_socket.c',
            'Event-Calling-Function': 'api_exec'}


FreeSWITCH Log Data
-------------------

If you want to receive FreeSWITCH log data from a switch object, you can set
its ``.log_level`` attribute to determine the level of detail that FreeSWITCH
will send::

    >>> switch.log_level = 42
    transmit: 'log 42\n\n'
    >>> switch.callback('+OK')

    >>> switch.log_level = 0
    transmit: 'log 0\n\n'
    >>> switch.callback('+OK')

Setting the level to ``None`` (the default) will turn off FreeSWITCH logging::

    >>> switch.log_level = None
    transmit: 'nolog\n\n'
    >>> switch.callback('+OK')

Log data sent by FreeSWITCH will be sent to the switch's ``.log_data`` pipe,
in response to messages of content-type ``log/data``::

    >>> @trellis.ObserverCell
    ... def print_log():
    ...     for log in switch.log_data:
    ...         print "log:", log

    >>> switch.receivedBytes("""\
    ... Content-Type: log/data
    ... Content-Length: 100
    ...
    ... 2008-03-01 13:50:50 [DEBUG] switch_core.c:\
    ... 3028 switch_core_standard_on_loopback() Standard LOOPBACK\n""")
    log: 2008-03-01 13:50:50 [DEBUG] switch_core.c:3028
         switch_core_standard_on_loopback() Standard LOOPBACK


``Channel`` Objects
===================

Switch objects keep track of the set of active communications channels, in
their ``.channels`` set::

    >>> switch.channels
    Set([Channel(key='7401d446-578f-4e87-a400-390e3f3583cf',
                 user=None, name=None, number=None)])

(The channel shown here exists because of a channel-related event we processed
in an earlier section.  Also, note that since the ``.channels`` set is a
trellis ``Set`` object, you can define rules that "notice" when channels have
been created or destroyed.)

If you want to create or access a specific channel, rather than the set of
active channels, you can do so using the ``Channel(switch, uuid)``
constructor::

    >>> from freeswytch import Channel
    >>> c=Channel(switch, '7401d446-578f-4e87-a400-390e3f3583cf')
    >>> c
    Channel(key='7401d446-578f-4e87-a400-390e3f3583cf',
            user=None, name=None, number=None)


Channel Events
--------------

When the switch processes events related to a channel, they are passed on to
the channel's ``.events`` pipe::

    >>> @trellis.ObserverCell
    ... def print_events():
    ...     for evt in c.events:
    ...         print "channel event:", evt

    >>> switch.events.append({
    ...     'Caller-Caller-ID-Name': 'example@gmail.com/Talk.v1044955BB6F',
    ...     'Channel-State': 'CS_INIT',
    ...     'Event-Name': 'CODEC',
    ...     'Unique-ID': '7401d446-578f-4e87-a400-390e3f3583cf',
    ...     'Caller-Caller-ID-Number': 'example@gmail.com/Talk.v1044955BB6F',
    ...     'Caller-Destination-Number': '1000',
    ...     'Caller-Username': 'example@jabber.org/talk'
    ... })
    channel event: {'Channel-State': 'CS_INIT',
        'Caller-Caller-ID-Number': 'example@gmail.com/Talk.v1044955BB6F',
        'Caller-Username': 'example@jabber.org/talk',
        'Event-Name': 'CODEC', 'Caller-Destination-Number': '1000',
        'Unique-ID': '7401d446-578f-4e87-a400-390e3f3583cf',
        'Caller-Caller-ID-Name': 'example@gmail.com/Talk.v1044955BB6F'}


Channel Attributes
------------------

When channel events occur, the associated channel object attributes are
updated::

    >>> c
    Channel(key='7401d446-578f-4e87-a400-390e3f3583cf',
            user='example@jabber.org/talk',
            name='example@gmail.com/Talk.v1044955BB6F',
            number='example@gmail.com/Talk.v1044955BB6F')

    >>> c.user          # Caller-Username
    'example@jabber.org/talk'

    >>> c.name          # Caller-Caller-ID-Name
    'example@gmail.com/Talk.v1044955BB6F'

    >>> c.number        # Caller-Caller-ID-Number
    'example@gmail.com/Talk.v1044955BB6F'

    >>> c.state         # Channel-State
    'CS_INIT'

    >>> c.destination   # Caller-Destination-Number
    '1000'

These attributes are stored in cells, so you can observe them with trellis
rules.  (Keep in mind that any or all of them may be ``None`` while a channel
is being initialized.)

In addition to the above attributes, the ``key`` and ``owner`` attributes give
the channel's UUID and switch, respectively::

    >>> c.key
    '7401d446-578f-4e87-a400-390e3f3583cf'

    >>> c.owner is switch
    True

These attributes are not stored in cells, as they are constant for the life of
a ``Channel``.


Channel Commands
----------------

The ``.command()`` method of a channel object is used to send FreeSWITCH
messages to it.  It takes exactly the same arguments as the ``.command()``
method of switch objects, but sends the message using the ``SendMsg`` command,
as a ``call-command``::

    >>> c.command('X')
    transmit: 'SendMsg 7401d446-578f-4e87-a400-390e3f3583cf\ncall-command: X'
    transmit: '\n\n'

    >>> switch.callback('')     # clear the pending command

In practice, however, it will generally be more useful to call the ``hangup()``
or ``execute()`` methods, rather than using ``command()`` directly.

The ``hangup()`` method takes two optional arguments: a ``cause`` code that
defaults to ``"NORMAL_CLEARING"``, and a callback for the command response::

    >>> c.hangup('NO_ANSWER', callback=show_reply)
    transmit: 'SendMsg 7401d446-578f-4e87-a400-390e3f3583cf\ncall-command: h'
    transmit: 'angup\nhangup-cause: NO_ANSWER\n\n'

    >>> switch.callback('+OK')     # clear the pending command
    got reply: +OK

The ``execute()`` method has the signature ``(app, data=None, times=1,
callback=None)``, where ``app`` is the FreeSWITCH application name, data is
an optional argument to that application, ``times`` specified how many times
the action should be repeated, and ``callback`` is an optional callback for
the command response.  A few examples::

    >>> c.execute('transfer', 'conf217')
    transmit: 'SendMsg 7401d446-578f-4e87-a400-390e3f3583cf\ncall-command: e'
    transmit: 'xecute\nexecute-app-name: transfer\nexecute-app-arg: conf217\n\n'

    >>> switch.callback('+OK')     # clear the pending command


    >>> c.execute('playback', 'beep.wav', 3, callback=show_reply)
    transmit: 'SendMsg 7401d446-578f-4e87-a400-390e3f3583cf\ncall-command: e'
    transmit: 'xecute\nexecute-app-name: playback\nexecute-app-arg: beep.wav\n'
    transmit: 'loops: 3\n\n'

    >>> switch.callback('+OK')     # clear the pending command
    got reply: +OK


    >>> c.execute('hold')
    transmit: 'SendMsg 7401d446-578f-4e87-a400-390e3f3583cf\ncall-command: e'
    transmit: 'xecute\nexecute-app-name: hold\n\n'

    >>> switch.callback('+OK')     # clear the pending command


Channel Shutdown
----------------

Channel objects have a ``.closed`` attribute which is normally False, but is
set to some other value when the channel (or its owning switch) is closed.  For
example, if a ``CHANNEL_DESTROY`` event occurs::

    >>> c.closed
    False

    >>> switch.events.append({
    ...     'Channel-State': 'CS_HANGUP',
    ...     'Event-Name': 'CHANNEL_DESTROY',
    ...     'Unique-ID': '7401d446-578f-4e87-a400-390e3f3583cf',
    ...     'variable_hangup_cause': 'NORMAL_CLEARING',
    ... })
    channel event: {'Channel-State': 'CS_HANGUP',
                    'Unique-ID': '7401d446-578f-4e87-a400-390e3f3583cf',
                    'Event-Name': 'CHANNEL_DESTROY',
                    'variable_hangup_cause': 'NORMAL_CLEARING'}

    >>> c.closed
    'NORMAL_CLEARING'

And, at the end of the trellis recalculation where the event occurred, the
channel is detached from its switch, and removed from the ``.channels`` set::

    >>> switch.channels
    Set([])

If there are any outstanding references to the channel, it will of course still
exist, but it will not receive any further events from the switch.
    

``Conference`` Objects
======================

Switch.conferences


``Member`` Objects
==================

Conference.members


Connecting To A FreeSWITCH Instance
===================================

``.connect()``

``.close()``, ``.closed``



Application Integration
=======================

EventHandler class - ``.event_attributes``, ``.ANY_EVENT()``, handler methods,
``.close()`` method

Child class


Command-Line Clients
====================





-------------------
Internals and Tests
-------------------

* test receivedData edge cases, loops
* test unrecognized job on callback
* test erroneous bgapi response


cvs-admin@eby-sarna.com

Powered by ViewCVS 1.0-dev

ViewCVS and CVS Help