Translate

sábado, 8 de noviembre de 2014

Sistema Solar.


Como nació el proyecto.

El día lunes mi hijo de 7 años nos pidió a mi esposa y a mi que le ayudáramos con su proyecto del sistema solar, como calificarían la creatividad nos pidió que llevará luces la verdad en lo primero que pensé fue en usar un PICMicro de Microchip; pero como el proyecto era a entregar en 2 días y con mis horarios de trabajo no me iba a alcanzar el tiempo. Por esa razón voltee a ver una de mis Raspberry Pi, sabía que me iban a sacar a flote con esto.


Materiales:


  • 9 esferas de unicel para los planetas.
  • 1 pelota de plástico transparente para el Sol.
  • Celofan para amarillo para las llamas del Sol.
  • 1 Raspberry Pi con soporte para python-gpio.
  • 1 caja para transportar la maqueta.
  • 1 Resistencia de 10K.
  • 10 LEDS de diferentes colores.
  • Varios palillos de madera.
  • Cable y cinta de asilar.


Pasos a Seguir:

Pintar las esferas con los colores deseados de los planetas.

Una vez que las esferas de unicel estén secas, un adulto deberá cortar las esferas por la mitad y rascar el unicel para que las mismas queden huecas. como se muestra en la siguiente imagen(repetir el proceso con todas las esferas).



Una vez que la esfera este hueca se deberá insertar el LED deseado para cada planeta.






Soldar un par de cables al LED(asegurarse de diferenciar el ánodo del cátodo con los cables, la patilla larga es el positivo y la patilla corta es el negativo). Con cuidado pegar la otra parte de la esfera con silicon.

Para el Sol pegar el celofan sobre la pelota transparente y posteriormente insertar un LED dentro de la pelota.

Construir una base para el sistema con los palillos de madera. mi esposa ha creado una similar a esta imagen.



Dando como resultado:



Conexión a la Raspberry Pi.

La distribución de los planetas con respecto a las entradas de la tarjeta quedó así:

Planeta BCM GPIO LED COLOR
Mercurio GPIO4 Blanco
Venus GPIO17 Amarillo
Tierra GPIO21 Azul
Marte GPIO22 Rojo
Jupiter GPIO18 Amarillo
Saturno GPIO23 Amarillo
Urano GPIO24 Azul
Neptuno GPIO25 Azul
Switch GPIO0



La conexión de los leds respetando el GPIO asignado es similar a:



El Switch será conectado en el GPIO0 que es usado como SDA de modo que tendremos que disparar el evento con un 0 lógico, ya que SDA tiene un Pull-Up desde el hardware.




Teniendo como resultado:



El Programa.

Para crear el programa he usado Python junto con la librería python-rpi.gpio. Realmente el programa no está optimizado pero tomando en cuenta que lo hicimos en una tarde no me puedo quejar mucho.


#!/usr/bin/env python

#importing libraries
from time import sleep
import os
import RPi.GPIO as GPIO

#setting mode to BCM GPIO
GPIO.setmode(GPIO.BCM)

#SDA as INPUT
GPIO.setup(0,GPIO.IN)

#ALL GPIO as OUTPUT
GPIO.setup(4, GPIO.OUT)
GPIO.setup(17, GPIO.OUT)
GPIO.setup(21, GPIO.OUT)
GPIO.setup(22, GPIO.OUT)
GPIO.setup(18, GPIO.OUT)
GPIO.setup(23, GPIO.OUT)
GPIO.setup(24, GPIO.OUT)
GPIO.setup(25, GPIO.OUT)

#Cleanning all OUTPUTS
GPIO.output(4, GPIO.LOW)
GPIO.output(17, GPIO.LOW)
GPIO.output(21, GPIO.LOW)
GPIO.output(22, GPIO.LOW)
GPIO.output(18, GPIO.LOW)
GPIO.output(23, GPIO.LOW)
GPIO.output(24, GPIO.LOW)
GPIO.output(25, GPIO.LOW)

#A blink for saying a live ah-ah staying a live
GPIO.output(4, GPIO.HIGH)
sleep(1)
GPIO.output(4, GPIO.LOW)
sleep(1)
GPIO.output(4, GPIO.HIGH)
sleep(1)
GPIO.output(4, GPIO.LOW)
sleep(1)

