Escribir PBX en Perl para Yate

Algún día escribiré algo como "Cómo me convertí en programador a los 40" . Pero hoy definitivamente no, además, ya no tengo 40 años y no me considero programador. Y me gustaría contarles sobre mi experiencia en el desarrollo de PBX para mis propias necesidades. Yate se utiliza como motor de VoIP , el front-end y el backend estarán en Perl.





A menudo me encuentro con preguntas en los comentarios de los artículos: "¿Por qué no (siguen las opciones favoritas de los comentaristas)?" Entonces, en orden.





Por qué

¿Por qué no Asterisk, FreeSwitch, Kamailio y otros? Si la memoria no me falla, entonces hace 12-13 años, fue con Asterisk que comenzó mi conocimiento del mundo de la telefonía SIP, ya que el umbral de entrada era bastante bajo, era posible descargar una imagen de disco lista para usar, donde Asterisk sí mismo, un bozal web e incluso algunas versiones rudimentarias de los sistemas de facturación. Naturalmente, todo este deleite exuberante despertaba, caía constantemente, y después de un ajuste exitoso era mejor no tocarlo. Recuerdo que incluso intentamos vender el servicio de telefonía sip a nuestros clientes, pero en algún momento todo esto requirió la obtención de licencias y simplemente se volvió económicamente poco rentable para nuestra base de clientes. Más tarde, durante mucho tiempo, usé Asterisk solo como PBX de oficina, hasta que me cansé de dejar / congelar constantemente el servicio en FreeBSD (respondo de antemano la pregunta "¿por qué no Linux?""porque gladiolos" ). Los experimentos con otros motores no terminaron en nada, por regla general, debido a la falta de una interfaz gráfica de usuario web adecuada o la dificultad de configuración (aquí exageré un poco, de hecho, ahora tengo dos instalaciones de FreeSwitch en funcionamiento que han estado funcionando durante varios años sin ninguna interferencia) ... Mientras navegaba por la red, me topé con Yate, en mi opinión la segunda versión entonces. Lo primero que me gustó fue la configuración mínima requerida para comenzar a llamar, quizás en ningún otro lugar he encontrado una configuración más simple. En segundo lugar, hay una cámara web simple, FreeSentralcubriendo el 90 por ciento de la configuración de un PBX de oficina. Y tercero, quizás lo más importante: todo funciona de inmediato. Lo que quiero decir cuando digo "todo funciona" es, por supuesto, trabajar detrás de NAT y DTMF, independientemente del hardware / software del lado del cliente. Quizás fui solo yo el que tuve tanta suerte, aunque tuve que trabajar con un manojo de piezas de hierro de largo a cisco, que, sin bailar con pandereta, con el mismo asterisco, por ejemplo, no transfirió dtmf. La documentación deficiente y los ejemplos defectuosos son quizás el principal inconveniente del proyecto. Es decir, si hay ganas de hacer algo serio, tendrás que acudir a las fuentes de 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;
}
  

      
      



De hecho, tengo más preguntas sobre trabajar con Yate ahora que al principio. Por ejemplo, simplemente no puedo entender por qué dtmf está volando en llamadas reenviadas, que no está en el módulo pbx nativo, etc. En general, el propósito de esta publicación es comentar sobre la implementación de Perl. Es una pena que los desarrolladores abandonaran su proyecto, aunque por otro lado, ya existe una funcionalidad por encima del techo, desde WebRTC hasta Jabber, y no es un hecho que más será mejor. Los chicos descartan errores críticos en el kernel, aunque mi ticket con el parche ha estado colgando durante varios años, pero nuevamente, esto no es un error en el kernel, sino en un módulo que se usa poco y es más bien un caso especial, ya que con un estructura correcta de la base de datos, un error es simplemente imposible.








All Articles