Translate

miércoles, 2 de octubre de 2013

Aplicación Android para Espiar llamadas(ChanSpy)

Hola, esta vez les quiero compartir una aplicación para smartphones Android, la cual sirve para obtener los canales SIP activos de nuestro servidor Asterisk y poder monitorearlos a través del softphone que tengamos en nuestro celular.

Screenshots:

ICON



SPLASH SCREEN


First RUN


SETTINGS

SETTINGS 2

HOME / GET CHANS


FETCHING

NO CHANS

ACTIVE CHAN

FORBIDDEN ID

ABOUT



Requisitos:

  1. Un smartphone con un cliente SIP ya registrado.
  2. Android >= 2.3.3 (GingerBread).
  3. Habilita la instalación de paquetes no firmados en tu teléfono: AJUSTES--->APLICACIONES--->ORIGENES DESCONOCIDOS
  4. Instalar la aplicación en el smartphone.
  5. Instalar la GUI en el servidor Asterisk.

Instalación:

Descarga el APK para tu celular desde este enlace: Descargar APK 
O bien usa esta imagen desde tu celular:

Para Asterisk plano:

  1. Descarga el paquete de instalación de la GUI aquí:  Paquete para Asterisk Plano
  2. Descomprime el tar con el comando:
    tar zxvf plain_asterisk_spyagents.tar.gz
  3. Ejecuta el archivo install.sh e ingresa los datos: 
    ./install.sh
  4. Una vez que se instale ve a la interfaz WEB(http://ipdelservidor/spyagents) y crea los IDs que podrán llamar para monitorear las llamadas.
Imagen de la GUI:



Dejo el video de como instalar el paquete:



Instalación y Pruebas de la aplicación Android:




Para FreePBX:

  1. Descarga el módulo para instalarlo en Freepbx: Descargar Módulo FreePBX
  2. Ve al Menú ADMIN-->MODULE ADMIN.
  3. Selecciona la opción UPLOAD MODULE.
  4. Busca el módulo recién descargado y seleccionalo.
  5. Crea la IDs necesarias para monitorear las llamadas.
Imagen del Módulo:



Dejo el video de como instalar el módulo en FreePBX:



Operación de la aplicación:




Para Elastix:

  1. Descarga el RPM para elastix desde el siguiente enlace: Descargar RPM
  2. Ingresa a la consola de linux del PBX e instala el paquete con el siguiente comando:
    rpm -ihv --nodeps elastix-spyagents-0.1-1.noarch.rpm
  3. Ingresa a la interfaz gráfica de Elastix y ve al Menú: PBX--->SpyAgents
  4. Crea las IDs necesarias para monitorear las llamadas. 
Imagen de la GUI:



Dejo el video la instalación del RPM:



Operación de la Aplicación con Elastix:







Que lo disfruten ;)

martes, 20 de agosto de 2013

Módulo HTML VoiceMail para FreePBX

Uno de los primeros artículos de este sitio fue crear la notificación de correo tipo HTML para los buzones de Voz que recibimos en Asterisk. Ahora ese script lo he adaptado para que se pueda usar con FreePBX > 2.10.

El módulo lo pueden Descargar de este enlace.

Instalación.


  • Ir al Menú Module Admin--->upload
    • Seleccionar el tarball y dar click en Upload
    • Ir a la sección UNSUPPORTED
    • Instalar y dar click en Process.
  • Ir al Menú  Settings---->Voicemail Admin
    • Cambiar emailbody por: ${VM_NAME}|${VM_MAILBOX}|${VM_DUR}|${VM_CALLERID}|${VM_DATE}
    • Cambiar mailcmd por: /etc/asterisk/sendvm.php
  • Aplicar los cambios
  • Ir al Menú Settings---->HTML Voicemail
    • Cambiar los datos de autenticación del correo.
    • Dar click en salvar.


Uso.

El módulo incluye un template HTML básico que se puede apreciar en la página del módulo.




Si se desea cambiar el template basta con crear un nuevo código HTML en la sección "Cambiar template" y dar click en "Salvar Template":


Las variables que se pueden usar dentro del template HTML son:
  • ${name}: El dueño del buzón.
  • ${mailbox}: El número del buzón.
  • ${dur}: La duración del buzón de voz.
  • ${cid}: El identificador de la persona que dejó el buzón de voz.
  • ${date}: La fecha en que fue recibido el buzón de voz.




Saludos y gracias por el feedback que puedan dar. ;)





jueves, 25 de julio de 2013

Sistema de Atención al Cliente con WebRTC y Elastix-CallCenter.

Hola, en este artículo vamos a crear un sistema de atención a cliente usando las herramientas WebRTC-SIPML5 y Elastix junto con su addon de Call Center. La idea general es generar 0 costos entre el usuario y nuestro centro de atención. Es por esto que vamos a usar WebRTC y Elastix dos herramientas open source las cuales van a interactuar usando nuestra conexión de internet.

Paso 1.

Instalar el soporte de WebRTC para el módulo de Call Center de Elastix que publiqué anteriormente.

Paso 2.

Crear una cola de atención:
  • Crear al menos 2 dispositivos SIP. Menú PBX--->Extensions.
  • Crear una cola de atención. Menú PBX--->Queue.
  • Asignar a la nueva cola de atención uno de los dispositivos SIP como miembro dinámico y usando el prefijo S, por ejemplo, para el dispositivo 1500 quedará de la siguiente manera: S1500,0
  • Seleccionar el 'ring strategy'(estrategia de timbrado) con 'fewestcalls'(menos llamadas recibidas).
  • Añadir los Anuncios necesarios.
  • Aplicar los cambios. 

Paso 3.

Configurar el Módulo de Call Center de Elastix, para recibir llamadas:
  • Ir al menú 'Agent Options'(Opciones de agente).
  • Crear al menos una 'callback extension'(Extension callback). Tiene que coincidir con el dispositivo que se añadió como miembro a la cola de atención.
  • Ir a 'Ingoing calls'(llamadas entrantes)---> Queues(Colas).
  • Seleccionar la Cola de atención previamente creada.

Paso 4.

Crear nuestra página web que será desde donde nuestros usuarios van a contactarnos(el diseño de la página web queda al gusto y capacidades de cada quien). 