#creating the pwm handlers for each planet
mercurio = GPIO.PWM(4, 50)
venus    = GPIO.PWM(17, 50)
tierra   = GPIO.PWM(21, 50)
marte    = GPIO.PWM(22, 50)
jupiter  = GPIO.PWM(18, 50)
saturno  = GPIO.PWM(23, 50)
urano    = GPIO.PWM(24, 50)
neptuno  = GPIO.PWM(25, 50)

#Looping
while True:
        #Start the loop when press the button
        if ( GPIO.input(0) == False ):
         for i in range(0 ,2):
                mercurio.start(10)
                sleep(.5)
                mercurio.ChangeDutyCycle(10)
                sleep(.5)
                mercurio.ChangeDutyCycle(30)
                sleep(.5)
                mercurio.ChangeDutyCycle(40)
                sleep(.5)
                mercurio.ChangeDutyCycle(50)
                sleep(.5)
                mercurio.ChangeDutyCycle(70)
                sleep(.5)
                mercurio.ChangeDutyCycle(100)
                sleep(3)
                mercurio.stop()


                venus.start(10)
                sleep(.5)
                venus.ChangeDutyCycle(20)
                sleep(.5)
                venus.ChangeDutyCycle(30)
                sleep(.5)
                venus.ChangeDutyCycle(40)
                sleep(.5)
                venus.ChangeDutyCycle(50)
                sleep(.5)
                venus.ChangeDutyCycle(70)
                sleep(.5)
                venus.ChangeDutyCycle(100)
                sleep(3)
                venus.stop()

                tierra.start(10)
                sleep(.5)
                tierra.ChangeDutyCycle(20)
                sleep(.5)
                tierra.ChangeDutyCycle(30)
                sleep(.5)
                tierra.ChangeDutyCycle(40)
                sleep(.5)
                tierra.ChangeDutyCycle(50)
                sleep(.5)
                tierra.ChangeDutyCycle(70)
                sleep(.5)
                tierra.ChangeDutyCycle(100)
                sleep(3)
                tierra.stop()

                marte.start(10)
                sleep(.5)
                marte.ChangeDutyCycle(20)
                sleep(.5)
                marte.ChangeDutyCycle(30)
                sleep(.5)
                marte.ChangeDutyCycle(40)
                sleep(.5)
                marte.ChangeDutyCycle(50)
                sleep(.5)
                marte.ChangeDutyCycle(70)
                sleep(.5)
                marte.ChangeDutyCycle(100)
                sleep(3)
                marte.stop()

                jupiter.start(10)
                sleep(.5)
                jupiter.ChangeDutyCycle(20)
                sleep(.5)
                jupiter.ChangeDutyCycle(30)
                sleep(.5)
                jupiter.ChangeDutyCycle(40)
                sleep(.5)
                jupiter.ChangeDutyCycle(50)
                sleep(.5)
                jupiter.ChangeDutyCycle(70)
                sleep(.5)
                jupiter.ChangeDutyCycle(100)
                sleep(3)
                jupiter.stop()

                saturno.start(10)
                sleep(.5)
                saturno.ChangeDutyCycle(20)
                sleep(.5)
                saturno.ChangeDutyCycle(30)
                sleep(.5)
                saturno.ChangeDutyCycle(40)
                sleep(.5)
                saturno.ChangeDutyCycle(50)
                sleep(.5)
                saturno.ChangeDutyCycle(70)
                sleep(.5)
                saturno.ChangeDutyCycle(100)
                sleep(3)
                saturno.stop()

                urano.start(10)
                sleep(.5)
                urano.ChangeDutyCycle(20)
                sleep(.5)
                urano.ChangeDutyCycle(30)
                sleep(.5)
                urano.ChangeDutyCycle(40)
                sleep(.5)
                urano.ChangeDutyCycle(50)
                sleep(.5)
                urano.ChangeDutyCycle(70)
                sleep(.5)
                urano.ChangeDutyCycle(100)
                sleep(3)
                urano.stop()

                neptuno.start(10)
                sleep(.5)
                neptuno.ChangeDutyCycle(20)
                sleep(.5)
                neptuno.ChangeDutyCycle(30)
                sleep(.5)
                neptuno.ChangeDutyCycle(40)
                sleep(.5)
                neptuno.ChangeDutyCycle(50)
                sleep(.5)
                neptuno.ChangeDutyCycle(70)
                sleep(.5)
                neptuno.ChangeDutyCycle(100)
                sleep(3)
                neptuno.stop()


                GPIO.output(4, GPIO.HIGH)
                GPIO.output(17, GPIO.HIGH)
                GPIO.output(21, GPIO.HIGH)
                GPIO.output(22, GPIO.HIGH)
                GPIO.output(18, GPIO.HIGH)
                GPIO.output(23, GPIO.HIGH)
                GPIO.output(24, GPIO.HIGH)
                GPIO.output(25, GPIO.HIGH)

                sleep(3)

                GPIO.output(4, GPIO.LOW)
                GPIO.output(17, GPIO.LOW)
                GPIO.output(21, GPIO.LOW)
                GPIO.output(22, GPIO.LOW)
                GPIO.output(18, GPIO.LOW)
                GPIO.output(23, GPIO.LOW)
                GPIO.output(24, GPIO.LOW)
                GPIO.output(25, GPIO.LOW)

