计算机系统应用教程网站

网站首页 > 技术文章 正文

Asterisk-ARI对通道中的DTMF事件处理

btikc 2024-10-30 02:17:04 技术文章 10 ℃ 0 评论

Asterisk通道中关于DTMF处理是一个非常重要的功能。通过DTMF可以实现很多的业务处理。现在我们介绍一下关于ARI对通道中的DTMF处理,我们通过自动话务员实例来说明Asterisk如何创建一个拨号规则,如何在Stasis中执行脚本语言,在脚本中对DTMF实现整个菜单的处理。

本文的实例是结合了Python和JavaScript两种开发语言。因为担心用户微信阅读中的阅读体验不好,所以,我们仅列出了Python代码,JavaScript的完整代码可以通过论坛获得。

DTMF事件处理

DTMF事件是通过ChannelDtmfReceived来传输。这个事件中包含了通道中的相关信息,它们可能是用户输入的DTMF键,数字和数字长度。

作为基本电话系统的要求,系统必须支持处理DTMF,然后根据DTMF来执行某些操作。这些操作包括控制媒体,发起呼叫功能,执行电话转接,拨号等等。以下实例仅说明如何处理DTMF事件,而不是针对DTMF相关功能的处理。

实例:自动话务员

以下是一个典型的自动话务员/IVR的拨号规则。它需要执行以下几步:

  • 对系统用户播放一个语音菜单,当用户执行某一个命令时,取消语音菜单提示。

  • 如果用户按1或者2时,重新进入菜单。

  • 如果用户输入无效的数字时,系统对用户播放一个提示语音指示此数字是无效的,然后重新进入菜单。

  • 如果用户在一定时间内没有输入任何数字,要求用户输入数字,然后重新进入到菜单播放流程。

注意,执行以下实例,系统安装需要满足几个条件:

  1. 安装extra 语音包,用户可以通过

    menuselect

    编译语音包安装。

  2. 如果使用Python 的例子,

    ari-py

    版本必须是0.1.3或更高版本。

  3. 如果使用JavaScript的例子,ari-client版本必须是0.1.4或者更高版本。

创建一个拨号规则

这是一个简单的拨号规则,可以满足我们实例的要求。拨号规则把通道引入到Stasis应用程序

channel-aa中。

exten =>1000,1,NoOp()

same =>n,Stasis(channel-aa) // 进入到用户指定的用户程序

same =>n,Hangup

(拨号规则)

Python开发实例

因为这个实例代码比较多,所以把代码分成两个部分来介绍:

  1. 构建一个语音菜单,当用户按按钮时处理相应的状态。

  2. 用户按键输入以后的处理流程。

播放语音菜单处理

不像Playback语音回放功能,它可以连续播放几个语音文件,然后重新回放这些语音文件,ARI则会把这些文件看作是一个文件播放。这些文件会按照队列的形式在通道中播放,然后返回到呼叫方一个对象,以便呼叫方对其单一文件进行操作控制。此菜单对话务员有以下要求:

  1. 对用户回放选项

  2. 如果用户按了一个DTMF键以后,取消回放选项,处理用户的输入请求

  3. 如果用户按了无效的DTMF按键,系统通知用户,重新开始语音菜单

  4. 如果用户在10秒内无任何输入,要求用户重新输入,重新启动菜单

对于第二个要求的处理可能所谓复杂一点。当用户按DTMF键时,我们需要取消掉正在播放的语音文件,马上处理下一步的请求。我们必须维持当前播放的语音文件状态,以便需要播放时能够重新回放。保证前面的语音文件完成后可以进行下一个语音文件的播放。

取消菜单处理

当用户按DTMF键后,系统可以停止当前的回放,结束菜单。为了实现这个功能,我们需要从通道订阅 DTMF事件。这里,我们需要定义一个新的函数

cancel_menu,当DTMF按键通过

ChannelDtmfReceived事件收到以后,

它通知

ari-py

调用这个函数。这里,我们不真正关心具体的数字按键,我们仅执行取消菜单的流程。在这个函数中,系统会设置

menu_state.complete

True,然后通知

current_playback停止播放。

超时处理

现在系统可以取消菜单,当然,我们也可以重新启动菜单流程,当系统用户没有收入任何按键时,超时后重新启动菜单。这里,我们使用了一个Python 定时器来控制这个时间。如果用户输入了按键以后,我们则不会启动这个定时器。在一定时间范围内,用户没有收入数字按键,定时器会调用menu_timeout,这个函数会播放一个 "are you still there?" 的提示音,然后重新启动菜单。

对DTMF的处理选项

现在系统可以对用户回放菜单,实际上,我们还要实现一个自动话务员的菜单。系统可以在

StasisStart

中注册

ChannelDtmfReceived

事件,所以,我们需要做以下几个事情:

  1. 可以取消和任何通道关联的定时器。

  2. 如果数字是1或2时处理输入的数字。

  3. 如果输入的数字不支持的话,对系统用户播放一个提示音,提示输入数字无效,然后重新播放菜单选项。

完整Python代码:channel-aa.py

#!/usr/bin/env python

import

ari

import

logging

import

threading

logging.basicConfig(level

=

logging.ERROR)

client

=

ari.connect(

'http://localhost:8088'

,

'asterisk'

,

'asterisk'

)

# Note: this uses the 'extra' sounds package

sounds

=

[

'press-1'

,

'or'

,

'press-2'

]

channel_timers

=

{}

class

MenuState(

object

):

"""A small tracking object for the channel in the menu"""

def

__init__(

self

, current_sound, complete):

self

.current_sound

=

current_sound

self

.complete

=

complete

def

play_intro_menu(channel):