Para usar la API de SIPML5 pueden checar el ejemplo online con el que ellos cuentan o su documentación. Les dejo el código con el que llevo trabajando en algunos proyectos y el cual estoy usando en este artículo:

<head>
       <title>Navaismo's ElastixCC-WebRTC</title>
    <!-- SIPMl5 API for WEBRTC calls -->
        <script src="js/SIPml-api.js"></script>
        <link href="css/bootstrap.min.css" media="screen" rel="stylesheet"></link>
</head>

<script>
      

 //Variables
 var mySipStack;
        var mycallSession;
 var myregisterSession;

 // readycallback for INIT
 var readyCallback = function(e){
  console.log("engine is ready");
  
  //CHeck if the SIPml start
  if(SIPml.isInitialized() == 1){
   console.log("Done to initialize the engine");
   //If the stack is started, create the sip stack
   startSipStack();
  }else{
   //If not started display console msg
   console.log("Failed to initialize the engine");
  }          
 }

 // error callback for INIT
 var errorCallback = function(e){
  console.error('Failed to initialize the engine: ' + e.message);
 }

 //INIT SIPML5 API
 SIPml.init(readyCallback, errorCallback);

 //Here we listen stack messages
 function listenerFunc(e){
  //Log incoming messages
  tsk_utils_log_info('==stack event = ' + e.type);
  
  switch(e.type){
  
   //If failed msg or error Log in console & Web Page
   case 'failed_to_start': case 'failed_to_stop':  case 'stopping': case 'stopped': {

    console.log("Failed to connect to SIP SERVER")
    mycallSession = null;
    mySipStack = null;
    myregisterSession = null;
 
    $("#mysipstatus").html('');
    $("#mysipstatus").html('<i>Disconnected: </i>'+e.description);
    
    break;
   }

   //If the msg is 'started' now try to Login to Sip server           
          case 'started': {
                  console.log("Trying to Login");
                 
    login();//function to login in sip server
 
    //Display msg in the web page
    $("#mysipstatus").html('');
    $("#mysipstatus").html('<i>Trying to Connect</i>');
   
    break;
   }

   //If the msg 'connected' display the register OK in the web page 
   case 'connected':{
    $("#mysipstatus").html('');
    $("#mysipstatus").html('<i>Registered with Sip Server</i>');

    break;
   }

   //If the msg 'Sent request' display that in the web page---Pattience
   case 'sent_request':{

    $("#mysipstatus").html('');
    $("#mysipstatus").html('<i>'+e.description+'</i>');

    break;
   }

   //If the msg 'terminated' display that on the web---error maybe?
   case 'terminated': {
    $("#mysipstatus").html('');
    $("#mysipstatus").html('<i>'+e.description+'</i>');
    
    break;
   }

   //If the msg 'i_new_call' the browser has an incoming call
   case 'i_new_call': {
     if (mycallSession) {
                          // do not accept the incoming call if we're already 'in call'
                          e.newSession.hangup(); // comment this line for multi-line support
                  }else{
                                
     mycallSession = e.newSession;
    
     //Change buttons values
                   btnCall.value = 'Answer';
                  btnHangUp.value = 'Reject';
                          btnCall.disabled = false;
                          btnHangUp.disabled = false;
 
     //Start ringing in the browser
                          startRingTone();
    
     //Display in the web page who is calling
                   var sRemoteNumber = (mycallSession.getRemoteFriendlyName() || 'unknown');
                   $("#mycallstatus").html("<i>Incoming call from [<b>" + sRemoteNumber + "</b>]</i>");
                   showNotifICall(sRemoteNumber);
    }
    break;
   }

   case 'm_permission_requested':{
                         break;
                 }
              case 'm_permission_accepted':
          case 'm_permission_refused': {
    if(e.type == 'm_permission_refused'){

            btnCall.value = 'Call';
            btnHangUp.value = 'HangUp';
            btnCall.disabled = false;
            btnHangUp.disabled = true;

            mycallSession = null;

            stopRingbackTone();
            stopRingTone();

             $("#mysipstatus").html("<i>" + s_description + "</i>");

                      }
                      break;
                 }
   case 'starting': default: break;
         }            
 }

 //Function to Listen the call session events
 function calllistener(e){
  //Log all events
  tsk_utils_log_info('****call event**** = ' + e.type);

  switch(e.type){

   //Display in the web page that the call is connecting
   case 'connected': case 'connecting': {

        var bConnected = (e.type == 'connected');
                      if (e.session == myregisterSession) {                          
                           $("#mycallstatus").html("<i>" + e.description + "</i>");
    
                      }else if (e.type == 'connecting') {                          
                           $("#mycallstatus").html("<i>" + e.description + "</i>");
                  
    }else if (e.session == mycallSession) {
                          btnHangUp.value = 'HangUp';

                         if (bConnected) {
                           stopRingbackTone();
                           stopRingTone();
     }
                             }
    break;
                        }
  
   //Display in the browser teh call is finished
   case 'terminated': case 'terminating': {
  
    if (e.session == mycallSession) {
                          mycallSession = null;
                         myregisterSession = null;

                          $("#mycallstatus").html("<i>" + e.description + "</i>");
            stopRingbackTone();
            stopRingTone();


                 }else if (e.session == mycallSession) {

            btnCall.value = 'Call';
            btnHangUp.value = 'HangUp';
            btnCall.disabled = false;
            btnHangUp.disabled = true;

            mycallSession = null;

            stopRingbackTone();
            stopRingTone();
                        }
    break;
                
   }  

   // future use with video
           case 'm_stream_video_local_added': {
                      if (e.session == mycallSession) {

                          }
                      break;
                 }
 
   //future use with video
                case 'm_stream_video_local_removed': {
                      if (e.session == mycallSession) {

                          }
                      break;
                 }
   
   //future use with video
         case 'm_stream_video_remote_added':  {
                      if (e.session == mycallSession) {

                          }
                         break;
              }
  
   //future use with video
          case 'm_stream_video_remote_removed': {
                      if (e.session == mycallSession) {

                          }
                      break;
                 }
 
   //added media audio todo messaging
                 case 'm_stream_audio_local_added':
   case 'm_stream_audio_local_removed':
          case 'm_stream_audio_remote_added':
          case 'm_stream_audio_remote_removed': {
         
                  stopRingTone();                   
                         stopRingbackTone();
 
                      break;
                 }

   //If the remote end send us a request with SIPresponse 18X start to ringing
   case 'i_ao_request':{
                         var iSipResponseCode = e.getSipResponseCode();
                         if (iSipResponseCode == 180 || iSipResponseCode == 183) {
                   startRingbackTone(); //function to start the ring tone
     $("#mycallstatus").html('');
                              $("#mycallstatus").html('<i>Remote ringing...</i>');
     $("#btnHangUp").show();
                         }
    break;
   }
           
   // If the remote send early media stop the sounds
   case 'm_early_media': {
                  if (e.session == mycallSession){ 
           stopRingTone();                   
                          stopRingbackTone();
     $("#mycallstatus").html('');
     $("#mycallstatus").html('<i>Call Answered</i>');
    }
    break;
   }
                }

 }

 //function to send the SIP Register
 function login(){
  //Show in the console that the browser is trying to register
  console.log("Registering");
  
  //create the session
         myregisterSession = mySipStack.newSession('register', {
                 events_listener: { events: '*', listener: listenerFunc } // optional: '*' means all events
                });

  //send the register
        myregisterSession.register();
 }

 // function to create the sip stack
 function startSipStack(){
  //show in the console that th browser is trying to create the sip stack
  console.info("attempting to start the SIP STACK");

  //stack options
  mySipStack  = new SIPml.Stack({
          realm: 'asterisk',
          impi: 'usuario',
          impu: 'sip:usuario@myip',
          password: 'mipassword', // optional
          display_name: 'TS', // optional
          websocket_proxy_url: 'ws://miip:10060', // optional
          outbound_proxy_url: 'udp://miip:5060', // optional
          //ice_servers: [{ url: 'stun:stun.l.google.com:19302'}, { url:'turn:user@numb.viagenie.ca', credential:'myPassword'}], // optional
          enable_rtcweb_breaker: true, // optional
          enable_click2call: false, // optional
          events_listener: { events: '*', listener: listenerFunc }, //optional
          sip_headers: [ //optional
              {name: 'User-Agent', value: 'DM_SIPWEB-UA'}, 
              {name: 'Organization', value: 'Digital-Merge'}
          ]
      });
  //If the stack failed show errors in console
  if (mySipStack.start() != 0) {
                 console.info("Failed to start Sip Stack");
             }else{
                 console.info("Started the Sip Stack");
  }
 
 }


 //Fucntion to call/answer
 function call(){
  var calltype;

  if(mySipStack){
   //create the session to call
          mycallSession = mySipStack.newSession('call-audio', {
                  audio_remote: document.getElementById('audio_remote'),
                  audio_local: document.getElementById('audio_local'),
                  video_remote: document.getElementById('video_remote'),
                  video_local: document.getElementById('video_local'),
                      events_listener: { events: '*', listener: calllistener } // optional: '*' means all events
                 });
   $("#mycallstatus").show();
   //call using the number 80000
          mycallSession.call("80000");
  }else if(!mySipStack){
   alert('Stack not ready!');
  
  //If the textbox is empty and the button call is ANSWER, then is a incoming call
  }else if(flag =='Answer' && mySipStack && mycallSession){

                        stopRingbackTone();
                        stopRingTone();                   

   //Accept the session call
   mycallSession.accept({
                                audio_remote: document.getElementById('audio_remote'),
                                audio_local: document.getElementById('audio_local'),
                                events_listener: { events: '*', listener: calllistener } // optional: '*' means all events
                        });
  }
 }


 //function to hangup the call
 function hangup(){
  //If exist a call session, hangup and reset button values
  if(mycallSession){
          mycallSession.hangup({events_listener: { events: '*', listener: calllistener }});
                        stopRingbackTone();
                        stopRingTone();                   
                        btnCall.value = 'Call';
                        btnHangUp.value = 'HangUp';
   $("#callnumber").attr('value','');
   $("#mycallstatus").html("Call Terminated")
   $("#btnHangUp").hide();
   //destroy the call session
   mycallSession = null;

  }else{
   $("#callnumber").attr('value','');
  }   
  
 }

 //Fucntion to send DTMF frames
 function sipSendDTMF(c){
         if(mycallSession && c){
              if(mycallSession.dtmf(c) == 0){
                  try { dtmfTone.play(); } catch(e){ }
              }
         }else{
   var lastn = $("#callnumber").val();

   $("#callnumber").val( lastn + c );
          try { dtmfTone.play(); } catch(e){ }

  }
  
   
     }


/**************** fucntion to play sounds *******************/
    function startRingTone() {
        try { ringtone.play(); }
        catch (e) { }
    }

    function stopRingTone() {
        try { ringtone.pause(); }
        catch (e) { }
    }

    function startRingbackTone() {
        try { ringbacktone.play(); }
        catch (e) { }
    }

    function stopRingbackTone() {
        try { ringbacktone.pause(); }
        catch (e) { }
    }


   function showNotifICall(s_number) {
        // permission already asked when we registered
        if (window.webkitNotifications && window.webkitNotifications.checkPermission() == 0) {
            if (oNotifICall) {
                oNotifICall.cancel();
            }
            oNotifICall = window.webkitNotifications.createNotification('images/sipml-34x39.png', 'Incaming call', 'Incoming call from ' + s_number);
            oNotifICall.onclose = function () { oNotifICall = null; };
            oNotifICall.show();
        }
    }

</script>

<div class="col">
            <h2>Llamenos Usando WebRTC</h2>
ll <audio id='audio_remote'></audio>
                        &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
   <button class="btn btn-primary" id="btnCall" onclick="call()" >Click Aquí Para Llamarnos!</button>
                        <br> &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
   <button class="btn btn-danger hide" id="btnHangUp" onclick="hangup()" >Colgar Llamada</button><br>
                        &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp;
   <span class="label hide" id="mycallstatus"></span>
                        <br> &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; 
   <span class="label label-inverse" id="mysipstatus">Si ve esta etiquete usted necesita usar Chrome para llamar</span>
   <br><br>
                        <p style="font-size: 10px;">WebRTC es una nueva tecnología que usa el Navegador Web para establecer una llamada, en este caso la llamada es de Audio y Usted necesita contar con una diadema(o bien micrófono y bocinas) para llamarnos sin costo alguno.<br>Esta tecnología por ahora solo es compatible con el navegador Chrome para descargarlo <a href="">de click aqui.</a>
                </div>
                <div class="col"> 
    <!-- Audios -->
    <audio id="audio_remote" autoplay="autoplay" />
    <audio id="ringtone" loop src="sounds/ringtone.wav" />
    <audio id="ringbacktone" loop src="sounds/ringbacktone.wav" />
    <audio id="dtmfTone" src="sounds/dtmf.wav" />