#GPIO.cleanup()



Para ejecutar el script al arranque de la Raspberry añadí el script anterior a /etc/rc.local y después de que mercurio hace blink se entra en el loop que espera a que el boton envíe la rutina de PWM a los planetas.

Siendo sincero el programa se puede optimizar mucho creando funciones pero quedará para la próxima.


Mejoras:

  • Optimizar el código para usar funciones y no taanto código.
  • Si se fijan en el código se cuenta con la línea import os. La idea original era añadir audio al sistema pero como no alcanzó el tiempo no pude hacerlo funcionar pero la idea es agregar un sonido a cada planeta con un comando similar a: os.system('mpg321 intro-planet &')
  • Hacerlo Rotar, se'ria genial crear una base giratoria para que todo se mueva.
  • Finalmente la idea es que mi Hijo comience a usar Scratch GPIO para que él pueda hacerlo solo.


El Vídeo.


video








domingo, 2 de marzo de 2014

Añadiendo un ADC a la Raspberry Pi

Se me encargó medir voltaje con la Raspberry Pi, así que como todo en san google uno encuentra mucha información así que dí con está página que muestra como usar un conversor analogo-digital SPI con la RPi de la tienda http://www.abelectronics.co.uk/

En el enlace anterior usan el integrado MCP3002 y en la web encontrarán miles de ejemplos para el MCP3008 como vivo en pueblo quieto lo único que pude conseguir en la ciudad fue un MCP3202 la única diferencia es que solo tiene 2 canales y que este sensor tiene una resolución de 12Bits, es decir, los valores obtenidos en decimal iràn de 0 a 4095.

Y bueno aquí les dejo los pasos para habilitar el soporte SPI en la Raspberry Pi, el programa(obtenido del enlace anterior) el circuito y unas capturas de prueba.

Background.

Este ADC para la Raspberry Pi esta basado en el chip MCP3202 de Microchip. Es un ADC de 12 bits
de resolución y se comunica vía SPI.

Como la Raspberry Pi usa 3.3v se usa un divisor de tensión para bajar 10V a 3.3V de modo que los
pasos del ADC van de 0 para 0V y 4095 para 3.3V.

REQUERIMIENTOS:

  1. Raspbian Wheezy
  2. Python
  3. Python-dev
  4. spidev
  5. Habilitar el soporte para SPI de la Raspberry Pi.

Instalación de Dependencias:

  • Para instalar python python-dev realizar:
 $ sudo apt-get install python-dev python
  • Para instalar spidev:
$ git clone git://github.com/doceme/py-spidev
$ cd py-spidev/
$ sudo python setup.py install
  • Habilitar el soporte de spi en la raspberry-pi:
$ sudo sed -i 's/blacklist spi\-bcm2708/\#blacklist spi-bcm2708/g' /etc/modprobe.d/raspi-blacklist.conf
$ sudo reboot


Diagrama:



El Programa
#!/usr/bin/python
import spidev
import time
DEBUG = 0

spi = spidev.SpiDev()
spi.open(0,0)

