您当前的位置是:  首页 > 新闻 > 国内 >
 首页 > 新闻 > 国内 >

Aterisk接口-ARI对通道的状态管理

2017-06-26 09:27:58   作者:james.zhu   来源:Aterisk   评论:0  点击:


  以前我们讨论了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 句柄中的通道状态信息。这里打印出通道应答的消息:
  \
  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通道进入到我们应用程序时的输出结果:
\

相关阅读:

专题