De inmediato pregunté a sus creadores si el rumor era cierto. No obtuve respuesta.
Hoy por la mañana Elio Rojano a través de su sitio Sinologic.net ha confirmado lo peor: Elastix ha sido vendido a una empresa cerrada que trabaja bajo Windows y que ahora renombrará Elastix como 3CX for Linux
Lo que a mi me preocupa y que la «comunidad» no ha alcanzado a ver es que la cultura del Todo gratis mata lentamente los proyectos FOSS, nos pasó con Trixbox hace unos años, nos pasó con MySQL y existen varios ejemplos más de cómo proyectos se vienen abajo por nada más y nada menos que la plata.
Ya lo decía alguien -que no encuentro la fuente- en mi timeline de Twitter, que aquel que quiera hacer negocio basado en Open Source (OS) debe estar loco o engañandose. Y es cierto todas las comunidades de OS están repletas de esos usuarios que ven una oportunidad de venta sin sacrificio. Lo he repetido una y otra vez hablando de Elastix, siempre hubo usuarios que sólo entraban a los foros a preguntar «¿Cómo hago esto?» «¿Cómo implemento aquello?» «Tengo una empresa de VoIP y me urge resolver esto a mis clientes». En cuanto se les mencionaba el soporte de pago huían (en el mejor de los casos). En el peor decían que era obligación de los creadores dar soporte gratis. Yo lo viví cuando trabajé en 2 de las empresas más fuertes de VoIP de la Ciudad de México: recibí llamadas de gente exigiendo soporte gratuito porque Asterisk es OS.
Esa es la gente que daña a la comunidad. También está la otra cara de la moneda: aquellos que apoyan mediante donaciones, participando en foros de discusión, reportando bugs, creando parches, módulos, etcétera. Lamentablemente son los menos.
Ahora bien como lo comenta Christian en un tweet, habrá que esperar y ver si esto puede ser un paso a las mejoras:
Siempre he sido un escéptico de las versiones enlatadas y sé que esto para los que saben Asterisk puro no representa ninguna tragedia. Sin embargo, como la única distribución creada por latinos para latinos es un fuerte golpe a la comunidad de habla hispana, o una estrategia para solo dejar FreePBX en el mapa. Cualquiera que fuese el resultado, la ventaja de que Asterisk sea un toolkit es que cualquiera con mínimos conocimientos podría crear la nueva distro enlatada para ser el una de las alternativas.
Hola, como ya lo venía diciendo en mi cuenta de Twitter, en Enlaza Comunicaciones hemos estado trabajando en un Addon para los usuarios de Elastix. Después de algunas semanas, finalmente ha sido publicado en el sitio de Elastix Addons y ya está disponible para su descarga e instalación desde su Elastix (PDF de instrucciones [4.2MB]).
¿Pero de que va este Addon? En Enlaza tomamos mucho en cuenta la seguridad de los PBX dado que sabemos que al día se reciben cientos de ataques a PBX por los puertos expuestos de SIP, IAX, SSH, entre otros. Es por esta razón que nosotros usamos, siempre que es posible, una conexión a través de OpenVPN tanto para administrar los sistemas a los que les damos soporte como para crear extensiones remotas.
Como la mayoría de ustedes saben y conocen, OpenVPN es una herramienta Open Source que nos permite crear VPNs seguras entre nuestros equipos, esta herramienta es la más popular en ambientes Unix para crear túneles seguros entre nuestras redes. Si bien existen miles de tutoriales en la WEB para crear los túneles y configuraciones de OpenVPN y seguramente existen ya muchos usuarios con esta herramienta instalada en sus PBX, nos decidimos a crear un front-end web intuitivo y fácil de usar para usuarios que prefieran lo visual(WEB) al texto(consola de Linux).
Con el addon EasyVPN podrán:
Crear la configuración de la VPN en tan solo 5 sencillos pasos.
Crear los certificados de los clientes con un par de clicks .
Ver y administrar los clientes conectados al momento.
Crear certificados para clientes Windows.
Crear certificados para clientes Linux.
Crear certificados para teléfonos Yealink.
Crear certificados para teléfonos SNOM.
Entre los beneficios de utilizar EasyVPN están:
Utilizar sólo un puerto para la comunicación desde el exterior. Ya no será necesario abrir o natear puertos en el firewall para servicios específicos (SIP, IAX, SSH, HTTPS, etc).
Toda la comunicación entre los clientes y el servidor estará cifrada, nadie podrá leer los paquetes que se transmitan.
Los accesos estarán controlados. El administrador sabrá quien está conectado y desde qué IP.
Ideal para troncalizar Asterisk en distintas ubicaciones físicas.
Utilizar OpenVPN Connect en iOS y Android para registrar extensiones remotas en smartphones y/o tablets.
Hola, como muchos de ustedes ya se habrán dado cuenta desde el pasado Diciembre el grupo de desarrolladores de Asterisk liberó la primer versión estable de Asterisk 12. A pesar de que no es un Branch LTS, Asterisk 12 será (en mi opinión) un parte-aguas con respecto al modo de trabajar y desarrollar soluciones basadas en Asterisk.
Se preguntarán: ¿Qué es lo que lo hace tan especial? Bueno, técnicamente hablando Asterisk 12 es la fusión del proyecto Asterisk y Asterisk SCF, lo cual da como resultado una nueva arquitectura, los desarrolladores están pensando en dejar de lado el «bugguiento» chan_sip (aún se puede usar en Asterisk 12) por el nuevo core de SIP que usa PJSIP; además de un nuevo modelo de desarrollo usando ARI (no confundir con la interfaz de usuario de FreePBX) y de STATSIS.
Siguiendo la línea técnica habrá mucho que aprender ya sea para desarrollar soluciones basadas en ARI (Asterisk REST Interface), familiarizarse con el bus de mensajes de STASIS, comprender los nuevos registros que generará el CDR, aunque el más importante será la migración a CHAN_PJSIP.
De este último podemos decir que la configuración cambia bastante con respecto al «viejo» CHAN_SIP, y es que ahora no solo basta con crear un peer y las definiciones que generalmente usamos como: secret, host, allow, disallow, qualify, context, etc. No, ahora necesitaremos: definir un tipo de transporte para ser usado por el endpoint, crear el endpoint, crear el AOR (Address Of Record) del endpoint, crear las autenticaciones del endpoint (auths), entre otras. Por ejemplo: si se tratase de una línea SIP o si hay que registrarse o si estamos fuera de la red. Suena complejo, ¿no? Lo cierto es que con el uso diario uno se va acostumbrando. Ejemplifico lo anterior:
Tuve la fortuna de probar las versiones alphas de Asterisk 12 cuando el CHAN_PJSIP se llamaba CHAN_GULP y una de las funciones más atractivas tanto para los desarrolladores como para los usuarios es la posibilidad de registrar más de un dispositivo SIP a la misma cuenta y llamarlas sin necesidad de usar el & en la aplicación Dial. Bueno, la primera vez que lo usas es como comer helado. Para poder hacer esto hay que especificar en la sección AOR cuantos contactos/dispositivos pueden «registrarse» usando la misma información, para el ejemplo anterior serán 5 los dispositivos capaces de usar la información del ENDPOINT 1000, o sea que podemos registrar el télefono IP de la oficina, el teléfono IP de la casa, el softphone del Celular y aún así nos quedan 2 libres para poder conectar un softphone basado en WS por ejemplo o un softphone en cualquier otro sitio de emergencia. Para llamar a todos los contactos registrados basta invocar esta línea en nuestro dialplan:
Eso sí, tendremos que poner especial atención al CDR cuando llamemos a nuestros contactos porque los demás aparecerán como UNANSWERED.
Hablando de la Interfaz REST de Asterisk(ARI) surgen un sin fin de posibilidades para programar lo que se les ocurra en el lenguaje de programación que deseen, un ejemplo muy vistoso de como usar esta nueva interfaz esta en esta dirección http://ari.asterisk.org/ y usa swagger-ui como ‘core’ para crear la documentación interactiva. Para aquellos que estén muy familiarizados con las APIs REStfull y JSON se les hará pan comido, a su servidor le da dolores de cabeza. Actualmente existen proyectos dedicados a usar ARI para reemplazar las applicaciones Voicemail y APP_QUEUE de Asterisk, lo cual lo hace muy interesante, en el canal de Youtube de Asterisk está la conferencia de Paul Belanger sobre esto.
Sobre el nuevo bus de mensajes llamado STASIS no tengo mucho que decir ya que aún lo sigo probando y tratando de entender, así que para que doy mala información, lo único que puedo decir es que con la aplicación STASIS podremos generar eventos bajo demanda de un canal para después obtener dichos eventos vía un Websocket. Útil para esas aplicaciones que necesitan estar monitoreando llamadas(y no, no es como AMI de hecho ni ARI ni STASIS están hechas para reemplazar AMI).
Desde la perspectiva del usuario no habrá mucho cambio, salvo que tendrá un sistema mas robusto y si usan PJSIP un canal mas estable con menos BUGS.
Desde la perspectiva del integrador o desarrollador, sólo es cuestión de leer y leer; pero seamos honestos en este mundo de Linux siempre estamos leyendo y leyendo, lo cuál hará el proceso mas llevadero. Hablando de la instalación surgen nuevas dependencias como PJPROJECT, JANSSON, LibSRTP(esta última la deberías instalar con todos tus asterisk en realidad). Para aquellos que siempre quisieron un Asterisk enfocado a ‘multi-tennant’, Asterisk 12 será su mejor amigo. Y lo mejor de todo es que para aquellos que son FAN FAN de las WEB GUI de Asterisk, la excelente noticia es que FreePBX ha sacado ya una versión beta para usar Asterisk 12, ha sido un gran trabajo en conjunto con los desarrolladores de Digium y sólo como probadita te dejan escoger entre Asterisk 11 o Asterisk 12, si deseas usar sólo CHAN_SIP, o sólo CHAN_PJSIP o ambos.
Viene un camino largo por recorrer con Asterisk 12 y parafraseando a Elio Rojano, «Asterisk 12 es la maduración de un proyecto» que lleva ya 10 años(o más) de vida.
Les dejo estos enlaces para saber más de Asterisk 12:
Hola de nuevo, durante mi presentación en el Addons Challenge del ElastixWorld comente un caso de estudio: Implementación de Centros de Contacto de Entrada balanceados por un PROXY SIP. Afortunadamente pude asistir al taller de OpenSIPS que se dió también durante el ElastixWorld y gracias a la gente de AG PROJECTS (Saúl y Adrian) conocí acerca de los usos de OpenSIPS.
Entonces tenemos: Un caso de estudio + un Proxy SIP + una placa ASIRI, vamos a demostrar que es posible lo que comente durante la presentación y que el ElastixWorld dió sus frutos (implementación de OpenSIPS y la placa ASIRI).
Instalación de OpenSIPS.
Una vez que ya tenemos nuestra placa ASIRI funcionando y tenemos acceso via SSH lo primero que vamos a hacer es instalar OpenSIPS:
1.- Instala las dependencias necesarias de los módulos que pretendas usar, en este caso solo habilitaré el módulo «db_mysql» por lo que necesitamos las librerías devel de MySQL, ¡aha! uElastix ya las trae instaladas. Pero aún así necesitamos unas cuantas(estoy instalando paquetes de más para su uso futuro) :
2.- Una vez que haya terminado la instalación vamos a crear el directorio para bajar las fuentes de OpenSIPS:
# cd /usr/src/
# mkdir opensips
# cd opensips/
3.- Descargamos las fuentes de OpenSIPS desde el repositorio de GIT. Uso GIT porque hubo un problema con las fuentes originales y la arquitectura ARM, después de reportar el problema los desarrolladores de OpenSIPS amablemente lo corrigieron y actualizaron las fuentes en GIT.
# git clone https://github.com/OpenSIPS/opensips.git -b 1.10 opensips_1_10
# cd opensips_1_10/
4.- A continuación compilamos OpenSIPS con el módulo db_mysql(puedes irte a jugar una partida de FIFA en el XBOX, un café o algo el proceso va a tardar):
# make include_modules="db_mysql" modules
# make include_modules="db_mysql" install
# make install
5.- Después de que haya terminado vamos a crear el directorio en /etc que contedrá los archivos de configuración:
# mkdir /etc/opensips
6.- Ahora crearemos el archivo de configuración para crear la base de datos de OpenSIPS:
# nano /etc/opensips/opensipsctlrc
Con el siguiente contenido, recuerda cambiar DBNAME por el nombre de la base de datos que desees, DBRWUSER por usuario de la base que desees y DBRWPW por el password que desees que use la base:
# $Id$
#
# The OpenSIPS configuration file for the control tools.
#
# Here you can set variables used in the opensipsctl and opensipsdbctl setup
# scripts. Per default all variables here are commented out, the control tools
# will use their internal default values.
## your SIP domain
# SIP_DOMAIN=opensips.org
## chrooted directory
# $CHROOT_DIR="/path/to/chrooted/directory"
## database type: MYSQL, PGSQL, ORACLE, DB_BERKELEY, or DBTEXT,
## by default none is loaded
# If you want to setup a database with opensipsdbctl, you must at least specify
# this parameter.
DBENGINE=MYSQL
## database host
DBHOST=localhost
## database name (for ORACLE this is TNS name)
DBNAME=opensips
# database path used by dbtext or db_berkeley
# DB_PATH="/usr/local/etc/opensips/dbtext"
## database read/write user
DBRWUSER=opensips
## password for database read/write user
DBRWPW="opensipsrw"
## database super user (for ORACLE this is 'scheme-creator' user)
DBROOTUSER="root"
# user name column
# USERCOL="username"
# SQL definitions
# If you change this definitions here, then you must change them
# in db/schema/entities.xml too.
# FIXME
# FOREVER="2020-05-28 21:32:15"
# DEFAULT_ALIASES_EXPIRES=$FOREVER
# DEFAULT_Q="1.0"
# DEFAULT_CALLID="Default-Call-ID"
# DEFAULT_CSEQ="13"
# DEFAULT_LOCATION_EXPIRES=$FOREVER
# Program to calculate a message-digest fingerprint
# MD5="md5sum"
# awk tool
# AWK="awk"
# grep tool
# GREP="grep"
# sed tool
# SED="sed"
# Describe what additional tables to install. Valid values for the variables
# below are yes/no/ask. With ask (default) it will interactively ask the user
# for an answer, while yes/no allow for automated, unassisted installs.
#
# If to install tables for the modules in the EXTRA_MODULES variable.
# INSTALL_EXTRA_TABLES=ask
# If to install presence related tables.
# INSTALL_PRESENCE_TABLES=ask
# Define what module tables should be installed.
# If you use the postgres database and want to change the installed tables,
# then you must also adjust the STANDARD_TABLES or EXTRA_TABLES variable
# accordingly in the opensipsdbctl.base script.
# opensips standard modules
# STANDARD_MODULES="standard acc domain group permissions registrar usrloc
# msilo alias_db uri_db speeddial avpops auth_db pdt dialog
# dispatcher dialplan drouting nathelper load_balancer"
# opensips extra modules
# EXTRA_MODULES="imc cpl siptrace domainpolicy carrierroute userblacklist b2b registrant"
## type of aliases used: DB - database aliases; UL - usrloc aliases
## - default: none
# ALIASES_TYPE="DB"
## control engine: FIFO or UNIXSOCK
## - default FIFO
# CTLENGINE=xmlrpc
## path to FIFO file
# OSIPS_FIFO="/tmp/opensips_fifo"
## MI_CONNECTOR control engine: FIFO, UNIXSOCK, UDP, XMLRPC
# MI_CONNECTOR=FIFO:/tmp/opensips_fifo
# MI_CONNECTOR=UNIXSOCK:/tmp/opensips.sock
# MI_CONNECTOR=UDP:192.168.2.133:8000
# MI_CONNECTOR=XMLRPC:192.168.2.133:8000
## check ACL names; default on (1); off (0)
# VERIFY_ACL=1
## ACL names - if VERIFY_ACL is set, only the ACL names from below list
## are accepted
# ACL_GROUPS="local ld int voicemail free-pstn"
## verbose - debug purposes - default '0'
# VERBOSE=1
## do (1) or don't (0) store plaintext passwords
## in the subscriber table - default '1'
# STORE_PLAINTEXT_PW=0
## do not display the output highlighted
# NOHLPRINT=1
## OPENSIPS START Options
## PID file path - default is: /var/run/opensips.pid
# PID_FILE=/var/run/opensips.pid
## Extra start options - default is: not set
# example: start opensips with 64MB share memory: STARTOPTIONS="-m 64"
# STARTOPTIONS=
7.- A continuación vamos a instalar la base de datos, para ello cambiamos al directrio «scripts» y ejecutamos el programa «opensipsdbctl» mas la instrucción «create» más el nombre de la base de datos que definimos en el archivo anterior:
# cd scripts/
# opensipsdbctl create opensips
Nos preguntará por la contraseña de root de MySQL, por defecto en uElastix es «palosanto». Después nos preguntará si queremos instalar las tablas extras, eso ya es su decisión:
MySQL password for root: <----palosanto
INFO: test server charset
INFO: creating database opensisps ...
INFO: Core OpenSIPS tables succesfully created.
Install presence related tables? (y/n): y
INFO: creating presence tables into opensips ...
INFO: Presence tables succesfully created.
Install tables for imc cpl siptrace domainpolicy carrierroute userblacklist registrant? (y/n): y
INFO: creating extra tables into opensips ...
INFO: Extra tables succesfully created.
8.- Ahora hay que crear el archivo de configuración de OpenSIPS, el que cargará los módulos y contendrá las reglas del proxy, balanceo de carga etc. Esto se puede hacer utilizando el programa «osipsconfig» y escoger crear script para balanceo de cargas.
Como en este ejemplo la idea es que OpenSIPS reciba las llamadas de un proveedor SIP y las redireccione a los nodos con menor carga para su procesamiento necesitamos de 3 módulos principalmente:
a) Módulo «load_balancer.so«, el cuál se encargará de balancear la carga de llamadas.
b) Módulo «uac_registrant.so«, el cuál se encargara de hacer el registro de las lineas SIP a recibir
c) Módulo «uac_auth.so«, el cuál es necesario para que el módulo anterior funcione correctamente.
La primera línea implica el verbose que veremos.
La segunda línea si escribiremos al stderr.
La tercera línea hacia donde vamos a escribir el log, por default escribimos al syslog del sistema.
La cuarta línea es el nombre que saldrá en el log, en mi caso indico que es: opensips como load balancer en el puerto 5260.
También tenemos que decirle en que IP y puerto vamos a escuchar las peticiones SIP:
listen=udp:10.0.1.120:5260
Como ya tenemos el Asterisk escuchando en el puerto 5060 yo he elegido el puerto 5260 para OpenSIPS.
Algo muy importante es indicar la ruta de los módulos, en este caso los modulos se encuentran en: «/usr/lib/opensips/modules/»:
mpath="/usr/lib/opensips/modules/"
Acerca de la lógica del balanceo de carga podemos usar el ejemplo que trae por default, en mi caso lo estoy utilizando de esta manera:
La sintaxis de la función load_balance es: load_balance(grp,resource[,alg]) donde(más información: http://www.opensips.org/html/docs/modules/1.10.x/load_balancer.html#id250102):
grp: Es el ID del cluster de los nodos, en mi caso el grupo es «1».
resource: Una cadena o lista de cadenas separadas por punto y coma que indican los recursos a usar, en mi caso «pstn».
alg: Es el algoritmo a usar para definir la carga en uso, puede ser 0 o 1. 0 es para usar un algoritmo absoluto y 1 para un algoritmo relativo.
A continuación dejo el archivo que estoy usando:
#
# $Id$
#
# OpenSIPS loadbalancer script
# by OpenSIPS Solutions <team@opensips-solutions.com>
#
# This script was generated via "make menuconfig", from
# the "Load Balancer" scenario.
# You can enable / disable more features / functionalities by
# re-generating the scenario with different options.
#
# Please refer to the Core CookBook at:
# http://www.opensips.org/Resources/DocsCookbooks
# for a explanation of possible statements, functions and parameters.
#
####### Global Parameters #########
debug=6
log_stderror=no
log_facility=LOG_LOCAL0
log_name="opensip_LB_5260"
fork=yes
children=4
/* uncomment the following lines to enable debugging */
#debug=6
#fork=no
#log_stderror=yes
/* uncomment the next line to enable the auto temporary blacklisting of
not available destinations (default disabled) */
#disable_dns_blacklist=no
/* uncomment the next line to enable IPv6 lookup after IPv4 dns
lookup failures (default disabled) */
#dns_try_ipv6=yes
/* comment the next line to enable the auto discovery of local aliases
based on revers DNS on IPs */
auto_aliases=no
listen=udp:10.0.1.120:5260 # CUSTOMIZE ME
####### Modules Section ########
#set module path
mpath="/usr/lib/opensips/modules/"
#### SIGNALING module
loadmodule "signaling.so"
#### StateLess module
loadmodule "sl.so"
#### Transaction Module
loadmodule "tm.so"
modparam("tm", "fr_timer", 5)
modparam("tm", "fr_inv_timer", 30)
modparam("tm", "restart_fr_on_each_reply", 0)
modparam("tm", "onreply_avp_mode", 1)
#### Record Route Module
loadmodule "rr.so"
/* do not append from tag to the RR (no need for this script) */
modparam("rr", "append_fromtag", 0)
#### MAX ForWarD module
loadmodule "maxfwd.so"
#### SIP MSG OPerationS module
loadmodule "sipmsgops.so"
#### FIFO Management Interface
loadmodule "mi_fifo.so"
modparam("mi_fifo", "fifo_name", "/tmp/opensips_fifo")
modparam("mi_fifo", "fifo_mode", 0666)
#### URI module
loadmodule "uri.so"
modparam("uri", "use_uri_table", 0)
#### MYSQL module
loadmodule "db_mysql.so"
#### AVPOPS module
loadmodule "avpops.so"
#### ACCounting module
loadmodule "acc.so"
/* what special events should be accounted ? */
modparam("acc", "early_media", 0)
modparam("acc", "report_cancels", 0)
/* by default we do not adjust the direct of the sequential requests.
if you enable this parameter, be sure the enable "append_fromtag"
in "rr" module */
modparam("acc", "detect_direction", 0)
modparam("acc", "failed_transaction_flag", "ACC_FAILED")
/* account triggers (flags) */
modparam("acc", "log_flag", "ACC_DO")
modparam("acc", "log_missed_flag", "ACC_MISSED")
#### DIALOG module
loadmodule "dialog.so"
modparam("dialog", "dlg_match_mode", 1)
modparam("dialog", "hash_size", 128)
modparam("dialog", "default_timeout", 21600) # 6 hours timeout
modparam("dialog", "db_mode", 2)
modparam("dialog", "db_url",
"mysql://opensips:opensipsrw@localhost/opensips") # CUSTOMIZE ME
#### LOAD BALANCER module
loadmodule "load_balancer.so"
modparam("load_balancer", "db_url","mysql://opensips:opensipsrw@localhost/opensips") # CUSTOMIZE ME
modparam("load_balancer", "probing_interval", 30)
modparam("load_balancer", "probing_method", "OPTIONS")
modparam("load_balancer", "probing_from", "sip:ospinger@10.0.1.102")
modparam("load_balancer", "probing_reply_codes", "501, 403,200")
#### UAC MODULE
loadmodule "uac_auth.so"
loadmodule "uac_registrant.so"
modparam("uac_registrant", "hash_size", 1)
modparam("uac_registrant", "timer_interval", 120)
modparam("uac_registrant", "db_url", "mysql://opensips:opensipsrw@localhost/opensips")
modparam("uac_registrant", "table_name", "registrant")
modparam("uac_registrant", "registrar_column", "registrar")
modparam("uac_registrant", "proxy_column", "proxy")
modparam("uac_registrant", "aor_column", "aor")
modparam("uac_registrant", "third_party_registrant_column", "third_party_registrant")
modparam("uac_registrant", "username_column", "username")
modparam("uac_registrant", "password_column", "password")
modparam("uac_registrant", "binding_URI_column", "binding_URI")
modparam("uac_registrant", "binding_params_column", "binding_params")
modparam("uac_registrant", "expiry_column", "expiry")
modparam("uac_registrant", "forced_socket_column", "forced_socket")
####### Routing Logic ########
# main request routing logic
route{
if (!mf_process_maxfwd_header("10")) {
sl_send_reply("483","Too Many Hops");
exit;
}
if (has_totag()) {
# sequential request withing a dialog should
# take the path determined by record-routing
if (loose_route()) {
# validate the sequential request against dialog
if ( $DLG_status!=NULL && !validate_dialog() ) {
xlog("In-Dialog $rm from $si (callid=$ci) is not valid according to dialog\n");
## exit;
}
if (is_method("BYE")) {
setflag(ACC_DO); # do accounting ...
setflag(ACC_FAILED); # ... even if the transaction fails
} else if (is_method("INVITE")) {
# even if in most of the cases is useless, do RR for
# re-INVITEs alos, as some buggy clients do change route set
# during the dialog.
record_route();
}
# route it out to whatever destination was set by loose_route()
# in $du (destination URI).
route(RELAY);
} else {
if ( is_method("ACK") ) {
if ( t_check_trans() ) {
# non loose-route, but stateful ACK; must be an ACK after
# a 487 or e.g. 404 from upstream server
t_relay();
exit;
} else {
# ACK without matching transaction ->
# ignore and discard
exit;
}
}
sl_send_reply("404","Not here");
}
exit;
}
#### INITIAL REQUESTS
# CANCEL processing
if (is_method("CANCEL")) {
if (t_check_trans())
t_relay();
exit;
} else if (!is_method("INVITE")) {
send_reply("405","Method Not Allowed");
exit;
}
if ($rU==NULL) {
# request with no Username in RURI
sl_send_reply("484","Address Incomplete");
exit;
}
t_check_trans();
# preloaded route checking
if (loose_route()) {
xlog("L_ERR",
"Attempt to route with preloaded Route's [$fu/$tu/$ru/$ci]");
if (!is_method("ACK"))
sl_send_reply("403","Preload Route denied");
exit;
}
# record routing
record_route();
setflag(ACC_DO); # do accounting
if(load_balance("1","pstn")){
xlog("sending call to $du\n");
t_relay();
exit;
}else{
send_reply("500","No Destination available");
exit;
}
t_on_failure("GW_FAILOVER");
route(RELAY);
}
route[RELAY] {
if (!t_relay()) {
sl_reply_error();
};
exit;
}
failure_route[GW_FAILOVER] {
if (t_was_cancelled()) {
exit;
}
# failure detection with redirect to next available trunk
if (t_check_status("(408)|([56][0-9][0-9])")) {
xlog("Failed trunk $rd/$du detected \n");
if ( load_balance("1","channel") ) {
t_on_failure("GW_FAILOVER");
t_relay();
exit;
}
send_reply("500","All GW are down");
}
}
local_route {
if (is_method("BYE") && $DLG_dir=="UPSTREAM") {
acc_log_request("200 Dialog Timeout");
}
}
9.- Ahora hay que crear el script de inicio:
# nano /etc/init.d/opensips
Con el siguiente contenido:
#!/bin/bash
#
# Startup script for OpenSIPS
#
# chkconfig: - 85 15
# description: OpenSIPS is a fast SIP Server.
#
# processname: opensips
# pidfile: /var/run/opensips.pid
# config: /etc/opensips/opensips.cfg
#
### BEGIN INIT INFO
# Provides: opensips
# Required-Start: $local_fs $network $named
# Should-Start: mysqld postgresql
# Short-Description: start, stop OpenSIPS
# Description: OpenSIPS is a very fast and flexible SIP (RFC3261) server.
### END INIT INFO
# Source function library.
. /etc/rc.d/init.d/functions
prog=opensips
oser=/usr/sbin/$prog
pidfile="/var/run/$prog.pid"
lockfile="/var/lock/subsys/$prog"
configfile="/etc/$prog/$prog.cfg"
m4configfile="/etc/$prog/$prog.m4"
m4archivedir="/etc/$prog/archive"
OPTIONS=""
RETVAL=0
[ -f /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog
start() {
echo -n $"Starting $prog: "
# check whether OpenSIPs was already started
if status -p $pidfile $prog > /dev/null 2>&1 ; then
echo -n "already running" && warning && echo
return 0
fi
# Generate config from M4
if [ -f $m4configfile ]; then
m4 -Q $m4configfile >$configfile.tmp
if [ $? != 0 ]; then
log "cannot process m4 macro"
rm "$configfile.tmp"
return 1
fi
[ -e $configfile ] || touch $configfile
# compare configs
if [ `md5sum $configfile|awk '{print $1}'` != `md5sum $configfile.tmp|awk '{print $1}'` ]; then
mkdir -p "$m4archivedir"
mv "$configfile" "$m4archivedir/$prog.cfg-`date +%Y%m%d_%H%M%S`"
fi
mv "$configfile.tmp" "$configfile"
chown root:root $configfile
chmod 640 $configfile
fi
# there is something at end of this output which is needed to
# report proper [ OK ] status in Fedora scripts
daemon $oser -u root -g root -P $pidfile -f $configfile $OPTIONS 2>/dev/null | tail -1
RETVAL=$?
echo
[ $RETVAL = 0 ] && touch $lockfile
return $RETVAL
}
stop() {
echo -n $"Stopping $prog: "
# check whether OpenSIPs is running
if ! status -p $pidfile $prog > /dev/null 2>&1 ; then
echo -n "not running" && warning && echo
return 0
fi
killproc $prog 2> /dev/null
RETVAL=$?
echo
[ $RETVAL = 0 ] && rm -f $lockfile $pidfile
return $RETVAL
}
# See how we were called.
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status -p $pidfile $prog
RETVAL=$?
;;
restart|reload)
stop
start
;;
condrestart|try-restart)
if [ -f $pidfile ] ; then
stop
start
fi
;;
*)
echo $"Usage: $prog {start|stop|reload|restart|condrestart|status|help}"
RETVAL=2
esac
exit $RETVAL
10.- Ya falta poco, ahora hay que añadir los valores a nuestra base de datos antes de iniciar el servicio de OpenSIPS. Entramos al cli de MySQL y añadimos los nodos que vamos a usar para balancear las llamadas(recuerda la contraseña de MySQL es: palosanto):
Se añadieron 3 servidores al grupo «1», cada uno con un recurso llamado «pstn» el cuál tiene un máximo de 1 llamada, para ambientes reales sustituir con las llamadas que se desean.
11.- Ahora vamos a añadir los valores de la cuenta sip que vamos a registrar:
mysql> INSERT INTO registrant(registrar,aor,username,password,binding_URI,expiry,) VALUES('sip:my.provider.host','sip:myusername@my.provider.host','myusername','mysuperpassword','sip:myDID@myAsiriIP','3600');
12.- Salimos de MySQL e intentamos iniciar el servicio de OpenSIPS, si todo va bien veremos una salida parecida a esta:
Oct 31 17:02:20 AsiriShaka opensips: INFO:core:init_tcp: using epoll_lt as the TCP io watch method (auto detected)
Oct 31 17:02:21 AsiriShaka opensip_LB_5260[3836]: NOTICE:core:main: version: opensips 1.10.0-notls (armv5tejl/linux)
Oct 31 17:02:21 AsiriShaka opensip_LB_5260[3836]: INFO:core:main: using 32 Mb shared memory
Oct 31 17:02:21 AsiriShaka opensip_LB_5260[3836]: INFO:core:main: using 2 Mb private memory per process
Oct 31 17:02:21 AsiriShaka opensip_LB_5260[3836]: INFO:core:evi_publish_event: Registered event <E_CORE_THRESHOLD(0)>
Oct 31 17:02:21 AsiriShaka opensip_LB_5260[3836]: INFO:core:evi_publish_event: Registered event <E_CORE_SHM_THRESHOLD(1)>
Oct 31 17:02:21 AsiriShaka opensip_LB_5260[3836]: INFO:core:evi_publish_event: Registered event <E_CORE_PKG_THRESHOLD(2)>
Oct 31 17:02:21 AsiriShaka opensip_LB_5260[3836]: NOTICE:signaling:mod_init: initializing module ...
Oct 31 17:02:21 AsiriShaka opensip_LB_5260[3836]: INFO:sl:mod_init: Initializing StateLess engine
Oct 31 17:02:21 AsiriShaka opensip_LB_5260[3836]: INFO:tm:mod_init: TM - initializing...
Oct 31 17:02:21 AsiriShaka opensip_LB_5260[3836]: CRITICAL:tm:lock_set_init: semget (..., 251, 0700) failed: Invalid argument
Oct 31 17:02:21 AsiriShaka opensip_LB_5260[3836]: INFO:tm:lock_initialize: semaphore arrays of size 250 allocated
Oct 31 17:02:21 AsiriShaka opensip_LB_5260[3836]: INFO:rr:mod_init: rr - initializing
Oct 31 17:02:22 AsiriShaka opensip_LB_5260[3836]: INFO:maxfwd:mod_init: initializing...
Oct 31 17:02:22 AsiriShaka opensip_LB_5260[3836]: INFO:sipmsgops:mod_init: initializing...
Oct 31 17:02:22 AsiriShaka opensip_LB_5260[3836]: INFO:core:evi_publish_event: Registered event <E_MYSQL_CONNECTION(3)>
Oct 31 17:02:22 AsiriShaka opensip_LB_5260[3836]: INFO:avpops:avpops_init: initializing...
Oct 31 17:02:22 AsiriShaka opensip_LB_5260[3836]: INFO:acc:mod_init: initializing...
Oct 31 17:02:22 AsiriShaka opensip_LB_5260[3836]: INFO:core:evi_publish_event: Registered event <E_ACC_EVENT(4)>
Oct 31 17:02:22 AsiriShaka opensip_LB_5260[3836]: INFO:core:evi_publish_event: Registered event <E_ACC_CDR(5)>
Oct 31 17:02:22 AsiriShaka opensip_LB_5260[3836]: INFO:core:evi_publish_event: Registered event <E_ACC_MISSED_EVENT(6)>
Oct 31 17:02:22 AsiriShaka opensip_LB_5260[3836]: INFO:dialog:mod_init: Dialog module - initializing
Oct 31 17:02:23 AsiriShaka opensip_LB_5260[3836]: INFO:load_balancer:mod_init: Load-Balancer module - initializing
Oct 31 17:02:23 AsiriShaka rsyslogd-2177: imuxsock begins to drop messages from pid 3836 due to rate-limiting
Oct 31 17:02:25 AsiriShaka opensips: INFO:core:daemonize: pre-daemon process exiting with 0
13.- Para saber el estado del cluster de servidores usaremos el comando:
Como podemos ver este comando es muy útil para saber cuántos nodos hay, cuál es su capacidad máxima y por último cuántas llamadas/carga tiene actualmente.
14.- Para saber el estado del registro de la línea SIP usaremos el comando:
Les dejo un video donde hago 2 llamadas y estas son ruteadas a dos diferentes Asterisk en mi red.
Conclusiones.
Como podemos ver OpenSips es capaz de correr en arquitecturas pequeñas como la Asiri o la Raspberry Pi, este mini-proyecto puede servir para hacer un cluster de muchas Asiris o RPis para armar un sistema de llamadas Inbound muy grande y a bajo costo.
Como lo comenté al inicio, es mi primera vez usando OpenSips de modo que puede haber un sin fin de errores en la redacciones o en la lógica del ruteo de llamadas. Para nada soy un experto en este proxy.
TO DO.
Queda por implementar, la detección de caídas de un nodo del cluster para rutear a otro Asterisk las llamadas o retenerlas hasta que otro nodo sea capaz de aceptar las llamadas.
Actualmente el registro de la línea SIP usa un puerto random, a pesar de que (según yo) estoy forzando el puerto, pero mi proveedor me muestra un puerto diferente lo cual complica el redireccionamiento en el firewall cuando no se tiene acceso a ese puerto.
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>
<button class="btn btn-primary" id="btnCall" onclick="call()" >Click Aquí Para Llamarnos!</button>
<button class="btn btn-danger hide" id="btnHangUp" onclick="hangup()" >Colgar Llamada</button>
<span class="label hide" id="mycallstatus"></span>
<span class="label label-inverse" id="mysipstatus">Si ve esta etiquete usted necesita usar Chrome para llamar</span>
<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.
Esta tecnología por ahora solo es compatible con el navegador Chrome para descargarlo <a href="">de click aqui.</a>
</div>
<div class="col"> ll
Si van a usar el ejemplo anterior sólo tienen que editar el siguiente código con los datos de su dispositivo SIP.
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. 😀
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.
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 invaluable 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:
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 campaña del Dialer (se puede modificar a futuro).
Si no se está 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:
Para salir de la sesión sin detener el gateway usar la combinación de teclas CTRL+A+D. Para detener el Gateway, entrar en la sesión y escribir ‘quit’+ENTER.
Instalación
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.
Los imágenes del Módulo ya editado:
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á. 😉