# read SPI data from MCP3202 chip
def get_adc(channel):
        # Only 2 channels 0 and 1 else return -1
        if ((channel > 1) or (channel < 0)):
                return -1
        r = spi.xfer2([1,(2+channel)<<6,0])

        ret = ((r[1]&0x0F) << 8) + (r[2])
        return ret

tempval = 0
#Take 5 reads on the adc
for x in range(0, 9):
 tempval += get_adc(0)

#Get the prom
promval = tempval/10

#Get the volts
V1 = (get_adc(0)*3.3)/4096
V2 = (V1*10)/3.3 


#Print results

print "Valor Decimal: ", promval
print ""
print "Divisor de Voltaje: %0.2f" % (V1)
print ""
print "Voltaje de Entrada: %0.2f" % (V2)
print ""


Y unas capturas del circuito y capturando el voltaje.


Primer captura de Voltaje:




Segunda Captura de Voltaje:



lunes, 2 de diciembre de 2013

Mini Portable Centro de Contacto con Asiri.

Hola hoy quiero iniciar con estas preguntas:

  • ¿Les gustaría contar con un mini centro de contacto capaz de ser transportado en la palma de su mano? 
  • ¿Que sea de bajo consumo?
  • ¿Que sea modular?
Si a todas las anteriores respondieron: Sí. Entonces sigan leyendo.

Como bien sabemos(o deberíamos saber) Asiri es un nuevo board basado en ARM creado en latinoamérica. Como la serie de temas anteriores han tratado acerca de este nuevo board, hoy les quiero compartir este proyectito: Crear un mini Centro de Contacto con esta placa de bajo costo, además de incluir el addon de "WebRTC Agent Console"  para hacer de este mini centro de contacto del tipo "Plug&Play".

Manos a la obra.

1.- Lo primero que tenemos que hacer es actualizar nuestro sistema con el comando:

# yum update

2.- Después de algunos minutos habrá que instalar el módulo de Call Center de Elastix(lo pueden hacer desde la página de addons del PBX).

# yum install -y elastix-callcenter

Preparar el sistema para usar WebRTC.

Como sabemos estas placas de desarrollo tienen algunos recursos limitados y nosotros queremos usar el addon "WebRTC Agent Console" el cual usa un servicio extra en nuestro servidor(el media gateway webrtc2sip) lo que nos provoca ocupar más recursos de memoria y espacio en la SDCARD lo que puede afectar el funcionamiento óptimo del sistema. Así que vamos a evitar el uso del media gateway y trabajar de manera nativa con WebRTC y Elastix, pero para ello tenemos que verificar dos cosas:

  • Tener actualizado nuestro Asterisk a la versión >11
  • Aplicar el siguiente parche para habilitar las opciones que las extensiones WebRTC necesitan para su funcionamiento.
1.- Lo primero que vamos a hacer es aplicar el siguiente parche(lo pueden descargar desde este enlace):


--- /var/www/html/admin/modules/core/functions.inc.php 2013-06-13 15:11:38.000000000 -0500
+++ /var/www/html/admin/modules/core/functions.inc.php 2013-11-29 15:19:23.600308427 -0500
@@ -3800,7 +3800,7 @@
    array($account,'deny',$db->escapeSimple((isset($_REQUEST['deny']))?$_REQUEST['deny']:''),$flag++),
    array($account,'permit',$db->escapeSimple((isset($_REQUEST['permit']))?$_REQUEST['permit']:''),$flag++),  
    array($account,'disallow',$db->escapeSimple((isset($_REQUEST['disallow']))?$_REQUEST['disallow']:''),$flag++),
