- Asterisk Bridges
- Bridges in a Stasis Application
- Bridge Types
- Subscription Model
- Example: Interacting with Bridges
- Dialplan
- Python
- bridge-hold.py
- bridge-hold.py in action
- JavaScript (Node.js)
- bridge-hold.js
- bridge-hold.js in action
- ari 0.1.3
- Usage
- API
- Making REST calls
- Registering event callbacks
- Object lifetime
- Caveats
- Examples
- Development
- TODO
- License
Asterisk Bridges
In Asterisk, bridges can be thought of as a container for channels that form paths of communication between the channels contained within them. They can be used to pass media back and forth between the channels, as well as to play media to the various channels in a variety of ways.
For more information on bridges in Asterisk, see Bridges.
Bridges in a Stasis Application
Bridge Types
When a bridge is created through ARI, there are a number of attributes that can be specified that determine how the bridge mixes media between its participants. These include:
- mixing — specify that media should be passed between all channels in the bridge. This attribute cannot be used with holding .
- dtmf_events — specify that media should be decoded within Asterisk so that DTMF can be recognized. If this is not specified, then DTMF events may not be raised due to the media being passed directly between the channels in the bridge. This attribute only impacts how media is mixed when the mixing attribute is used.
- proxy_media — specify that media should always go through Asterisk, even if it could be redirected between clients. This attribute only impacts how media is mixed when the mixing attribute is used.
- holding — specify that the channels in the bridge should be entertained with some media. Channels in the bridge have two possible roles: a participant or an announcer. Media between participant channels is not shared; media from an announcer channel is played to all participant channels.
Depending on the combination of attributes selected when a bridge is created, different mixing technologies may be used for the participants in the bridge. Asterisk will attempt to use the most performant mixing technology that it can based on the channel types in the bridge, subject to the attributes specified when the bridge was created.
Subscription Model
Unlike channels, bridges in a Stasis application are not automatically subscribed to for events. In order to receive events concerning events for a given bridge, the applications resource must be used to subscribe to the bridge via the POST — /applications//subscription operation. Events related to channels entering and leaving bridges will be sent without the need to subscribe to them since they are related to a channel in a Stasis application.
Example: Interacting with Bridges
For this example, we’re going to write an ARI application that will do the following:
- When it connects, it will print out the names of all existing holding bridges. If there are no existing holding bridges, it will create one.
- When a channel enters into its Stasis application, it will be added to the holding bridge and music on hold will be started on the bridge.
Dialplan
The dialplan for this will be very straight forward: a simple extension that drops a channel into Stasis.
Python
For our Python examples, we will rely primarily on the ari-py library. Because the ari library will emit useful information using Python logging, we should go ahead and set that up as well — for now, a basicConfig with ERROR messages displayed should be sufficient. Finally, we’ll need to get a client made by initiating a connection to Asterisk. This occurs using the ari.connect method, where we have to specify three things:
- The HTTP base URI of the Asterisk server to connect to. Here, we assume that this is running on the same machine as the script, and that we’re using the default port for Asterisk’s HTTP server — 8088 .
- The username of the ARI user account to connect as. In this case, we’re specifying it as asterisk .
- The password for the ARI user account. In this case, that’s asterisk.
Modify the connection credentials as appropriate for your server, although many examples will use these credentials.
Please don’t use these credentials in production systems!
Once we’ve made our connection, our first task is to look for an existing holding bridge — if there is no existing holding bridge — we need to create it. The bridges resource has an operation for listing existing bridges — GET /bridges . Using ari-py we need to use the operation nickname — list . We can then use another bridges resource operation to create a holding bridge if none was found — POST /bridges . Using ari-py, we need to use the operation nickname — create .
The GET /channels operation returns back a list of Bridge resources. Those resources, however, are returned as JSON from the operation, and while the ari-py library converts the uniqueid of those into an attribute on the object, it leaves the rest of them in the JSON dictionary.
Our next step involves adding channels that enter our Stasis application to the bridge we either found or created and signaling when a channel leaves our Stasis application. To do that, we need to subscribe for the StasisStart and StasisEnd events:
We need two handler functions — stasis_start_cb for the StasisStart event and stasis_end_cb for the StasisEnd event:
Finally, we need to tell the client to run our application. Once we call client.run , the websocket connection will be made and our application will wait on events infinitely. We can use Ctrl+C to kill it and break the connection.
bridge-hold.py
The full source code for bridge-hold.py is shown below:
bridge-hold.py in action
Here, we see the output from the bridge-hold.py script when a PJSIP channel for endpoint ‘alice’ enters into the application:
JavaScript (Node.js)
For our JavaScript examples, we will rely primarily on the Node.js ari-client library. We’ll need to get a client made by initiating a connection to Asterisk. This occurs using the ari.connect method, where we have to specify four things:
- The HTTP base URI of the Asterisk server to connect to. Here, we assume that this is running on the same machine as the script, and that we’re using the default port for Asterisk’s HTTP server — 8088 .
- The username of the ARI user account to connect as. In this case, we’re specifying it as asterisk .
- The password for the ARI user account. In this case, that’s asterisk.
- A callback that will be called with an error if one occurred, followed by an instance of an ARI client.
Modify the connection credentials as appropriate for your server, although many examples will use these credentials.
Please don’t use these credentials in production systems!
Once we’ve made our connection, our first task is to look for an existing holding bridge — if there is no existing holding bridge — we need to create it. The bridges resource has an operation for listing existing bridges — GET /bridges . Using ari-client we need to use the operation nickname — list . We can then use another bridges resource operation to create a holding bridge if none was found — POST /bridges . Using ari-client, we need to use the operation nickname — create .
The GET /channels operation returns back a an error if it occurred and a list of Bridge resources. ari-client will return a JavaScript object for each Bridge resource. Properties such as bridge_type can be accessed on the object directly.
Our next step involves adding channels that enter our Stasis application to the bridge we either found or created and signaling when a channel leaves our Stasis application. To do that, we need to subscribe for the StasisStart and StasisEnd events:
We need two callback functions — stasisStart for the StasisStart event and stasisEnd for the StasisEnd event:
Finally, we need to tell the client to start our application. Once we call client.start , a websocket connection will be established and the client will emit Node.js events as events come in through the websocket. We can use Ctrl+C to kill it and break the connection.
bridge-hold.js
The full source code for bridge-hold.js is shown below:
bridge-hold.js in action
Here, we see the output from the bridge-hold.js script when a PJSIP channel for endpoint ‘alice’ enters into the application:
ari 0.1.3
This package contains the Python client library for the Asterisk REST Interface. It builds upon the Swagger.py library, providing an improved, Asterisk-specific API over the API generated by Swagger.py
Usage
Install from source using the setup.py script.
API
An ARI client can be created simply by the ari.connect method. This will create a client based on the Swagger API downloaded from Asterisk.
The API is modeled into the Repository Pattern, as you would find in Domain Driven Design. Each Swagger Resource (a.k.a. API declaration) is mapped into a Repository object, which is provided as a field on the client ( client.channels , client.bridges ).
Responses from Asterisk are mapped into first-class objects, akin to Domain Objects in the Repository Pattern. These are provided both on the responses to RESTful API calls, and for fields from events received over the WebSocket.
Making REST calls
Each Repository Object provides methods which invoke the non-instance specific operations of the associated Swagger resource ( bridges.list() , channels.get() ). Instance specific methods are also provided, which require identity parameters to be passed along ( channels.get(channelId=id) ).
Instance specific methods are also provided on the Domain Objects ( some_channel.hangup() ).
Registering event callbacks
Asterisk may send asyncronous messages over a WebSocket to indicate events of interest to the application.
The Client object has an on_event method, which can be used to subscribe for specific events from Asterisk.
The first-class objects also have ‘on_event’ methods, which can subscribe to Stasis events relating to that object.
Object lifetime
The Repository Objects exist for the lifetime of the client that owns them.
Domain Objects are ephemeral, and not tied to the lifetime of the underlying object in Asterisk. Pratically, this means that if you call channels.get(‘1234’) several times, you may get a different object back every time.
You may hold onto an instance of a Domain Object, but you should consider it to be stale. The data contained in the object may be out of date, but the methods on the object should still behave properly.
If you invoke a method on a stale Domain Object that no longer exists in Asterisk, you will get a HTTPError exception (404 Not Found).
Caveats
The dynamic methods exposed by Repository and Domain objects are, effectively, remote procedure calls. The current implementation is synchronous, which means that if anything were to happen to slow responses (slow network, packet loss, system load, etc.), then the entire application could be affected.
Examples
Development
The code is documented using Sphinx, which allows IntelliJ IDEA to do a better job at inferring types for autocompletion.
To keep things isolated, I also recommend installing (and using) virtualenv.
$ sudo pip install virtualenv $ mkdir -p ~/virtualenv $ virtualenv ~/virtualenv/ari $ . ~/virtualenv/ari/bin/activate
Setuptools is used for building. Nose is used for unit testing, with the coverage plugin installed to generated code coverage reports. Pass —with-coverage to generate the code coverage report. HTML versions of the reports are put in cover/index.html .
$ ./setup.py develop # prep for development (install deps, launchers, etc.) $ ./setup.py nosetests # run unit tests $ ./setup.py bdist_egg # build distributable
TODO
License
Copyright (c) 2013, Digium, Inc. All rights reserved.
Swagger.py is licensed with a BSD 3-Clause License.