Una de las cosas que llama fuertemente la atención cuando se empieza a trabajar con Asterisk es la posibilidad de incluir expresiones matemáticas con operadores Booleanos y operadores matemáticos en el DialPlan de Asterisk. A primera vista parece algo extraño y sin relación con una “centralita telefónica”, pero nada más lejos de la realidad, ya que estos operadores permiten realizar tareas dentro del DialPlan que son absolutamente imprescindibles en el funcionamiento de una PBX.
Los operadores matemáticos nos permiten realizar operaciones matemáticas como las siguientes:
- same => n,Set(X=3) Asignamos un valor a una variable
- same => n,Set(X=$[${X}+1]) Incrementamos en 1 el valor de una variable
- same => n,Set(X=$[${X}*2]) Multiplicamos una variable por 2
- same => n,set(Z=$[${X}+${Y}]) Asignamos a Z la suma de las variables X e Y
Por otra parte, los operadores booleanos junto con la aplicación GotoIf() nos permiten realizar bifurcaciones en el DialPlan de una forma sencilla:
- same => n,GotoIf($[${hora}<=24]?hora_correcta:hora_incorrecta) En este caso si la variable “hora” es menor o igual que 24, la ejecución del DialPlan se bifurcará a la etiqueta denominada “hora_correcta” y en caso contrario se bifurcará hacia la etiqueta denominada “hora_incorrecta)
- same => n,GotoIf($[${dia}<=31&${dia}>=0]?dia_correcto:dia_incorrecto) En este otro caso, si la variable “día” está comprendida entre 1 y 31, la ejecución del DialPlan se bifurcará hacia la etiqueta “dia_correcto” y en caso contrario, se bifurcará a la etiqueta “día_incorrecto”
Existen más operadores matemáticos y booleanos, y si se desea utilizarlos, se recomienda consultar en la wiki de Asterisk o en la múltiple documentación al respecto existente en Internet. Pero la pregunta inicial sigue en el aire: ¿Para que sirve todo esto en una centralita telefónica? Y ahora, al ver los ejemplos propuestos, ya es posible adivinarle a todo esto, por lo menos una utilidad clara, aunque hay muchas más: La validación de los números introducidos por un usuario a través del teclado telefónico. Este es el caso del denominado Servicio Horario, en el cual desde el teléfono de operadora es necesario introducir cosas como las horas de comienzo y fin de la actividad en la empresa, los días festivos o el mes de vacaciones, a fin de que la operadora automática reproduzca en cada caso el mensaje de audio adecuado y dirija las llamadas entrantes de la forma deseada.
En la anterior entrada de este blog se ha visto un ejemplo de programación del Servicio Horario dentro del DialPlan, pero en dicho ejemplo no se había previsto que el usuario pudiera introducir datos erróneos, como por ejemplo una hora mayor que 24, un día mayor que 31 o un mes mayor que 12. A continuación se expone el DialPlan modificado, con el código necesario para tratar estos posibles errores por parte del usuario:
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
;x
;x CODIGO GRABACION VALORES EN ASTERISK DATABASE
;x PARA ZONAS HORARIAS DE LA EMPRESA
;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
; CODIGO “410” PARA AJUSTE DEL HORARIO DE INICIO DE ACTIVIDAD EN LA EMPRESA
; INTRODUCIR LA HORA DE COMIENZO, DESDE 00 HASTA 24
exten => _410XX,1,Set(hora=${EXTEN:-2})
same => n,GotoIf($[${hora}<=24]?hora_correcta:hora_incorrecta)
same => n(hora_correcta),Set(DB(horarios/inicio)=${EXTEN:-2}:00)
same => n,Wait(1)
same => n,Playback(open)
same => n,SayNumber(${DB(horarios/inicio)})
same => n,Playback(hours)
same => n,Wait(1)
same => n,Playback(beep)
same => n,Hangup()
same => n(hora_incorrecta),Playback(an-error-has-occured)
same => n,Playback(you-have-dialed)
same => n,SayNumber(${hora})
same => n,Playback(hours)
same => n,Playback(nothing-recorded&no_worries_try_again)
same => n,Playback(beep)
same => n,Hangup()
; CODIGO “411XX” PARA AJUSTE DEL HORARIO DE FIN DE ACTIVIDAD EN LA EMPRESA
; INTRODUCIR LA HORA DE FIN DE ACTIVIDAD, DESDE 00 HASTA 24
exten => _411XX,1,Set(hora=${EXTEN:-2})
same => n,GotoIf($[${hora}<=24]?hora_correcta:hora_incorrecta)
same => n(hora_correcta),Set(DB(horarios/fin)=${EXTEN:-2}:00)
same => n,Wait(1)
same => n,Playback(closed)
same => n,SayNumber(${DB(horarios/fin)})
same => n,Playback(hours)
same => n,Wait(1)
same => n,Playback(beep)
same => n,Hangup()
same => n(hora_incorrecta),Playback(an-error-has-occured)
same => n,Playback(you-have-dialed)
same => n,SayNumber(${hora})
same => n,Playback(hours)
same => n,Playback(nothing-recorded&no_worries_try_again)
same => n,Playback(beep)
same => n,Hangup()
; CODIGO “412XX” PARA AJUSTE DE UN DIA FESTIVO
; INTRODUCIR LA FECHA DEL DIA FESTIVO, DESDE 01 HASTA 31
; PARA LA CONFIGURACION SIN FESTIVOS INTRODUCIR DIA 00
exten => _412XX,1,Set(dia=${EXTEN:-2})
same => n,GotoIf($[${dia}<=31&${dia}>=0]?dia_correcto:dia_incorrecto)
same => n(dia_correcto),Set(DB(horarios/festivo)=${EXTEN:-2})
same => n,Wait(1)
same => n,Playback(day)
same => n,SayNumber(${DB(horarios/festivo)})
same => n,Wait(1)
same => n,Playback(beep)
same => n,Hangup()
same => n(dia_incorrecto),Playback(an-error-has-occured)
same => n,Playback(you-have-dialed)
same => n,Playback(day)
same => n,SayNumber(${dia})
same => n,Playback(nothing-recorded&no_worries_try_again)
same => n,Playback(beep)
same => n,Hangup()
; CODIGO “413XX” PARA AJUSTE DEL MES DE VACACIONES
; INTRODUCIR MES DE VACACIONES DESDE 01 PARA ENERO HASTA 12 PARA DICIEMBRE. ;INTRODUCIR 00 PARA MES NO FESTIVO
exten => _413XX,1,Set(mes=${EXTEN:-2})
same => n,GotoIf($[${mes}<=12&${mes}>=0]?mes_correcto:mes_incorrecto)
same => n(mes_correcto),Set(DB(horarios/vacaciones)=${EXTEN:-2})
same => n,GotoIf($[${mes}=0]?mes_cero)
same => n,Wait(1)
same => n,Set(mes=$[${EXTEN:-2} – 1])
same => n,Playback(month)
same => n,Playback(/usr/share/asterisk/sounds/es/digits/mon-${mes})
same => n,Wait(1)
same => n,Playback(beep)
same => n,Hangup()
same => n(mes_incorrecto),Playback(an-error-has-occured)
same => n,Playback(you-have-dialed)
same => n,Playback(month)
same => n,SayNumber(${mes})
same => n,Playback(nothing-recorded&no_worries_try_again)
same => n,Playback(beep)
same => n,Hangup()
same => n(mes_cero),Playback(you-have-dialed)
same => n,Playback(month)
same => n,SayNumber(${mes})
same => n,Playback(beep)
same => n,Hangup()
A la vista de los ejemplos de operadores matemáticos y booleanos comentados anteriormente, el código del DialPlan mostrado se entiende sin dificultad, ya que en cada caso se verifica que las horas introducidas están en el rango de 00 a 24, los días en el rango de 0 a 31 y los meses en el rango de 0 a 12. (Nota: Se ha previsto que el usuario pueda introducir el día 0 y el mes 0, a fin de contemplar aquellos casos donde no hay días festivos en el mes y donde no hay meses de vacaciones (la empresa mantienen la actividad todo el año).
Cuando el valor introducido es correcto, se almacena en la base de datos de Asterisk en la clave correspondiente. Cuando el valor introducido es incorrecto, se notifica al usuario mediante un mensaje de audio y se ignora el valor introducido, no modificándose el valor previo existente en la base de datos. Por último y como curiosidad, se debe de tener en cuenta lo siguiente:
- Los mensajes de audio se han compuesto con los ficheros de audio ya disponibles en el propio Asterisk, en el directorio /usr/share/asterisk/sounds. Como todos están en la ruta por defecto, no hay necesidad de indicar tal ruta en la aplicación Playback()
- Los mensajes de audio correspondientes a los meses (enero, febrero, marzo……) no están en el directorio por defecto, sino que están en /usr/share/asterisk/sounds/es/digits. Por eso se ha tenido que incluir la ruta en la aplicación Playback() hasta dichos ficheros de audio.
- Los ficheros de audio correspondientes a los meses tienen por nombre mon-0, mon-1,mon-2…….mon-11. Por eso, para localizar el fichero correspondiente le restamos 1 al valor del mes introducido por el usuario, que va desde 1 para enero hasta 12 para diciembre.
- Si el usuario pulsa por error la tecla correspondiente al asterisco o a la almohadilla, la llamada será colgada de inmediato, ya que en los patrones de llamada solo se espera la introducción de números.
Puesto que ahora es posible introducir el valor dia=00 para indicar mes sin días festivos o el valor mes=00 para indicar que la empresa mantiene su actividad todos los meses del año, surge un nuevo problema y es que la aplicación GotoIfTime() da un warning cuando tiene que comparar el día actual con el valor 0 y también da un warning cuando tienen que comparar el mes actual con el valor 0. En ambos casos el DialPlan funciona correctamente, ya que Asterisk ignora los resultados de las comparaciones anteriores, pero para evitar estos avisos de warning en la consola de Asterisk, se ha preferido modificar un poco el código inicial del DialPlan, de tal forma que cuando el día=0 o cuando mes=0, se saltan las respectivas líneas del DialPlan donde se compara el día introducido con el día actual y el mes introducido con el mes actual. La parte afectada del DialPlan queda como sigue:
; ENTRANTES DESDE SAREVOZ
[LlamadasEntrantesSarenet]
; CODIGO PARA EL FUNCIONAMIENTO DE LA OPERADORA AUTOMATICA
; CUANDO HAY UNA LLAMADA ENTRANTE DESDE EL CONTEXTO “LlamadasEntrantesSarenet”
exten => s,1,NoOp(Salto en la entrada de llamadas según horario de la empresa)
same => n,Set(hora_inicio=${DB(horarios/inicio)})
same => n,Set(hora_fin=${DB(horarios/fin)})
same => n,Set(mes_vacaciones=${DB(horarios/vacaciones)})
same => n,Set(dia_festivo=${DB(horarios/festivo)})
same => n,GotoIf($[${dia_festivo}=0]?no_festivos)
same => n,GotoIfTime(*,*,${dia_festivo},*?dia_festivo,s,1)
same => n(no_festivos),GotoIf($[${mes_vacaciones}=0]?no_vacaciones)
same => n,GotoIfTime(*,*,*,${mes_vacaciones},*?horario_vacaciones,s,1)
same => n(no_vacaciones),GotoIfTime(*,sat-sun,*,*?horario_noche,s,1)
same => n,GotoIfTime(${hora_inicio}-${hora_fin},mon-fri,*,*?horario_dia,s,1:horario_noche,s,1)
same => n, Hangup()
[horario_dia]
exten => s,1,Background(/var/lib/asterisk/sounds/mensajeoperadora1)
same => n,WaitExten(5)
same => n,goto(sinmarcacion,1,1)
same => n,Hangup()
; TRATAMIENTO DE LAS OPCIONES DE LA OPERADORA 1 => 201, 2 => 202 3 => 203 4 => 204
exten => 1,1,Dial(SIP/201)
same => n,Hangup()
exten => 2,1,Dial(SIP/202)
same => n,Hangup()
exten => 3,1,Dial(SIP/203)
same => n,Hangup()
exten => 4,1,Dial(SIP/204)
same => n,Hangup()
exten => _[0-56789]!,1,Goto(sinmarcacion,1,1)
same => n,Hangup()
[sinmarcacion]
exten => 1,1,Playback(/var/lib/asterisk/sounds/mensajetransferirllamadaoperador)
same => n,Wait(1)
same => n,Dial(SIP/201)
same => n,Hangup()
[horario_vacaciones]
exten => s,1,Playback(/var/lib/asterisk/sounds/mensajehorariovacaciones)
same => n,Wait(1)
same => n,Playback(beep)
same => n,Hangup()
[dia_festivo]
exten => s,1,Playback(/var/lib/asterisk/sounds/mensajediafestivo)
same => n,Wait(1)
same => n,Playback(beep)
same => n,Hangup()
[horario_noche]
exten => s,1,Playback(/var/lib/asterisk/sounds/mensajehorarionoche)
same => n,Wait(1)
same => n,Playback(beep)
same => n,Hangup()
; FIN DEL CONTEXTO PARA LLAMADAS ENTRANTES DESDE EL TRUNK SIP SARENET
En el fragmento mostrado del DialPlan se han marcado en negrita las líneas donde sendos GotoIf() “saltan” las correspondientes aplicaciones GotoIfTime() donde se comparan las variables día_festivo con el día del sistema y mes_vacaciones con el mes del sistema. De esta forma nunca se evalua mediante la aplicación GotoIfTime() un valor de día con el día 0 ni un valor de mes con el mes 0, que como ya hemos indicado, da un mensaje de warning en el CLI de Asterisk
En cualquier caso y como ya se indicó en la anterior entrada de este mismo blog y al contrario de lo que sucede con las PBX convencionales, los límites de Asterisk solo vienen dados por la imaginación y las ganas de trabajar del programador. En el siguiente vídeo se puede observar de forma práctica el funcionamiento del DialPlan anterior:
Funcionamiento del DialPlan con verificación de valores introducidos por el usuario
Para terminar esta aplicación, se ha añadido el código de marcado 414 al DialPlan, el cual permite consultar de forma sencilla los valores programados en el Servicio Horario. El código añadido al DialPlan es el siguiente:
; CODIGO “414” PARA CONSULTA DE LOS VALORES PROGRAMADOS EN EL SERVICIO HORARIO
exten => _414,1,NoOP()
same => n,Wait(1)
same => n,Playback(/usr/share/asterisk/sounds/open)
same => n,SayNumber(${DB(horarios/inicio)})
same => n,Playback(hours)
same => n,Wait(1)
same => n,Playback(/usr/share/asterisk/sounds/closed)
same => n,SayNumber(${DB(horarios/fin)})
same => n,Playback(hours)
same => n,Wait(1)
same => n,Playback(day)
same => n,SayNumber(${DB(horarios/festivo)})
same => n,Wait(1)
same => n,Set(mes=${DB(horarios/vacaciones)})
same => n,Set(mes=$[${mes} – 1])
same => n,Playback(month)
same => n,Playback(/usr/share/asterisk/sounds/es/digits/mon-${mes})
same => n,Wait(1)
same => n,Playback(beep)
same => n,Hangup()
Comprobación de los valores programados en el Servicio Horario