Si van a usar el ejemplo anterior sólo tienen que editar el siguiente código con los datos de su dispositivo SIP.

 //stack options
mySipStack  = new SIPml.Stack({
          realm: 'asterisk',
          impi: 'usuario',
          impu: 'sip:usuario@myip',
          password: 'mipassword', // optional
          display_name: 'TS', // optional
          websocket_proxy_url: 'ws://miip:10060', // optional
                        outbound_proxy_url: 'udp://miip:5060', // optionalk


Y lo más importante es apuntar el número de la llamada hacia nuestra cola de atención previamente creada, eso se logra editando el código(en éste ejemplo la cola creada tiene el número 80000):

//call using the number 80000
  mycallSession.call("80000");

Al finalizar tendrán algo como esto:



Paso 5.

Probar el sistema. Video Demo(sin audio por problemas técnicos):




Notas.

Es claro que este ejemplo sólo usa un peer SIP, sin embargo estos son algunos hints para crear múltiples usuarios.
  • Crear una página web de atención donde primero verifique los peers registrados.
  • Basados en el punto anterior elegir qué peer registrar.
  • Crear un formulario para que el usuario registre algunos datos básicos como su nombre y pasar estos datos con el peer de registro.
  • Adaptar realtime o bien escribir directamente en la base de datos de asterisk para crear peers 'dinámicos'.
  • Crear una página web para clientes/usuarios registrados y crear extensiones/peers basados en estos datos, así mismo se pueden crear campañas entrantes con estos datos. 

El registro no es necesario realmente, pero de esta forma demuestro que es posible interactuar con asterisk y WebRTC.
WebRTC no necesita de Asterisk para lograr esto, de hecho lo puede hacer Peer to Peer(punto a punto) como lo haría la fantástica aplicación llamada Twelephone, sin embargo este artículo esta diseñado para integrar un sistema de atención online con Elastix y su módulo de call center.
Se puede añadir video a las llamadas ;).
Asterisk Plano también es capaz de esto(y más) usando extensiones directamente o bien una cola de atención. Es solo que estoy de buenas con Elastix. :D
Para aquellos que no sólo quieran tener soporte en Chrome existe una extensión que permite a los navegadores como Internet Explorer y Opera funcionar con WebRTC se llama webrtc4all.

Recuerden su feedback es bienvenido.

miércoles, 26 de junio de 2013

Integrando WebRTC al Módulo de Call Center de Elastix

ACTUALIZACIÓN 25/Nov/2013: 



Ya está disponible como Addon de Elastix se puede acceder desde el menú de addons del PBX o bien desde http://addons.elastix.org/index.php 



Hola, quienes me conocen saben que yo prefiero usar Asterisk Plano sobre cualquier cosa. Cuando la gente me dice que quieren usar sí o sí una GUI para administrar su asterisk porque no se quieren meter en líos, recomiendo FreePBX. Son pocas las veces que me veo en la necesidad de usar Elastix o recomendarlo de hecho me puedo considerar hasta cierto punto un 'hater' de esa Distro; sin embargo, como dice la canción: "...No todo es blanco, O negro: es gris Todo depende del matiz...", debo decir que una de las cualidades más atractivas de Elastix es su Módulo de Call Center. El cual permite crear campañas de Salida o de Entrada de manera más que sencilla y que su operación es muy pulcra, sin contar además que, integrar nuestro propio CRM es sumamente sencillo.

Este año vendrá de nueva cuenta el ElastixWorld a México y esta vez si pienso asistir ya que dejar pasar las conferencias de las personalidades que vendrán sería un error y más si tomo en cuenta que asistir al Astricon en Atlanta es prácticamente imposible para mi. Es por esta razón que decidí "contribuir"(si se le puede llamar así a una simple edición de código) un poquito al proyecto de Elastix, no quiero ir solo a recibir información valuable al ElastixWorld sin haber "contribuido" en algo.

Elegí un tema de "moda": WebRTC y una herramienta útil como el Módulo de Call Center que combinadas podrían tener mucho éxito-según yo-. Así que me di a la tarea de integrar la fantástica API SIPML5 y el Gateway WebRTC2SIP ambos de Doubango a una instalación de Elastix.

Notas sobre la Integración y Operación.


  • Se requiere una versión de 'libtool' más reciente de modo que se instala la version 2.4.2
  • Se requiere instalar el Gateway WebRTC2SIP para su operación ya que la versión de Asterisk no soporta WebRTC. La conexión es ws.
  • Solo funciona correctamente con el explorador Google Chrome.
  • Se modifican algunos archivos(consideren respaldarlos antes) utilizados por Elastix entre ellos:
    • /var/www/hmtl/themes/elastixneo/_common/_menu.tpl
    • /var/www/html/modules/agent_console/index.php
    • /var/www/html/agent_console/themes/default/agent_console.tpl

  • La Integración solo funciona con el tema default 'Elastixneo'.
  • Debido a mi falta de conocimiento tuve que crear una conexión externa a MySQL para obtener el password del 'Peer'.
  • Se han creado algunos archivos extras:
    • Conexión a la BD: /var/www/html/libs/astdb.php
    • Javascript para el Teléfono: /var/www/html/modules/agent_console/themes/default/js/ml5.js
    • SIPml5 API: /var/www/html/modules/agent_console/themes/default/js/SIPml-api.js
    • Estilos para botones del Teléfono: /var/www/html/modules/agent_console/themes/default/css/bootstrap.css
    • Sonidos utilizados por el Teléfono: /var/www/html/modules/agent_console/themes/default/sounds

  • No se puede marcar desde el Teléfono. Esto lo hice así pensando en que el agente sólo debe de recibir las llamadas de una cola Entrante o una campàña del Dialer(Se puede modificar a futuro).
  • Si no se esta en llamada y se presiona el botón (CALL/Answer) se enviará la llamada al buzón de voz del Agente.
  • La integración esta hecha para trabajar  solamente en modo 'Agent CallBack'. Esto se debe a la facilidad de tener el Teléfono registrado en 'Stand-by' una vez que se accedió a la consola del Agente. 
El gateway WebRTC2SIP corre bajo una sessión 'screen' con el comando:
screen -dmS wrtc /usr/local/sbin/webrtc2sip --config=/usr/local/sbin/config.xml

Para ingresar a la sesión usar:
screen -r wrtc

Para Salir de la sesión sin detener el gateway usar la combinación de teclas CRTL+A+D.
Para detener el Gateway, entrar en la sesión y escribir 'quit'+ENTER.


Instalación.

Actualización 17/Sep/2013:
Este es el último RPM usado para adaptar chat y marcado libre al módulo de call center.

Servicio de Chat habilitado.




Actualización 04/Sep/2013:

He creado paquetes RPMs para que la instalación sea mucho mas rapida, aqui dejo las instrucciones para instalar via RPMs:

Para instalar desde la consola de Linux seguir estos pasos:
 cd /usr/src/
 yum install -y screen perl-WWW-Curl elastix-callcenter
 wget https://dl.dropboxusercontent.com/u/1277237/SIPML5toElastix.zip
 unzip SIPML5toElastix.zip
 cd SIPML5toElastix
 rpm -ihv libtool-2.4.2-DMv1.i386.rpm
 rpm -ihv libsrtp-1.4.5-DMv1.i386.rpm
 rpm -ihv --force openssl-1.0.1c-DMv1.i386.rpm
 rpm -ihv yasm-1.2.0-DMv1.i386.rpm
 rpm -ihv libvpx-1.2.0-DMv1.i386.rpm
 rpm -ihv x264-snapshot20130810.2245-DMv1.i386.rpm
 rpm -ihv ffmpeg-1.2-DMv1.i386.rpm
 rpm -ihv doubango-2.0r985-DMv1.i386.rpm
 rpm -ihv webrtc2sip-2.5.1r114-DMv1-Elastix.i386.rpm
 rpm -ihv --force sipml5_elastix_cc-0.1-DMv1.i386.rpm

Si se cuenta con las versiones más recientes de Elastix y el módulo de Call Center instalar los RPMs hasta el punto 14 y después ejecutar los siguientes comandos :

 yum reinstall -y elastix-framework elastix-callcenter
 rpm -ihv https://dl.dropboxusercontent.com/u/1277237/sipml5_elastix_cc-0.1b-DMv1.i386.rpm

Los pasos anteriores corrigen el bug que elimina los submenús de la barra de Menús y corrige también que no se registre el cliente SIPML5.


Instalación antigua:

 Descargar el tarball desde este enlace. Y extraer el contenido con el comando:
# tar zxvf Elastix_CC_ML5.tar.gz


Cambiar al directorio creado con:
# cd ccml5


Instalar el gateway webrtc2sip.

Para instalar el Gateway ejecutar el siguiente comando:
# ./install_wrtcgw.sh

Esto instalará el framework de doubango, las dependencias y el gateway, dependiendo de la velocidad del equipo puede tomar desde 30 minutos hasta un par de horas. 

Añadir el Teléfono WebRTC a la consola de Agente.

Para integrar el teléfono a la consola de agente ejecutar el siguiente comando:
# ./install_webfiles.sh

Si no se ha instalado el Módulo de Call Center previamente, el script preguntará si desean instalarlo. Basta presionar Y+enter para que así lo haga.

Inicio de Sesión Consola de Agente modo 'Callback login'

Consola de Agente con el Telefóno WebRTC Insertado 


Demo.

Para configurar y usar el Módulo del Call Center de Elastix pueden usar el manual oficial que esta en este enlace Recuerden que el Teléfono WebRTC solo trabajará correctamente en el modo 'callback login' en el manual se refieren a él como modo dinámico.

Yo ya tengo preparado una Cola de Entrada sin datos y una Campaña saliente con un CRM Custom.   Y este es el Resultado:

Inbound Queue:



Outgoing Campaign:



Feedback: Como nada en esta vida es perfecto cualquier feedback se agradecerá. ;)

viernes, 24 de mayo de 2013

Instalando Soporte WebRTC en FreePBX

UPDATE 09/02/2013: He quitado los enlaces ya que FreePBX ya lanzo un módulo oficial: http://www.freepbx.org/news/2014-01-27/webrtc-softphone-module-now-available-for-freepbx


Hola, he creado un script para instalar el media gateway de Doubango WebRTC2SIP en un sistema FreePBX basado en CentOS, Fedora o La Distro de FreePBX.

El gateway permitirá de manera sencilla tener integración con WebRTC basado en la API SIPML5 con conexión a Asterisk; además de que permite el funcionamiento correcto del módulo emergencyphones-0.1.b3  creado anteriormente para dotar de un télefono web a los usuarios y administradores de FreePBX.

También he creado un módulo para poder cambiar la configuración del gateway WebRTC2SIP desde la Página web de FreePBX.


Vista de la página de configuración del Gateway con Warning de Archivo.

Vista de la Página con Hint y Configuración completa.

El paquete que contiene las instrucciones de instalación, el script para instalar el gateway y ambos módulos lo pueden descargar de este enlace.

viernes, 5 de abril de 2013

Módulo: Telefono WebRTC para FreePBX usando SIPML5

UPDATE 09/02/2014: He eliminado los enlaces ya que FreePBX ha sacado su módulo oficial: http://www.freepbx.org/news/2014-01-27/webrtc-softphone-module-now-available-for-freepbx


Hola, aquí les dejo un módulo(bastante mal hecho pero funcional) que provee un teléfono de software utilizando SIPML5 embebido en FreePBX.

La idea de este módulo es dar a los usuarios/clientes la posibilidad de un teléfono de emergencia en caso que las troncales locales fallen. El teléfono necesita registrarse a un server SIP remoto para poder operar correctamente.

La instalación es bastante sencilla basta con ir a MODULE ADMIN---->Upload y seleccionar el modulo para instalarlo.

Una vez instalado se creara un enlace dentro de la categoría Conectividad(Connectivity).

Las pruebas las he hecho con mi FreePBX 2.11 registrando a mi Asterisk en la RaspberryPi usando el gateway webrtc2Sip de doubango. Todo el feedback es apreciado.

En el futuro espero mejorar el código del módulo.

Imagen del Modulo:






























Actualización 17/04/2013:

Usando el archivo CSS del dashboard actualicé la apariencia, para que se viera más decente.
Dejo un vídeo de como se instala el modulo y su funcionamiento:






Actualización 22/04/2013:

Se añadió esta función a la página del usuario(ARI) sin opción a edición de datos. Por lo que el administrador deberá registrar por primera vez el módulo.

Vista de la Página de Usuario(ARI).

Vista de la Página Admin sin registrar.

Vista de la Página de Admin con detalles.

Vista de la página de Admin teléfono Registrado.































































Actualización 29/04/2013:
Se añadió el al módulo de ARI un softphone que se registra usando las credenciales del usuario, registrándose en el servidor local.

Cambio el nombre del módulo y los menús en la sección ARI.



Descarga el modulo dando click aquí.




viernes, 7 de diciembre de 2012

Asterisk con Alta-Disponibilidad + MySQL

En algunas ocasiones nos vemos en la necesidad de crear un Cluster de alta disponibilidad para nuestros servicios de Asterisk. A continuación se describen los pasos necesarios para llevar esto acabo en nuestros servidores usando Asterisk y MySQL(por si queremos usar Asterisk Realtime Architechture).

Estos pasos están basados en las instrucciones que brinda Digium y los tutoriales de DRBD para MySQL.
Este tutorial no esta hecho para hacer copy&paste.
El color verde indica que son pasos para realizar en ambos servidores.
El color Naranja indica que son pasos para realizar en el servidor primario.
El color Rojo indica que son pasos para realizar en el servidor secundario.
El Hostname del Servidor primario es "node1".
El Hostname del Servidor secundario es "node2".
La dirección IP del servidor primario es 10.0.1.51
La dirección IP del servidor secundario es 10.0.1.52
La dirección IP compartida del cluster y a la que deberán apuntar los servicios(como el registro de teléfonos, MySQL o apache) es 10.0.1.50.
La dirección IP del Gateway es 10.0.1.1.
Se puede adaptar fácilmente el Hardware Failover que provee Digium(rseries) y los servicios de Apache.



La Imagen anterior describe el funcionamiento del Cluster:
-- Escenario 1: El servidor primario esta activo y el secundario esta en modo pasivo esperando.
-- Escenario 2: El servidor Primario ha entrado en estado de falla(por conexión de RED o por reinicio o falla en el kernel), el servidor secundario entonces, se convierte en el servidor primario y es marcado como activo.
-- Escenario 3: El servidor secundario(antes primario) se ha recuperado de la falla y ha entrado en modo pasivo.
-- Escenario 4: El servidor primario(antes secundario)  ha presentado falla  y el servidor secundario(antes primario) es marcado como servidor primario nuevamente y entra en modo activo.


Paso 1 ---- Realizar en Ambos Servidores:

Instala CentOS 5.X(Los agentes de recursos "ocf"de Digium no son compatibles con las versiones 6.X de CentOS). Escoger el modo de partición manual y dejar un espacio libre sin formateo ni nada, en este tutorial yo he dejado 5GB sin particionar. Este espacio será donde guardemos nuestros datos a replicar en el Cluster, así que deberán considerar cuanto espacio necesitaran para sus archivos y logs.

Paso 2 ---- Realizar en Ambos Servidores:

Instala las dependencias para nuestros servidores. Primero añadiré el repositorio de rpmforge:
#rpm -ihv http://pkgs.repoforge.org/rpmforge-release/rpmforge-release-0.5.2-2.el5.rf.[ARCH].rpm
Cambia [ARCH] por la arquitectura del servidor: i386 o x86_64.

Ahora instala las dependencias:
#yum -y install kernel kernel-devel libxml2  libxml2-devel  libtiff  libtiff-devel  lame httpd  mysql mysql-devel mysql-server php  php-pear  php-mysql  php-gd openssl openssl-devel perl  bison ncurses-devel audiofile-devel curl sox gcc newt-devel libusb-devel glibc-devel  zlib-devel svn gcc-c++ subversion make nano wget cfdisk


Paso 3 ---- Realizar en Ambos Servidores:

Ejecuta el comando:
#cfdisk

Obtendrás una ventana como esta:

Selecciona el espacio libre y crea una nueva partición con todo el espacio libre usando los botones: New, Primary, Write. Al finalizar te preguntara si quieres efectuar los cambios, escribe la palabra: yes y da enter.

Cuando el proceso termine reinicia el servidor:
#reboot  

Una vez que el servidor haya arrancado de nuevo hay que limpiar la nueva partición usando el siguiente comando:
#dd if=/dev/zero of=/dev/[hs]da[#] bs=1M; sync
Cambia /dev/[hs]da[#] por tu dispositivo que puede ser SDA3,HDA3 o SDB3 HDB3 etc. En este tutorial es HDA3.

Veras una salida similar cuando termine el proceso, dependiendo del tamaño de tu partición y de la velocidad de tus discos duros puede tomar minutos u horas.


Crea un nuevo directorio en /usr/src:
#cd /usr/src/
#mkdir asterisk
#cd asterisk/


En el nuevo directorio descarga Asterisk y sus componentes:
#wget http://downloads.asterisk.org/pub/telephony/dahdi-linux-complete/dahdi-linux-complete-2.6.1+2.6.1.tar.gz
#wget http://downloads.asterisk.org/pub/telephony/libpri/libpri-1.4.13.tar.gz
#wget http://downloads.asterisk.org/pub/telephony/certified-asterisk/certified-asterisk-1.8.11-current.tar.gz
#wget http://downloads.digium.com/pub/telephony/rseries/rseries-current.tar.gz

Descomprime los archivos fuente:
#for i in `ls *gz`; do tar zxvf $i; done

Compilamos DAHDI:
#cd dahdi-linux-complete-2.6.1+2.6.1
#make && make all && make install && make config

Compilamos LibrPRI y configuramos DAHDI:
#cd ../libpri-1.4.13
#make && make install
#service dahdi start
#dahdi_genconf
#dahdi_cfg -vvvvv

Instalamos los agentes de Digium para el cluster:
#cd ../rseries-1.0.0/
#make && make install


Paso 4 ---- Realizar solo en  Servidor Primario..

Instalar Asterisk en el servidor primario:

#cd ../certified-asterisk-1.8.11-cert9
#contrib/scripts/get_mp3_source.sh
#./configure && make menuselect


En el menú selecciona las aplicaciones que quieras disponibles en tu asterisk:

Guarda los cambios y compila Asterisk:
#make && make install && make config && make samples

Inicia el servicio de Mysql y detenlo esto solo para crear las bases de datos por default.
#service mysqld start
#service mysqld stop


Paso 5 ---- Realizar en Ambos Servidores.

Cambiate al directorio de rseries:
#cd /usr/src/asterisk/rseries-1.0.0/

Instala los servicios de DRBD:
#yum -y install drbd83 kmod-drbd83

Instala Libesmtp, dependencia necesaria para Pacemaker:
#wget http://dl.fedoraproject.org/pub/epel/5/[ARCH]/libesmtp-1.0.4-5.el5.[ARCH].rpm #64bits
Cambia [ARCH] por la arquitectura del servidor: i386 o x86_64.

Instala PaceMaker y Corosync:
#wget -O /etc/yum.repos.d/pacemaker.repo http://clusterlabs.org/rpm/epel-5/clusterlabs.repo
#yum -y install -x heartbeat-stonith* pacemaker corosync
Estamos exluyendo el paquete heratbeat-stonith ya que cre un conflicto con pacemaker.

Instala los archivos de configuración que provee Digium:
#make samples

Agrega estas lineas al archivo /etc/drbd.conf:
echo "include \"drbd.d/global_common.conf\";" >> /etc/drbd.conf
echo "include \"drbd.d/*.res\";" >> /etc/drbd.conf

Edita el archivo /etc/drbd.d/asterisk.res. Cambia astnode1 por el nombre el hostname del servidor primario, cambia astnode2 por el hostname del servidor secundario, cambia /dev/sda3 por la partición que creamos con el espacio libre(en este tutorial hda3). Cambia las IPs por las de tus servidores primarios y secundarios.Cambia el correo electrónico por el tuyo o el administrador del sistema en este tutorial el archivo quedo así:
resource asterisk {
  handlers {
    split-brain "/usr/lib/drbd/notify-split-brain.sh clusteradmin@example.com";
  }

  net {
    after-sb-0pri discard-younger-primary;
    after-sb-1pri discard-secondary;
    after-sb-2pri disconnect;
  }

  on node1 {
    device    /dev/drbd0;
    disk      /dev/hda3;
    address   10.0.1.51:7789;    
    meta-disk internal;
  }
  on node2 {
    device    /dev/drbd0;
    disk      /dev/hda3;
    address   10.0.1.52:7789;
    meta-disk internal;
  }
}

Crea el Recurso llamado asterisk e inicia el servicio de DRDB:
#drbdadm create-md asterisk
#service drbd start


Paso 6 ---- Realizar solo en  Servidor Primario.

A continuación crea el UUID llamado Asterisk, y formatea la partición que será usada por el cluster tipo: EXT3
#drbdadm disconnect asterisk
#drbdadm -- --clear-bitmap new-current-uuid asterisk
#drbdadm -- --overwrite-data-of-peer primary asterisk
#mkfs.ext3 -m0 /dev/drbd0
#drbdadm secondary asterisk
#drbdadm detach asterisk
#drbdadm up asterisk


Crea el directorio que será usado para el cluster, pero antes marca como nodo primario para poder montarlo:
#drbdadm primary asterisk
#mkdir /mnt/asterisk

Monta la partición:
#mount -t ext3 /dev/drbd0 /mnt/asterisk

Ahora crea el directorio donde estará Mysql:
#mkdir /mnt/asterisk/mysql
#mkdir /mnt/asterisk/mysql/data
#cd /mnt/asterisk/mysql

Copia el contenido del directorio de Mysql al nuevo directorio:
#cp -aR /var/lib/mysql/* /mnt/asterisk/mysql/data
#ls data/

Mueve el archivo /etc/my.cnf al nuevo directorio de Mysql:
#mv /etc/my.cnf .

Crea un enlace simbólico al nuevo directorio:
#ln -s /mnt/asterisk/mysql/my.cnf /etc/
Edita el archivo my.cnf y cambia la directiva de DATADIR: datadir=/mnt/asterisk/mysql/data.

Crea un archivo dummy solo para verificar la replicación en el nodo secundario.
#touch test11

Desmonta la partición y marca el nodo como secundario:
#cd --
#umount /mnt/asterisk
#drbdadm secondary asterisk


Paso 7 ---- Realizar solo en Servidor Secundario

Elimina el archivo /etc/my.cnf
#rm -rf /etc/my.cnf

Vamos a verificar que los archivos de nuestra partición en el cluster se repliquen. Marca el servidor como primario y crea el mismo directorio:
#drbdadm primary asterisk
#mkdir /mnt/asterisk

Monta la partición y cambiate al directorio:
#mount -t ext3 /dev/drbd0 /mnt/asterisk
#cd /mnt/asterisk

Haz un enlace simbolico del archo my.cnf al directorio de la partición del cluster:
#ln -s /mnt/asterisk/mysql/my.cnf /etc/
Verifica que el archivo my.cnf sea el mismo que editamos anteriormente, es decir, que contenga:  datadir=/mnt/asterisk/mysql/data

Verifica que los archivos de Mysql y el archivo test11 existan en el directorio:
#ls

Si los archivos existen la replicación va de maravilla.

Desmonta la partición y marca el nodo como secundario:
#cd --
#umount /mnt/asterisk
#drbdadm secondary asterisk


Paso 8 ---- Realizar solo en  Servidor Primario.

Cambiate al directorio de rseries, mara el servdiro como primario y monta la partición:
#cd /usr/src/asterisk/rseries-1.0.0
#drbdadm primary asterisk
#mount -t ext3 /dev/drbd0 /mnt/asterisk/

Ejecuta el script cretalinks.sh:
#./createlinks.sh
Veras una salida como la siguiente:


Elimina el script de asterisk para que no arranque automáticamente cuando inicie el sistema, desmonta la partición y marca el nodo como secundario:
#chkconfig --del asterisk
#umount /mnt/asterisk
#drbdadm secondary asterisk


Paso 9 ---- Realizar solo en Servidor Secundario

Cambiate al directorio de rseries, marca el nodo como primario, monta la partición y ejecuta el script createlinks.sh:
#cd /usr/src/asterisk/rseries-1.0.0
#drbdadm primary asterisk
#mount -t ext3 /dev/drbd0 /mnt/asterisk/
#./createlinks.sh
Veras una salida como en la imagen anterior.

Cambiate al directorio de Asterisk, configura las mismas opciones que se configuraron en el servidor primario. Compilalo solo ejecutando make && make install. Desmonta la partición y marca el nodo como secundario.
#cd ../certified-asterisk-1.8.11-cert9
#contrib/scripts/get_mp3_source.sh
#./configure && make menuselect
#make && make install
#umount /mnt/asterisk/
#drbdadm secondary asterisk


Paso 10 ---- Realizar en Ambos Servidores.

Edita el archivo /etc/corosync/corosync.conf. Cambia la opción bindnetaddr, y las opciones memberaddr. Para este tutorial el archivo quedo de la siguiente manera:

totem {
        version: 2
        token: 3000
        token_retransmits_before_loss_const: 10
        join: 60
        consensus: 5000
        vsftype: none
        max_messages: 20
        clear_node_high_bit: yes
        secauth: off
        threads: 0
        rrp_mode: none

        interface {
                ringnumber: 0
                bindnetaddr: 10.0.1.0
                broadcast: yes
                mcastport: 5405
                member {
                        memberaddr: 10.0.1.51
                }
                member {
                        memberaddr: 10.0.1.52
                }
        }
}

aisexec {
        user:   root
        group:  root
}

logging {
        fileline: off
        to_stderr: yes
        to_logfile: no
        to_syslog: yes
        syslog_facility: daemon
        debug: off
        timestamp: on
        logger_subsys {
                subsys: AMF
                debug: off
                tags: enter|leave|trace1|trace2|trace3|trace4|trace6
        }
}

amf {
        mode: disabled
}

Inicia el servicio de Corosync y añade drbd y corosync al startup:
#service corosync start
#chkconfig drdb on
#chkconfig corosync on

Verifica el estado del Cluster con el siguiente comando:
#cat /proc/drbd
Veras una imagen como la siguiente:
Veras que se esta sincronizando la particion del cluster, también verás como Secondary/Secondary(no como en la imagen).

Si el proceso de sincronización reporta que tardará mucho tiempo puedes usar este comando para acelerar la velocidad de sincronización:
#drbdsetup /dev/drbd0 syncer -r 250M

La velocidad máxima de sincronización dependerá de la velocidad de tus tarjetas de red así como la velocidad de escritura de tus discos duros. Para más información de como calcular la velocidad ve a este enlace.

Una vez que el estado sea UpToDate/UpToDate reinicia los servidores:
#reboot


Paso 11 ---- Realizar solo en  Servidor Primario.

Edita el siguiente codigo para que:
-- node1 y node2. Sean los hostnames de tus servidores. En este ejemplo node1 y node2
-- ip bajo ClusterIP. Sea la Ip de tu Cluster, la IP flotante. En este ejemplo 10.0.1.50
-- cidr_mask bajo ClusterIP. Sea la mascara de tu red. en este ejemplo de 24bits(255.255.255.0)
- -host_list bajo GatewayStatus. Sea el gateway de tu red. en este ejemplo 10.0.1.1

node node1
node node2
primitive ClusterIP ocf:heartbeat:IPaddr2 \
        params ip="10.0.1.50" cidr_netmask="24" \
        op monitor interval="5"
primitive drbd ocf:linbit:drbd \
      params drbd_resource="asterisk" \
      op monitor start-delay="10" interval="5"  \
primitive drbd_fs ocf:heartbeat:Filesystem \
      params device="/dev/drbd0" directory="/mnt/asterisk/" fstype="ext3"
primitive mysqld lsb:mysqld
primitive Asterisk ocf:Digium:asterisk \
        op monitor interval="5"
primitive GatewayStatus ocf:pacemaker:ping \
        params host_list="10.0.1.1" multiplier="100" \
        op monitor interval="5" timeout="10"
ms drbd_ms drbd \
        meta master-max="1" master-node-max="1" clone-max="2" clone-node-max="1" notify="true"
clone GatewayStatusClone GatewayStatus
location Asterisk-with-ping Asterisk \
        rule $id="Asterisk-with-ping-rule" -inf: not_defined pingd or pingd lte 0
group mysql drbd_fs ClusterIP mysqld
colocation mysql_on_drbd inf: mysql drbd_ms:Master
order mysql_after_drbd inf: drbd_ms:promote mysql:start
colocation Everything-with-Asterisk inf: ( drbd_ms:Master )  ( ClusterIP drbd_fs )  Asterisk
order  Asterisk-after-Everything inf:   ( drbd_ms:promote ) ( ClusterIP drbd_fs )  Asterisk:start
property $id="cib-bootstrap-options" \
        cluster-infrastructure="openais" \
        expected-quorum-votes="2" \
        stonith-enabled="false" \
        no-quorum-policy="ignore"
rsc_defaults $id="rsc-options" \
        resource-stickiness="99"

Una vez que has editado el archivo Cluster_Mysql_Asterisk.cfg, actualiza la configuración de pacemaker:
#crm configure load update Cluster_Mysql_Asterisk.cfg

Si todo sale bien veras una salida como la siguiente y los servicios de MySQL y Asterisk se iniciaran en el nodo primario:

Para verificar que los servicios estén corriendo puedes revisar el estado del cluster con:
#crm_mon

Y verás algo así:



Demostración:

El siguiente video muestra las pruebas del cluster. Asterisk esta configurado con "static realtime" obteniendo los datos de una base de datos de MySQL. Este tutorial no cubre la cofiguración de ARA(Asterisk Realtime Architechture).




Herramientas para diagnosticar fallas:

Puedes ejecutar el siguiente comando en el nodo secundario para verificar que la configuración de pacemaker se replico:
#crm configure show

Para detener los servicios del cluster:
#crm configure property stop-all-resources=true

Para borrar la configuracion del cluster:
#crm configure erase

Verificar el estado del cluster:
#crm_mon

Verificar el estado de la sincronización y los roles de los servidores:
#cat /prco/drbd

Aumentar la velocidad de sincronización:
#drbdsetup /dev/drbd0 syncer -r 250M