いつか「40歳でプログラマーになった経緯」のようなものを書きます。しかし、今日は間違いなくそうではありません。さらに、私はもう40歳ではなく、自分自身をプログラマーとは見なしていません。そして、私自身のニーズに合わせてPBXを開発した私の経験についてお話ししたいと思います。YateはVoIPエンジンとして使用され、フロントエンドとバックエンドはPerlになります。
記事へのコメントでよく質問に出くわします:「なぜ(コメンテーターのお気に入りのオプションが続く)?」だから、順番に。
なぜ
Asterisk、FreeSwitch、Kamailioなどを使ってみませんか。私の記憶が正しければ、12〜13年前、SIPテレフォニーの世界についての私の知り合いが始まったのは、Asteriskでした。エントリのしきい値が非常に低かったため、Asterisk自体が含まれる既製のディスクイメージをダウンロードできました。 、Web銃口、さらには課金システムのいくつかの初歩的なバージョン。当然のことながら、これらすべては熱狂的な喜びを引き起こし、絶えず落ちました、そして成功した設定の後、それに触れない方が良いです。私たちはクライアントにSIPテレフォニーサービスを販売しようとしたことさえ覚えていますが、ある時点でこれはすべてライセンスを取得する必要があり、クライアントベースにとって単に経済的に不採算になりました。その後、長い間、私はAsteriskをオフィスのPBXとしてのみ使用していましたが、FreeBSDでサービスを絶えずドロップ/フリーズすることに飽き飽きしていました(「なぜLinuxではないのか」という質問に事前に答えます。「グラジオラスのため」)。他のエンジンでの実験は、適切なWeb GUIの欠如またはセットアップの難しさのために、原則として何も終わりませんでした(ここでは少し誇張しましたが、実際、数年間機能している2つのFreeSwitchインストールがあります干渉なし)..。ネットサーフィンをしていると、Yateに出くわしました。私の意見では2番目のバージョンです。私が最初に気に入ったのは、通話を開始するために必要な最小限の設定でした。おそらく、これほど単純な設定に出くわした場所は他にありません。第二に、シンプルなウェブカメラ、FreeSentralがありますオフィスのPBXセットアップの90%をカバーします。そして第三に、おそらく最も重要なこと-すべてが箱から出して動作します。 「すべてが機能する」と言うときは、もちろん、クライアント側のハードウェア/ソフトウェアに関係なく、NATとDTMFの背後で機能します。たぶん、とても幸運だったのは私だけでしたが、たとえば、同じアスタリスクを使用して、タンバリンで踊らずにdtmfを転送しなかった、長いものからCiscoまでの鉄片を扱う必要がありました。不十分なドキュメントと壊れた例は、おそらくプロジェクトの主な欠点です。つまり、何か深刻なことをしたいという願望がある場合は、Yateの情報源にアクセスする必要があります。
2- , - , jail 2-3 . , - php. freesentral . - , , . , , Yate . ...
. . - . , , . , Perl. Abiils, .
Yate , Perl, Vasily i. Redkin github.
. Yate , - . - clang 64- FreeBSD, - . , PBX , C++ , , , mysql psql( ). , Perl .
. yate.conf [modules]. / ( , ):
[modules]
; SIP
ysipchan.yate=yes
;
wavefile.yate=yes
; CDR
cdrbuild.yate=yes
;
cdrcombine.yate=yes
;
moh.yate=yes
;
rmanager.yate=yes
;
register.yate=yes
;
tonegen.yate=yes
; (Perl, PHP, JS )
extmodule.yate=yes
; RTP
yrtpchan.yate=yes
;
openssl.yate=yes
;,
dumbchan.yate=yes
; , - ,
; .
msgsniff.yate=yes
; , ,
park.yate=yes
extmodule.conf. :
; , scripts
[scripts]
pbx_route.pl=
; ,
[listener tcp5039]
type=tcp
addr=10.0.0.7
port=5039
, , , . - PHP, . Perl . Yate vir', .
. pbx_route.pl:
#!/usr/bin/perl -w
#
use strict;
use warnings;
# @INC
BEGIN {
use FindBin '$Bin';
our $libpath = $Bin . '/../';
my $sql_type = 'mysql';
unshift( @INC,
$libpath . "Abills/$sql_type/",
$libpath . '/lib/',
$libpath . "Abills/modules" );
}
use Abills::SQL;
# Yate
use Pbx::Yate;
#
use Pbx::Pbx;
my $message = Yate->new();
my $Pbx = Pbx->new($db, $message, \%conf);
#
trunks_init($message);
#
$message->install('call.answered', \&call_answered_handler, 50);
$message->install('call.route', \&call_route_handler);
$message->install_watcher('call.execute', \&call_execute_handler, 50);
$message->install('chan.hangup', \&chan_hangup_handler);
$message->install('chan.disconnected', \&chan_disconnected_handler, 10);
$message->install('chan.dtmf', \&chan_dtmf_handler, 50);
$message->install('user.auth', \&user_auth_handler);
#$message->install('user.authfail', \&user_authfail);
$message->install('user.register', \&user_register_handler);
$message->install('user.unregister', \&user_unregister_handler);
$message->install('user.notify', \&user_notify_handler);
$message->install_watcher("engine.timer", \&engine_timer_handler);
#
$message->listen();
sub trunks_init {
my $message = shift;
my ($attr) = @_;
#
my $trunks = $Pbx->trunk_list({
ACCOUNT => '_SHOW',
PROTOCOL => '_SHOW',
USERNAME => '_SHOW',
PASSWORD => '_SHOW',
REGISTRAR => '_SHOW',
LOCALADDRESS => '_SHOW',
OUTBOUND => '_SHOW',
DOMAIN => '_SHOW',
ENABLED => 1,
INTERVAL => '_SHOW',
OPTIONS => '_SHOW',
COLS_NAME => 1
});
if ($trunks) {
foreach my $tr (@$trunks) {
$message->message('user.login', undef, undef, %$tr );#
}
}
}
#
sub call_route_handler {
my $message = shift;
my $id = $message->param('id');
my $called = $message->param('called');
my $caller = $message->param('caller');
#
$called =~ s/\+//g;
#
my $call_type = ($Pbx->extensions_list({ NUMBER => $called, LIST2HASH => 'number,location' })) ? 'to_internal' : 'to_external';
# ,
#
if ($Pbx->get_route($called)) {
$message->params($Pbx->{params});
$message->param('call_type', $call_type);
$message->param('copyparams', 'maxcall,call_type,pbx_from');
delete $Pbx->{params};
return $Pbx->{location}
}
return 'noroute'
}
#
sub user_auth_handler {
my $message = shift;
my $user = $message->param('username');
if ($user) {
my $auth = $Pbx->extensions_list({ NUMBER => $user, PASSWORD => '_SHOW', COLS_NAME => 1 });
if ($auth) {
return $auth->{password};
}
}
return undef;
}
#
sub user_register_handler($) {
my $message = shift;
$Pbx->update_location({
LOCATION => $message->param('data'),
CONN_ID => $message->param('connection_id'),
EXPIRES => $message->param('expires'),
NUMBER => $message->param('number')
});
return 'true'
}
sub user_unregister_handler($) {
my $message = shift;
$Pbx->update_location({
CONN_ID => '',
NUMBER => $message->param('number')
});
return 'true'
}
#
sub user_notify_handler($) {
my $message = shift;
my $account = $message->param('account');
my $status = ($message->param('registered') ne 'false') ? 0 : 1;
$Pbx->query2("UPDATE pbx_trunks SET status=$status WHERE account='$account';", 'do');
return undef;
}
, , dtmf, -. , IVR. :
#
#id - ,
#replace - ,
$message->message('chan.attach', undef,'',
replace => 'true',
source => "wave/play/hi.wav",
notify => $id,
id => $id
);
#
# 'eof', wavefile.yate
# 'chan.notify'
# ,
my $handl;
$message->install('chan.notify', $handl = sub {
$message->message('chan.attach', undef, '',
replace => 'true',
source => "wave/play/hi.wav",
notify => $id,
id => $id
)
}, 50, 'reason', 'eof');
# .
# , -
#
# ,
#caller -
# CDR
sub pbx_call {
my ($attr) = @_;
#
my $info = $admin->list({
SIP_NUMBER => '_SHOW',
AID => $admin->{AID},
COLS_NAME => 1
});
my $message = Yate->new();
# ID
my $msgid = $message->generate_id;
# , extmodule.conf
$message->connect("10.0.0.7:5039");
$message->message('call.execute', undef, $msgid,
message => 'call.execute',
direct => $Pbx->build_location($info->[0]->{sip_number}),
caller => $FORM{PHONE},# , -
callto => "dumb/",#
callback => $FORM{PHONE},
cdrwrite => 'false',
cdrtrack => 'false',
target => $info->[0]->{sip_number},
);
return 1;
}
実際、Yateでの作業については、当初よりも多くの質問があります。たとえば、ネイティブのpbxモジュールなどにない転送された通話でdtmfが飛んでいる理由がわかりません。概して、この投稿の目的は、Perlの実装についてコメントすることです。開発者がプロジェクトを放棄したのは残念ですが、一方で、WebRTCからJabberまで、すでに屋根の上に機能があり、より多くの方が優れているというのは事実ではありません。パッチのチケットが数年間ぶら下がっていますが、彼らはカーネルの重大なエラーを支配していますが、これもカーネルのエラーではなく、めったに使用されないモジュールであり、正しいデータベース構造、エラーは単に不可能です。