"""Play our intro menu to the specified channel

Since we want to interrupt the playback of the menu when the user presses

a DTMF key, we maintain the state of the menu via the MenuState object.

A menu completes in one of two ways:

(1) The user hits a key

(2) The menu finishes to completion

In the case of (2), a timer is started for the channel. If the timer pops,

a prompt is played back and the menu restarted.

Keyword Arguments:

channel The channel in the IVR

"""

menu_state

=

MenuState(

0

,

False

)

def

play_next_sound(menu_state):

"""Play the next sound, if we should

Keyword Arguments:

menu_state The current state of the IVR

Returns:

None if no playback should occur

A playback object if a playback was started

"""

if

(menu_state.current_sound

=

=

len

(sounds)

or

menu_state.complete):

return

None

try

:

current_playback

=

channel.play(media

=

'sound:%s'

%

sounds[menu_state.current_sound])

except

:

current_playback

=

None

return

current_playback

def

on_playback_finished(playback, ev, menu_state):

"""Callback handler for when a playback is finished

Keyword Arguments:

playback The playback object that finished

ev The PlaybackFinished event

menu_state The current state of the menu

"""

queue_up_sound(channel, menu_state)

def

queue_up_sound(channel, menu_state):

"""Start up the next sound and handle whatever happens

Keywords Arguments:

channel The channel in the IVR

menu_state The current state of the menu

"""

def

menu_timeout(channel):

"""Callback called by a timer when the menu times out"""

print

'Channel %s stopped paying attention...'

%

channel.json.get(

'name'

)

channel.play(media

=

'sound:are-you-still-there'

)

play_intro_menu(channel)

def

cancel_menu(channel, ev, current_playback, menu_state):

"""Cancel the menu, as the user did something"""

menu_state.complete

=

True

try

:

current_playback.stop()

except

:

pass

return

current_playback

=

play_next_sound(menu_state)

if

not

current_playback:

if

menu_state.current_sound

=

=

len

(sounds):

# Menu played, start a timer!

timer

=

threading.Timer(

10

, menu_timeout, [channel])

channel_timers[channel.

id

]

=

timer

timer.start()

return

menu_state.current_sound

+

=

1

current_playback.on_event(

'PlaybackFinished'

, on_playback_finished,

callback_args

=

[menu_state])

# If the user hits a key or hangs up, cancel the menu operations

channel.on_event(

'ChannelDtmfReceived'

, cancel_menu,

callback_args

=

[current_playback, menu_state])

channel.on_event(

'StasisEnd'

, cancel_menu,

callback_args

=

[current_playback, menu_state])

queue_up_sound(channel, menu_state)

def

handle_extension_one(channel):

"""Handler for a channel pressing '1'

Keyword Arguments:

channel The channel in the IVR

"""

channel.play(media

=

'sound:you-entered'

)

channel.play(media

=

'digits:1'

)

play_intro_menu(channel)

def

handle_extension_two(channel):

"""Handler for a channel pressing '2'

Keyword Arguments:

channel The channel in the IVR

"""

channel.play(media

=

'sound:you-entered'

)

channel.play(media

=

'digits:2'

)

play_intro_menu(channel)

def

cancel_timeout(channel):

"""Cancel the timeout timer for the channel

Keyword Arguments:

channel The channel in the IVR

"""

timer

=

channel_timers.get(channel.

id

)

if

timer:

timer.cancel()

del

channel_timers[channel.

id

]

def

on_dtmf_received(channel, ev):

"""Our main DTMF handler for a channel in the IVR

Keyword Arguments:

channel The channel in the IVR

digit The DTMF digit that was pressed

"""

# Since they pressed something, cancel the timeout timer

cancel_timeout(channel)

digit

=

int

(ev.get(

'digit'

))

print

'Channel %s entered %d'

%

(channel.json.get(

'name'

), digit)

if

digit

=

=

1

:

handle_extension_one(channel)

elif

digit

=

=

2

:

handle_extension_two(channel)

else

:

print

'Channel %s entered an invalid option!'

%

channel.json.get(

'name'

)

channel.play(media

=

'sound:option-is-invalid'

)

play_intro_menu(channel)

def

stasis_start_cb(channel_obj, ev):

"""Handler for StasisStart event"""

channel

=

channel_obj.get(

'channel'

)

print

"Channel %s has entered the application"

%

channel.json.get(

'name'

)

channel.on_event(

'ChannelDtmfReceived'

, on_dtmf_received)

play_intro_menu(channel)

def

stasis_end_cb(channel, ev):

"""Handler for StasisEnd event"""

print

"%s has left the application"

%

channel.json.get(

'name'

)

cancel_timeout(channel)

client.on_channel_event(

'StasisStart'

, stasis_start_cb)

client.on_channel_event(

'StasisEnd'

, stasis_end_cb)

client.run(apps

=

'channel-aa'

)

channel-aa.py 实战输出结果

以下是执行

channel-aa.py

输出的结果,当PJSIP通道收到按键输入1,2,8后,超时的流程,最后挂机。

Channel PJSIP/alice-

00000001

has entered the application

Channel PJSIP/alice-

00000001

entered

1

Channel PJSIP/alice-

00000001

entered

2

Channel PJSIP/alice-

00000001

entered

8

Channel PJSIP/alice-

00000001

entered an invalid option!

Channel PJSIP/alice-

00000001

stopped paying attention...

PJSIP/alice-

00000001

has left the application

以上例子是一个自动话务员的实例。在ARI对通道的DTMF处理,基本上都需要经过以上几个个方面的处理,它们包括:创建拨号规则,通过拨号规则进入到Stasis应用程序中,脚本语言负责执行语音播放处理,DTMF选项处理,无效输入处理,和超时处理。

因为微信中插入大量代码会影响用户的阅读体验,完整的Python和Javasript的源代码可以访问论坛查看。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表