-   array($account,'allow',$db->escapeSimple((isset($_REQUEST['allow']))?$_REQUEST['allow']:''),$flag++)
+   array($account,'allow',$db->escapeSimple((isset($_REQUEST['allow']))?$_REQUEST['allow']:''),$flag++),
   );
     $vmexten = isset($_REQUEST['vmexten'])?$db->escapeSimple(trim($_REQUEST['vmexten'])):'';
     if ($vmexten != '') {
@@ -3811,6 +3811,15 @@
  // Very bad
  $sipfields[] = array($account,'account',$db->escapeSimple($account),$flag++); 
  $sipfields[] = array($account,'callerid',$db->escapeSimple((isset($_REQUEST['description']) && $_REQUEST['description'])?$_REQUEST['description']." <".$account.'>':'device'." <".$account.'>'),$flag++);
+
+ //mein
+ $sipfields[] = array($account,'transport',$db->escapeSimple((isset($_REQUEST['transport']))?$_REQUEST['transport']:'udp'),$flag++);
+  $sipfields[] = array($account,'avpf',$db->escapeSimple((isset($_REQUEST['avpf']))?$_REQUEST['avpf']:'no'),$flag++);
+ $sipfields[] = array($account,'encryption',$db->escapeSimple((isset($_REQUEST['transport']))?$_REQUEST['transport']:'no'),$flag++);
+ $sipfields[] = array($account,'icesupport',$db->escapeSimple((isset($_REQUEST['transport']))?$_REQUEST['transport']:'no'),$flag++);
+
+         //end mein
+
  
  // Where is this in the interface ??????
  $sipfields[] = array($account,'record_in',$db->escapeSimple(($_REQUEST['record_in'])?$_REQUEST['record_in']:'On-Demand'),$flag++);
@@ -6009,6 +6018,13 @@
   $tmparr['vmexten'] = array('value' => '', 'level' => 1);
   $tmparr['deny'] = array('value' => '0.0.0.0/0.0.0.0', 'level' => 1);
       $tmparr['permit'] = array('value' => '0.0.0.0/0.0.0.0', 'level' => 1);
+      //mein
+  $tmparr['transport'] = array('value' => '', 'level' => 1);
+      $tmparr['avpf'] = array('value' => '', 'level' => 1);
+      $tmparr['encryption'] = array('value' => '', 'level' => 1);
+      $tmparr['icesupport'] = array('value' => '', 'level' => 1);
+  //endmien 
+ 
   $currentcomponent->addgeneralarrayitem('devtechs', 'sip', $tmparr);
   unset($tmparr);
 

2.- Nos cambiamos al directorio /var/www/html/admin/modules/core y aplicamos el parche:

# cd /var/www/html/admin/modules/core
# wget https://dl.dropboxusercontent.com/u/1277237/webrtcvals_for_freepbx.patch
# patch  < webrtcvals_for_freepbx.patch

3.- Una vez que hayamos aplicado el parche al crear un dispositivo SIP desde la interface gráfica ahora podremos ver las opciones que nos ayudan a usar WebRTC nativamente:






4.- Para cada dispositivo que usará WebRTC se deberá asignar:
  • transport: ws
  • avpf: yes
  • encryption: yes
  • icesupport: yes

Recompilando Asterisk para tener soporte WebRTC.

Algo que me encontré en mi version de uElastix es que la versión de Asterisk 11.6.0 no viene compilado con la librería uuid-devel la cual es necesaria para tener soporte ICE en Asterisk. 

Sin el soporte de ICE practicamente no podriamos usar ninguna API de WebRTC para Asterisk: JSSIP o SIPML5 de modo que es necesario recompilar Asterisk para que estas APIs puedan funcionar correctamente.

1.- Instalar la librería uuid-devel:

# yum -y install uuid-devel libuuid-devel

2.- Descargar Asterisk y compilarlo:

#  cd /usr/src/
#  mkdir asterisk
#  cd asterisk
#  wget http://downloads.asterisk.org/pub/telephony/asterisk/asterisk-11.6.0.tar.gz && tar zxvf asterisk-11.6.0.tar.gz && cd asterisk-11.6.0 && ./configure && make menuselect

NOTA: Asegurarse en la ventana del Make MenuSelect de tener activados los mismos módulos que exiten en /usr/lib/asterisk/modules/

#  contrib/scripts/get_mp3_source.sh
#  make && make install

3.- Aproximadamente unas 3 horas después, Asterisk debe estar instalado. Ahora nos falta reiniciar el sistema:

# reboot

Instalando el Addon para uElastix

He creado un RPM modificado para esta versión de uElastix el cual pueden descargar desde este enlace. Para instalarlo ejecutar el siguiente comando:

# rpm -ihv elastix-agent_console_webrtc-0.1-2_uElastix.noarch.rpm 


Una vez que esta instalado el sistema y hemos configurado el módulo de CallCenter de Elastix para trabajar en modo CallBack podemos ingresar al a Interfaz Web del módulo y probar. El manual del addon "WebRTC Agent Console" lo pueden descargar desde aquí.

Video:




Conclusiones.

  • Con esto podemos tener un mini CC para al menos 4 personas. 
  • La parte modular se aplica con el tema anterior usando el balanceador de llamadas.
  • Elastix/Asterisk está trabajando nativamente con WebRTC.
  • Para una PyME o un startup ofrecer este servicio es exageradamente económico.
  • Es 100% compatible usar una Raspberry Pi.

Wish List

Ojalá que los desarrolladores de uElastix puedan agregar el soporte de ICE en las nuevas actualizaciones de Asterisk para evitarse la compilación nuevamente y perder 3 horas o más. :P


jueves, 7 de noviembre de 2013

Guía para crear un Case para la AsiriARM

Articulo escrito por: EternalBH    


Materiales a Utilizar


  1.           Asiri
  2.           4 tornillos de 2.5 cm
  3.           4 Taquetes Plásticos de 2 cm
  4.           Placa de Acrílico de 15x18 cm
  5.           Cortador (Exacto)
  6.           Marcador
  7.           Desarmador (De acuerdo a los tornillos)
  8.           Regla
  9.           Taladro o punzón (Para perforar)






1º - Marcar la Placa de Acrilico a la medida de la Asiri (9x15 cm), para poder tener 2 mitades que una será la base y la otra la cubierta. Si pueden comprar las placas de acrílico ya cortadas a la medida pueden omitir este paso.




  
2º- Con el cortador (y con extremo cuidado) pasar unas tropecientas veces por la marca del medio para que podamos realizar el corte, si cuentan con alguna sierra para hacer lo pues eviten el esfuerzo.


  
Al final de muchas pasadas podremos obtener 2 placas de 9x15 cm.




  
3º - Después las acoplaremos para colocar la tarjeta Asiri sobre las placas y luego con el marcador pintaremos los puntos donde haremos los orificios de los tornillos.




Así tendremos los 4 puntos donde irán los tornillos.



4º - Con el taladro haremos los orificios en las placas, les recomiendo sujetarlas firmemente para que no queden disparejos.





Les recomiendo que utilicen una broca con el diámetro del tornillo que utilizaran, deben medirlo con los orificios de la tarjeta Asiri, así se aseguran que todo encaje correctamente. De preferencia que el tornillo quede justo para que pueda crear “cuerda” en el acrílico y de esta manera se fije la tarjeta y se mantenga firme.

5º - Ya con los 4 orificios, con las 2 placas juntas se introducirán los tornillos, para que se aseguren que estén correspondientes los orificios y se ajusten al acrílico.



6º - Medir la altura del conector RJ-45 para poder cortar el taquete plástico, ya que este servirá de soporte para la tarjeta y los acrílicos.





Con los taquetes cortados procederemos a colocarlos a cada uno de los tornillos que atornillaremos en la placa de acrílico que utilizaremos como cubierta.





De esta manera se asegura la primer placa y estos taquetes servirán de postes para que la tarjeta este nivelada.





7º - Colocaremos la tarjeta Asiri en posición de los 4 tornillos de tal manera que se centren para poder colocar la segunda placa, también hay que verificar que la medida de los taquetes plásticos sea la adecuada para sostener la placa.




Como podemos observar la tarjeta se mantiene alineada y con espacio suficiente.

8º - Colocaremos la placa que será la base, de acuerdo a los orificios que anteriormente hicimos.


Recomiendo que vayan ajustando de a poco cada tornillo y que vayan notando la separación de la base con la tarjeta Asiri, como les indique es bueno dejar justos los orificios en el acrílico para que a medida que se atornilla se ajuste la placa y tenga firmeza. Deberá quedar de la siguiente manera.



   
9º - (Opcional) El momento creativo, si quieren pueden decorar de alguna manera su Asiri, yo imprimi los logos de Asiri y Elastix, luego solo remarque las imágenes en la placa y listo un toque artístico J

Dejen salir cualquier idea para personalizarla.






 Saludos!!!!
  

lunes, 4 de noviembre de 2013

Balanceador de Llamadas con OpenSIPS y ASIRI

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) :

# yum install -y git bison flex perl-Frontier-RPC radiusclient-ng-devel lynx libconfuse libconfuse-devel pcre-devel librabbitmq-devel  expat-devel json-c-devel xmlrpc-c-devel mongodb-devel libmicrohttpd-devel GeoIP-devel perl-ExtUtils-Embed python-devel

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.

Para configurar el módulo  "load_balancer.so" escribimos(para más información sobre la configuración: http://www.opensips.org/html/docs/modules/1.10.x/load_balancer.html):

#### LOAD BALANCER module
loadmodule "load_balancer.so"
modparam("load_balancer", "db_url","mysql://opensips:opensipsrw@localhost/opensips")
modparam("load_balancer", "probing_interval", 30)
modparam("load_balancer", "probing_method", "OPTIONS")
modparam("load_balancer", "probing_from", "sip:ospinger@10.0.1.120")
modparam("load_balancer", "probing_reply_codes", "501, 403,200")


Para configurar el módulo "uac_registrant.so" escribimos(para más información sobre la configuración: http://www.opensips.org/html/docs/modules/1.10.x/uac_registrant.html ):
#### 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")


Ya que nuestro hardware es limitado tenemos que cambiar el tamaño del hash a solo 128, esto dentro del módulo dialog, debe quedar así:

#### 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")

Si queremos habilitar el log tenemos que añadir esto:

debug=6
log_stderror=no
log_facility=LOG_LOCAL0
log_name="opensip_LB_5260"

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:

if(load_balance("1","pstn")){
    xlog("sending call to $du\n");
    t_relay();
    exit;
}else{
   send_reply("500","No Destination available");
   exit;
}

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):

# mysql -p opensips

mysql>  INSERT INTO load_balancer(group_id,dst_uri,resources,probe_mode,description) VALUES('1','sip:10.0.1.103:5060','pstn=1','1','Asterisk1-onRPi');

mysql>  INSERT INTO load_balancer(group_id,dst_uri,resources,probe_mode,description) VALUES('1','sip:10.0.1.102:5060','pstn=1','1','Asterisk2-onShaka');

mysql>  INSERT INTO load_balancer(group_id,dst_uri,resources,probe_mode,description) VALUES('1','sip:10.0.1.120:5060','pstn=1','1','Asterisk3-onAsiri');

mysql> SELECT * FROM load_balancer;

+----+----------+---------------------+-----------+------------+-----------------------+
| id | group_id | dst_uri             | resources | probe_mode | description           |
+----+----------+---------------------+-----------+------------+-----------------------+
|  1 |        1 | sip:10.0.1.103:5060 | pstn=1    |          1 | Asterisk1-onRPi | 
|  2 |        1 | sip:10.0.1.102:5060 | pstn=1    |          1 | Asterisk2-onShaka       |
|  3 |        1 | sip:10.0.1.120:5060 | pstn=1    |          1 | Asterisk3-onAsiri |
+----+----------+---------------------+-----------+------------+-----------------------+

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:

# opensipsctl fifo lb_list


Y veremos una salida similar a:

Destination:: sip:10.0.1.120:5060 id=1 group=1 enabled=yes auto-re=on
        Resource:: pstn max=1 load=0
Destination:: sip:10.0.1.103:5060 id=2 group=1 enabled=yes auto-re=on
        Resource:: pstn max=1 load=0
Destination:: sip:10.0.1.105:5060 id=3 group=1 enabled=yes auto-re=on
        Resource:: pstn max=1 load=0

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:

# opensipsctl fifo reg_list


Y veremos un salida similar a:

AOR:: sip:myuser@mysip.provider:5060 expires=3600
state:: REGISTERED_STATE
last_register_sent:: Mon Nov  4 10:39:20 2013
registration_t_out:: Mon Nov  4 11:37:21 2013
registrar:: sip:mysip.provider:5060
binding:: sip:mydid@10.0.1.120:5261
dst_IP:: IPv4 ip=mysipprovider


Video demo:

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.

TODO.

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. 

Interfaz Visual del manejo de las Llamadas.

Saludos ;)