以前我们讨论了ARI接口对DTMF事件的处理,其实ARI也支持对通道方面的处理方式,否则ARI就不能成为一个未来Asterisk接口通信的主要方式。Asterisk通过ARI接口可以获得各种通道的状态信息,然后通过ARI对其进行管理控制,按照用户的业务需求做进一步的处理。首先我们简单说明一下什么是通道和其状态的含义。通道状态是反映当前介于Asterisk和终端设备之间的通信介质的状态。通道的状态会影响所支持的操作,什么样的通道状态支持什么样的通道操作,同时也会最终影响终端设备的操作执行。
目前通道可以支持多种状态,基本常见的几种状态是:
- Down - 通信路径存在,但是Asterisk和终端之间没有任何媒体。
- Ringing - 设备正在振铃,在Asterisk和设备之间可能存在媒体。
- Up - 设备之间已经互相应答。当在UP状态时,在Asterisk和设备之间的媒体可以双向流动。
注意
某些通道介绍,例如Dahdi通道,可能支持了更多的状态 (例如,"Pre-ring" 或者 "Dialing Offhook")。当处理通道状态市,需要访问Channel data model 的各种变量信息。现在我们主要介绍几种不同的状态处理方式和脚本语言的处理流程。
指示振铃
Asterisk 可以通知设备对呼叫方进行振铃,使用的方式是POST /channels/{channel_id}/ring 的操作模式,或者可以使用
DELETE /channels/{channel_id}/ring 的操作停止振铃。这里用户要注意,指示振铃并不是真正从Asterisk到设备之间传输媒体。Asterisk仅是通知设备振铃,但是终端是否振铃取决于设备自己本身。
应答通道
当通道没有应答之前,Asterisk仍然没有通知设备如何进行通信。应答通道以后,Asterisk才完成了服务器端和设备端之间的通信路径的创建,可以实现媒体的双向流通。使用的方法是
POST /channels/{channel_id}/answer 操作。
对通道挂机
用户可以对通道挂机,使用的是DELETE /channels/{channel_id} 操作方式。当挂机以后,介于Asterisk和设备终端之间的通信会结束,通道离开Stasis的应用程序。用户的应用程序通过一个 StasisEnd 事件被提醒。
设备挂机也是同样的方式。在这种环境下,Asterisk和终端之间的通信结束以后,通道挂机,用户程序通过一个StasisEnd 事件被通知通道离开了应用程序。
一般情况下,一旦通道离开了用户应用程序,用户就不能收到任何关于通道的事件信息。但是当用户程序订阅了通道的所有事件时,无论通道是否在应用程序中,如果通道确实挂机,用户会收到一个ChannelDestroyed事件。
举例:控制通道的状态
这个实例ARI 程序会执行以下步骤:
- 等待通道进入到一个Stasis 应用程序。
- 当通道进入到Stasis程序后,它会对通道指示振铃,如果通道没有准备好振铃,那么就让通道现在开始振铃。
- 几秒钟以后,通道应答呼叫。
- 一旦通道应答了呼叫,我们对通道启动一个静音的时段。几秒钟以后,通道挂机。
- 如果在这个过程中终端挂机,系统会平滑处理挂机流程。
- 拨号规则
以下拨号实例仅表示通道进入到Stasis程序中:
extensions.conf
Python
这个例子中我们使用了ari-py 支持包。这个基本的架构类似于channel-dump Python- 用户可以参考这个实例来学习如何使用这个支持包连接ARI。
这里,我们首先需要创建我们的ARI客户端,并且注册三个不同的事件-StasisStart,ChannelStateChange,和StasisEnd。
很多流程的处理是在StasisStart中进行,当通道进入到我们的应用程序时,StasisStart就会被调用。大部分情况下,这里还要设置一个Python定时器来初始化通道中的一些指令。
当通道应答呼叫后,ChannelStateChange 句柄会打印出通道改变状态的输出信息。
最后,系统会把初始化的定时器权限,然后发出StasisEnd 的事件。当通道离开应用程序后,这个事件就会马上被调用,通常情况下,可能是呼叫方挂机,或者我们自己挂机。
我们可以启动一个通道的定时器:
然后注册三个事件:
这里的StasisStart 事件需要用户更多注意一下。
- 首先,我们通知通道指令,2秒钟以后,应答这个通道:
这里,我们在通道channel_timers 中存储了一个定时器,如果用户挂机以后,我们可以发出StasisEnd 的事件。
- 一旦程序执行进入到answer_channel中,系统应答这个通道,在通道中启动一个静音播放。注意,我们会继续声明一个answer_channel,这是一个在我们StasisStart 句柄内嵌的函数:
- 我们应答了通道以后,系统会在4秒钟内启动另外一个定时器对通道进行挂机处理。当定时器被触发后,它会调用hangup_channel。这是通道中最后的挂机程序。另外,我们会在StasisStart句柄中再次声明hangup_channel:
当创建了定时器以后,我们在通道中振铃时,系统会把定时器信息存储到程序中的channel_timers 中。在StasisEnd事件句柄中,我们可以取消掉定时器。如果通道离开了我们的应用程序后,我们可以触发这个定时器来执行某些流程或告警信息,例如HTTP返回消息等。
最后,我们打印出在ChannelStateChanged 句柄中的通道状态信息。这里打印出通道应答的消息:
![\](http://www.ctiforum.com/uploadfile/2017/0626/20170626094903241.jpg)
channel-state.py
完整的channel-state.py 源代码:
channel-state.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
#!/usr/bin/env python
import ari
import logging
import threading
logging.basicConfig(level = logging.ERROR)
client = ari.connect( 'http://localhost:8088' , 'asterisk' , 'asterisk' )
channel_timers = {}
def stasis_end_cb(channel, ev):
"""Handler for StasisEnd event"""
print "Channel %s just left our application" % channel.json.get( 'name' )
# Cancel any pending timers
timer = channel_timers.get(channel. id )
if timer:
timer.cancel()
del channel_timers[channel. id ]
def stasis_start_cb(channel_obj, ev):
"""Handler for StasisStart event"""
def answer_channel(channel):
"""Callback that will actually answer the channel"""
print "Answering channel %s" % channel.json.get( 'name' )
channel.answer()
channel.startSilence()
# Hang up the channel in 4 seconds
timer = threading.Timer( 4 , hangup_channel, [channel])
channel_timers[channel. id ] = timer
timer.start()
def hangup_channel(channel):
"""Callback that will actually hangup the channel"""
print "Hanging up channel %s" % channel.json.get( 'name' )
channel.hangup()
channel = channel_obj.get( 'channel' )
print "Channel %s has entered the application" % channel.json.get( 'name' )
channel.ring()
# Answer the channel after 2 seconds
timer = threading.Timer( 2 , answer_channel, [channel])
channel_timers[channel. id ] = timer
timer.start()
def channel_state_change_cb(channel, ev):
"""Handler for changes in a channel's state"""
print "Channel %s is now: %s" % (channel.json.get( 'name' ),
channel.json.get( 'state' ))
client.on_channel_event( 'StasisStart' , stasis_start_cb)
client.on_channel_event( 'ChannelStateChange' , channel_state_change_cb)
client.on_channel_event( 'StasisEnd' , stasis_end_cb)
client.run(apps = 'channel-state' )
|
channel-state.py 实际输出结果
这是我们终端“alice” 使用PJSIP通道进入到我们应用程序时的输出结果:
JavaScript (Node.js)
这个例子中我们使用了ari-client 支持包。这个基本的架构类似于channel-dump JavaScript- 用户可以参考这个实例来学习如何使用这个支持包连接ARI。
这里,我们首先需要创建我们的ARI客户端,并且注册三个不同的事件-StasisStart,ChannelStateChange,和StasisEnd。
很多流程的处理是在StasisStart中进行,当通道进入到我们的应用程序时,StasisStart就会被调用。大部分情况下,这里还要设置一个Python定时器来初始化通道中的一些指令。
当通道应答呼叫后,ChannelStateChange 句柄会打印出通道改变状态的输出信息。
最后,系统会把初始化的定时器权限,然后发出StasisEnd 的事件。当通道离开应用程序后,这个事件就会马上被调用,通常情况下,可能是呼叫方挂机,或者我们自己挂机。
我们可以启动一个通道的定时器:
然后注册三个事件:
这里的StasisStart 事件需要用户更多关注。
首先我们通知通道指令,2秒钟以后应答通道:
这里,我们在通道channel_timers 中存储了一个定时器,如果用户挂机以后,我们可以发出StasisEnd 的事件。
一旦程序执行进入到answer_channel中,系统应答这个通道,在通道中启动一个静音播放。注意,我们会继续声明一个answer_channel,这是一个在我们StasisStart 句柄内嵌的函数:
我们应答了通道以后,系统会在4秒钟内启动另外一个定时器对通道进行挂机处理。当定时器被触发后,它会调用hangup_channel。这是通道中最后的挂机程序。另外,我们会在StasisStart句柄中再次声明hangup_channel:
当创建了定时器以后,我们在通道中振铃时,系统会把定时器信息存储到程序中的channel_timers 中。在StasisEnd事件句柄中,我们可以取消掉定时器。如果通道离开了我们的应用程序后,我们可以触发这个定时器来执行某些流程或告警信息,例如HTTP返回消息等。
最后,我们打印出在ChannelStateChanged 句柄中的通道状态信息。这里打印出通道应答的消息:
channel-state.js
完整的channel-state.js 源代码:
channel-state.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
/*jshint node: true*/
'use strict' ;
var ari = require( 'ari-client' );
var util = require( 'util' );
var timers = {};
ari.connect( 'http://localhost:8088' , 'asterisk' , 'asterisk' , clientLoaded);
// handler for client being loaded
function clientLoaded (err, client) {
if (err) {
throw err;
}
// handler for StasisStart event
function stasisStart(event, channel) {
console.log(util.format(
'Channel %s has entered the application' , channel.name));
channel.ring( function (err) {
if (err) {
throw err;
}
});
// answer the channel after 2 seconds
var timer = setTimeout(answer, 2000);
timers[channel.id] = timer;
// callback that will answer the channel
function answer() {
console.log(util.format( 'Answering channel %s' , channel.name));
channel.answer( function (err) {
if (err) {
throw err;
}
});
channel.startSilence( function (err) {
if (err) {
throw err;
}
});
// hang up the channel in 4 seconds
var timer = setTimeout(hangup, 4000);
timers[channel.id] = timer;
}
// callback that will hangup the channel
function hangup() {
console.log(util.format( 'Hanging up channel %s' , channel.name));
channel.hangup( function (err) {
if (err) {
throw err;
}
});
}
}
// handler for StasisEnd event
function stasisEnd(event, channel) {
console.log(util.format(
'Channel %s just left our application' , channel.name));
var timer = timers[channel.id];
if (timer) {
clearTimeout(timer);
delete timers[channel.id];
}
}
// handler for ChannelStateChange event
function channelStateChange(event, channel) {
console.log(util.format(
'Channel %s is now: %s' , channel.name, channel.state));
}
client.on( 'StasisStart' , stasisStart);
client.on( 'StasisEnd' , stasisEnd);
client.on( 'ChannelStateChange' , channelStateChange);
client.start( 'channel-state' );
}
|
channel-state.js in action
这是我们终端“alice” 使用PJSIP通道进入到我们应用程序时的输出结果: