Algum dia escreverei algo como "Como me tornei um programador aos 40" . Mas definitivamente não hoje, além disso, não tenho mais 40 anos e não me considero um programador. E eu gostaria de falar sobre minha experiência no desenvolvimento de PBX para minhas próprias necessidades. Yate é usado como mecanismo de VoIP , a frente e o backend serão em Perl.
Costumo encontrar perguntas nos comentários dos artigos: "Por que não (seguem as opções favoritas dos comentaristas)?" Então, em ordem.
Por que
Por que não Asterisk, FreeSwitch, Kamailio e outros. Se não me falha a memória, então 12-13 anos atrás, foi com o Asterisk que minha familiaridade com o mundo da telefonia SIP começou, já que o limite de entrada era bastante baixo, você poderia baixar uma imagem de disco pronta, onde o próprio Asterisk , um focinho da web e até mesmo algumas versões rudimentares de sistemas de faturamento. Naturalmente, todo esse deleite exuberante despertava, caía constantemente, e depois de uma colocação bem-sucedida era melhor não tocar nele. Lembro que até tentamos vender o serviço de telefonia sip para nossos clientes, mas em algum momento tudo isso exigiu a obtenção de licenças e tornou-se simplesmente não lucrativo economicamente para nossa base de clientes. Mais tarde, por um longo tempo, usei o Asterisk apenas como um PBX de escritório, até que me cansei de constantemente deixar cair / congelar o serviço no FreeBSD (respondo antecipadamente à pergunta "por que não Linux?"porque gladíolo" ). Experimentos com outros engines não acabaram em nada, via de regra, devido à falta de interface web adequada ou à dificuldade de instalação (aqui exagerei um pouco, na verdade, agora tenho duas instalações FreeSwitch funcionando que estão funcionando há vários anos sem qualquer interferência) ... Enquanto navegava na net, me deparei com Yate, na minha opinião a 2ª versão então. A primeira coisa que gostei foi o mínimo de configurações necessárias para começar a chamar, talvez em nenhum outro lugar eu encontrei uma configuração mais simples. Em segundo lugar, existe uma webcam simples, FreeSentralcobrindo 90 por cento de uma configuração de PBX de escritório. E terceiro, talvez a coisa mais importante - tudo funciona fora da caixa. O que quero dizer quando digo "tudo funciona" é, obviamente, trabalhar por trás de NAT e DTMF, independentemente do hardware / software no lado do cliente. Talvez tenha sido só eu que tive a sorte, embora tenha tido que trabalhar com um monte de pedaços de ferro do long ao cisco, que, sem dançar com um pandeiro, com o mesmo Asterisk, por exemplo, não transferia dtmf. A documentação deficiente e os exemplos corrompidos são talvez a principal desvantagem do projeto. Ou seja, se houver desejo de fazer algo sério, você terá que consultar as fontes do 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;
}
Na verdade, tenho mais dúvidas sobre como trabalhar com Yate agora do que no início. Por exemplo, eu simplesmente não consigo descobrir por que dtmf está voando em chamadas encaminhadas, que não está no módulo PBX nativo, etc. Em geral, o objetivo desta postagem é comentar sobre a implementação do Perl. É uma pena que os desenvolvedores tenham abandonado o projeto, embora por outro lado, já exista funcionalidade acima do telhado, do WebRTC ao Jabber, e não é fato que mais será melhor. Os caras determinam erros críticos no kernel, embora meu tíquete com o patch esteja pendurado por vários anos, mas, novamente, isso não é um erro no kernel, mas em um módulo raramente usado e é um caso especial, já que com um estrutura de banco de dados correta, um erro é simplesmente impossível.