La historia de un proyecto o cómo pasé 7 años creando una PBX basada en Asterisk y PHP

Seguro que muchos de vosotros, como yo, tuvisteis una idea para hacer algo único. En este artículo describiré los problemas técnicos y las soluciones que tuve que afrontar al desarrollar la centralita. Quizás esto ayude a alguien a decidirse por su propia idea y a alguien a seguir el camino ya trillado, porque yo también me beneficié de la experiencia de los pioneros.

La historia de un proyecto o cómo pasé 7 años creando una PBX basada en Asterisk y PHP

Idea y requisitos clave

Y todo empezó simplemente con amor por asterisco (framework para la construcción de aplicaciones de comunicación), automatización de telefonía e instalaciones FreePBX (interfaz web para asterisco). Si las necesidades de la empresa no fueran específicas y estuvieran dentro de las capacidades FreePBX - todo esta bien. Toda la instalación se realizó en XNUMX horas, la empresa recibió una PBX configurada, una interfaz fácil de usar y una breve formación, además de soporte si lo deseaba.

Pero las tareas más interesantes no eran estándar y luego ya no era tan fabuloso. asterisco Puede hacer mucho, pero para mantener la interfaz web en funcionamiento, fue necesario dedicar muchas veces más tiempo. Por lo que un pequeño detalle podría tardar mucho más que instalar el resto de la centralita. Y la cuestión no es que se necesite mucho tiempo para escribir una interfaz web, sino más bien las características arquitectónicas. FreePBX. Enfoques y métodos de arquitectura. FreePBX se presentó en el momento de php4, y en ese momento ya existía php5.6 en el que todo se podía hacer más simple y conveniente.

El colmo fueron los planos de marcación gráficos en forma de diagrama. Cuando intenté construir algo como esto para FreePBX, Me di cuenta de que tendría que reescribirlo significativamente y que sería más fácil construir algo nuevo.

Los requisitos clave fueron:

  • Configuración sencilla, intuitivamente accesible incluso para un administrador novato. Así, las empresas no requieren mantenimiento de PBX por nuestra parte,
  • fácil modificación para que las tareas se resuelvan en el tiempo adecuado,
  • Facilidad de integración con PBX. Ud. FreePBX no había API para cambiar la configuración, es decir No puedes, por ejemplo, crear grupos o menús de voz desde una aplicación de terceros, solo la propia API asterisco,
  • Código abierto: para los programadores esto es extremadamente importante para realizar modificaciones para el cliente.

La idea de un desarrollo más rápido era que toda la funcionalidad consistiera en módulos en forma de objetos. Todos los objetos debían tener una clase principal común, lo que significa que los nombres de todas las funciones principales ya se conocen y, por lo tanto, ya existen implementaciones predeterminadas. Los objetos le permitirán reducir drásticamente la cantidad de argumentos en forma de matrices asociativas con claves de cadena, lo cual puede encontrar en FreePBX Fue posible examinando la función completa y las funciones anidadas. En el caso de los objetos, el autocompletado banal mostrará todas las propiedades y, en general, simplificará la vida muchas veces. Además, la herencia y la redefinición ya resuelven muchos problemas con las modificaciones.

Lo siguiente que ralentizó el tiempo de retrabajo y que valía la pena evitar fue la duplicación. Si hay un módulo responsable de marcar a un empleado, entonces todos los demás módulos que necesiten enviar una llamada a un empleado deben usarlo y no crear sus propias copias. Por lo tanto, si necesita cambiar algo, tendrá que cambiar solo en un lugar y la búsqueda de "cómo funciona" debe realizarse en un solo lugar y no buscarse en todo el proyecto.

Primera versión y primeros errores.

El primer prototipo estuvo listo en un año. Todo el PBX, según lo planeado, era modular y los módulos no solo podían agregar nuevas funciones para procesar llamadas, sino también cambiar la propia interfaz web.

La historia de un proyecto o cómo pasé 7 años creando una PBX basada en Asterisk y PHP
Sí, la idea de construir un plan de marcación en forma de tal esquema no es mía, pero es muy conveniente e hice lo mismo para asterisco.

La historia de un proyecto o cómo pasé 7 años creando una PBX basada en Asterisk y PHP

Al escribir un módulo, los programadores ya podrían:

  • cree su propia funcionalidad para el procesamiento de llamadas, que podría colocarse en el diagrama, así como en el menú de elementos de la izquierda,
  • cree sus propias páginas para la interfaz web y agregue sus plantillas a las páginas existentes (si el desarrollador de la página lo ha previsto),
  • agregue su configuración a la pestaña de configuración principal o cree su propia pestaña de configuración,
  • el programador puede heredar de un módulo existente, cambiar parte de la funcionalidad y registrarla con un nuevo nombre o reemplazar el módulo original.

Por ejemplo, así es como puedes crear tu propio menú de voz:

......
class CPBX_MYIVR extends CPBX_IVR
{
 function __construct()
 {
 parent::__construct();
 $this->_module = "myivr";
 }
}
.....
$myIvrModule = new CPBX_MYIVR();
CPBXEngine::getInstance()->registerModule($myIvrModule,__DIR__); //Зарегистрировать новый модуль
CPBXEngine::getInstance()->registerModuleExtension($myIvrModule,'ivr',__DIR__); //Подменить существующий модуль

Las primeras implementaciones complejas trajeron los primeros orgullos y las primeras decepciones. Me alegré de que funcionara, de que ya podía reproducir las características principales. FreePBX. Me alegré de que a la gente le gustara la idea del plan. Aún quedaban muchas opciones para simplificar el desarrollo, pero ya en aquel momento algunas tareas ya se estaban simplificando.

La API para cambiar la configuración de la PBX fue una decepción: el resultado no fue en absoluto el que queríamos. Tomé el mismo principio que en FreePBX, al hacer clic en el botón Aplicar, se recrea toda la configuración y se reinician los módulos.

Se ve así:

La historia de un proyecto o cómo pasé 7 años creando una PBX basada en Asterisk y PHP
*El plan de marcación es una regla (algoritmo) mediante la cual se procesa una llamada.

Pero con esta opción, es imposible escribir una API normal para cambiar la configuración de PBX. Primero, la operación de aplicar cambios a asterisco demasiado largo y requiere muchos recursos.
En segundo lugar, no puedes llamar a dos funciones al mismo tiempo, porque ambos crearán la configuración.
En tercer lugar, aplica todas las configuraciones, incluidas las realizadas por el administrador.

En esta versión, como en Askozia, fue posible generar la configuración solo de los módulos modificados y reiniciar solo los módulos necesarios, pero todo esto son medidas a medias. Era necesario cambiar el enfoque.

Segunda versión. Nariz sacada cola pegada

La idea para resolver el problema no era recrear la configuración y el plan de marcado para asterisco, pero guarda información en la base de datos y lee desde la base de datos directamente mientras procesa la llamada. asterisco Ya sabía leer configuraciones de la base de datos, simplemente cambie el valor en la base de datos y la próxima llamada se procesará teniendo en cuenta los cambios, y la función era perfecta para leer los parámetros del plan de marcado. REALTIME_HASH.

Al final, ni siquiera hubo necesidad de reiniciar. asterisco al cambiar la configuración y todas las configuraciones comenzaron a aplicarse inmediatamente a asterisco.

La historia de un proyecto o cómo pasé 7 años creando una PBX basada en Asterisk y PHP

Los únicos cambios en el plan de marcado son la adición de números de extensión y consejos. Pero estos fueron pequeños cambios puntuales.

exten=>101,1,GoSub(‘sub-callusers’,s,1(1)); - точечное изменение, добавляется/изменяется через ami

; sub-callusers – универсальная функция генерится при установке модуля.
[sub-callusers]
exten =>s,1,Noop()
exten =>s,n,Set(LOCAL(TOUSERID)=${ARG1})
exten =>s,n,ClearHash(TOUSERPARAM)
exten =>s,n,Set(HASH(TOUSERPARAM)=${REALTIME_HASH(rl_users,id,${LOCAL(TOUSERID)})})
exten =>s,n,GotoIf($["${HASH(TOUSERPARAM,id)}"=""]?return)
...

Puede agregar o cambiar fácilmente una línea en el plan de marcado usando amigo (interfaz de control asterisco) y no es necesario reiniciar todo el plan de marcado.

Esto resolvió el problema con la API de configuración. Incluso podrías ingresar directamente a la base de datos y agregar un nuevo grupo o cambiar, por ejemplo, el tiempo de acceso telefónico en el campo “dialtime” del grupo y la siguiente llamada ya duraría el tiempo especificado (Esto no es una recomendación para acción, ya que algunas operaciones API requieren amigo llamadas).

Las primeras implementaciones difíciles trajeron nuevamente el primer orgullo y decepción. Me alegré de que funcionara. La base de datos se convirtió en un vínculo crítico, aumentó la dependencia del disco, hubo más riesgos, pero todo funcionó de manera estable y sin problemas. Y lo más importante, ahora todo lo que se podía hacer a través de la interfaz web se podía hacer a través de la API y se utilizaban los mismos métodos. Además, la interfaz web eliminó el botón "aplicar configuración a PBX", que los administradores a menudo olvidaban.

La decepción fue que el desarrollo se volvió más complicado. Desde la primera versión, el lenguaje PHP ha generado un plan de marcado en el lenguaje asterisco y parece completamente ilegible, además del idioma en sí. asterisco escribir un plan de marcación es extremadamente primitivo.

Cómo se veía:

$usersInitSection = $dialplan->createExtSection('usersinit-sub','s');
$usersInitSection
 ->add('',new Dialplanext_gotoif('$["${G_USERINIT}"="1"]','exit'))
 ->add('',new Dialplanext_set('G_USERINIT','1'))
 ->add('',new Dialplanext_gosub('1','s','sub-AddOnAnswerSub','usersconnected-sub'))
 ->add('',new Dialplanext_gosub('1','s','sub-AddOnPredoDialSub','usersinitondial-sub'))
 ->add('',new Dialplanext_set('LOCAL(TECH)','${CUT(CHANNEL(name),/,1)}'))
 ->add('',new Dialplanext_gotoif('$["${LOCAL(TECH)}"="SIP"]','sipdev'))
 ->add('',new Dialplanext_gotoif('$["${LOCAL(TECH)}"="PJSIP"]','pjsipdev'))

En la segunda versión, el plan de marcado se volvió universal, incluía todas las opciones de procesamiento posibles según los parámetros y su tamaño aumentó significativamente. Todo esto ralentizó enormemente el tiempo de desarrollo, y la sola idea de que una vez más era necesario interferir con el plan de marcado me entristeció.

Tercera versión

La idea para solucionar el problema no era generar asterisco marcar plan desde php y usar RápidoAGI y escriba todas las reglas de procesamiento en el propio PHP. RápidoAGI permite asterisco, para procesar la llamada, conéctese al socket. Reciba comandos desde allí y envíe resultados. Por tanto, la lógica del dialplan ya está fuera de los límites. asterisco y se puede escribir en cualquier idioma, en mi caso en PHP.

Hubo mucho ensayo y error. El principal problema era que ya tenía muchas clases/archivos. Se necesitaron aproximadamente 1,5 segundos para crear objetos, inicializarlos y registrarse entre sí, y este retraso por llamada no es algo que pueda ignorarse.

La inicialización debería haber ocurrido solo una vez y, por lo tanto, la búsqueda de una solución comenzó escribiendo un servicio en php usando subprocesos. Después de una semana de experimentación, esta opción se dejó de lado debido a las complejidades de cómo funciona esta extensión. Después de un mes de pruebas, también tuve que abandonar la programación asincrónica en PHP; necesitaba algo simple, familiar para cualquier principiante de PHP, y muchas extensiones para PHP son sincrónicas.

La solución fue nuestro propio servicio multiproceso en C, que fue compilado con PHPLIB. Carga todos los archivos php de ATS, espera a que se inicialicen todos los módulos, agrega una devolución de llamada entre sí y, cuando todo está listo, lo almacena en caché. Al preguntar por RápidoAGI Se crea una secuencia, se reproduce una copia del caché de todas las clases y datos y la solicitud se pasa a la función php.

Con esta solución, el tiempo desde que se envía una llamada a nuestro servicio hasta el primer comando asterisco disminuyó de 1,5 segundos a 0,05 segundos y este tiempo depende ligeramente del tamaño del proyecto.

La historia de un proyecto o cómo pasé 7 años creando una PBX basada en Asterisk y PHP

Como resultado, el tiempo para el desarrollo del plan de marcado se redujo significativamente y puedo apreciar esto ya que tuve que reescribir el plan de marcado completo de todos los módulos en PHP. En primer lugar, los métodos para obtener un objeto de la base de datos ya deberían estar escritos en PHP, eran necesarios para su visualización en la interfaz web, y en segundo lugar, y esto es lo principal, finalmente es posible trabajar cómodamente con cadenas con números y matrices. con base de datos más muchas extensiones PHP.

Para procesar el plan de marcado en la clase de módulo, necesita implementar la función plan de marcaciónDynamicCall y argumento solicitud de llamada pbx contendrá un objeto para interactuar con asterisco.

La historia de un proyecto o cómo pasé 7 años creando una PBX basada en Asterisk y PHP

Además, fue posible depurar el plan de marcado (php tiene xdebug y funciona para nuestro servicio), puede avanzar paso a paso viendo los valores de las variables.

datos de llamada

Cualquier análisis e informe requiere datos recopilados correctamente, y este bloque PBX también pasó por muchas pruebas y errores desde la primera hasta la tercera versión. A menudo, los datos de las llamadas son una señal. Una llamada = una grabación: quién llamó, quién respondió, cuánto tiempo hablaron. En opciones más interesantes, hay un cartel adicional que indica a qué empleado de la centralita se llamó durante la llamada. Pero todo esto cubre sólo una parte de las necesidades.

Los requisitos iniciales fueron:

  • guarde no solo a quién llamó el PBX, sino también quién respondió, porque hay intercepciones y esto deberá tenerse en cuenta al analizar las llamadas,
  • tiempo antes de conectarse con un empleado. En FreePBX y algunas otras PBX, la llamada se considera contestada tan pronto como la PBX contesta el teléfono. Pero para el menú de voz ya es necesario levantar el teléfono, por lo que se responden todas las llamadas y el tiempo de espera para una respuesta es de 0 a 1 segundo. Por lo tanto, se decidió ahorrar no solo el tiempo antes de una respuesta, sino también el tiempo antes de conectarse con los módulos clave (el propio módulo establece esta bandera. Actualmente es "Empleado", "Línea externa"),
  • para un plan de marcación más complejo, cuando una llamada viaja entre diferentes grupos, era necesario poder examinar cada elemento por separado.

La mejor opción resultó ser que los módulos PBX envíen información sobre ellos mismos durante las llamadas y finalmente guarden la información en forma de árbol.

Se parece a esto:

Primero, información general sobre la llamada (como todos los demás, nada especial).

La historia de un proyecto o cómo pasé 7 años creando una PBX basada en Asterisk y PHP

  1. Recibí una llamada en una línea exterior "Para la masa"a las 05:55:52 del numero 89295671458 al numero 89999999999, al final me contesto un empleado"secretaria2» con el número 104. El cliente esperó 60 segundos y habló durante 36 segundos.
  2. Empleado "secretaria2"llama al 112 y contesta un empleado"Gerente1» después de 8 segundos. Hablan durante 14 segundos.
  3. El Cliente se transfiere al Empleado "gerente1"donde siguen hablando por otros 13 segundos

Pero esto es la punta del iceberg: para cada registro puedes obtener un historial de llamadas detallado a través del PBX.

La historia de un proyecto o cómo pasé 7 años creando una PBX basada en Asterisk y PHP

Toda la información se presenta como un anidamiento de llamadas:

  1. Recibí una llamada en una línea exterior "Para la masa» a las 05:55:52 del número 89295671458 al número 89999999999.
  2. A las 05:55:53 la línea exterior envía una llamada al circuito Entrante"test»
  3. Al procesar una llamada según el esquema, el módulo "llamada del gerente", en el que la llamada dura 16 segundos. Este es un módulo desarrollado para el cliente.
  4. Módulo "llamada del gerente" envía una llamada al empleado responsable del número (cliente) "Gerente1”Y espera 5 segundos para recibir una respuesta. El gerente no respondió.
  5. Módulo "llamada del gerente"envía una llamada al grupo"Gerentes CORP" Estos son otros gerentes de la misma dirección (sentados en la misma habitación) y esperando 11 segundos por una respuesta.
  6. Grupo "Gerentes CORP"llama a los empleados"Gerente1, Gerente2, Gerente3"simultáneamente durante 11 segundos. Sin respuesta.
  7. Finaliza la llamada del gerente. Y el circuito envía una llamada al módulo "Seleccionar una ruta desde 1c" También un módulo escrito para el cliente. Aquí la llamada fue procesada durante 0 segundos.
  8. El circuito envía una llamada al menú de voz "Básico con marcación adicional" El cliente esperó allí durante 31 segundos, no hubo marcación adicional.
  9. El esquema envía una llamada al Grupo "secretarias", donde el cliente esperó 12 segundos.
  10. En un grupo se llama a 2 empleados al mismo tiempo "secretaria1"Y"secretaria2" y después de 12 segundos el empleado responde "secretaria2" La respuesta a la llamada se duplica en las llamadas de los padres. Resulta que en el grupo respondió “secretaria2", al llamar al circuito respondió "secretaria2" y respondió la llamada en la línea exterior con "secretaria2".

Es el almacenamiento de información sobre cada operación y su anidamiento lo que permitirá realizar informes de forma sencilla. Un informe en el menú de voz le ayudará a saber cuánto ayuda o dificulta. Construya un informe sobre las llamadas perdidas de los empleados, teniendo en cuenta que la llamada fue interceptada y, por lo tanto, no se considera perdida, y teniendo en cuenta que fue una llamada grupal y alguien más respondió antes, lo que significa que la llamada tampoco se perdió.

Dicho almacenamiento de información le permitirá tomar cada grupo por separado y determinar con qué eficacia funciona, y crear un gráfico de los grupos respondidos y perdidos por hora. También puede comprobar qué tan precisa es la conexión con el administrador responsable analizando las transferencias después de conectarse con el administrador.

También se pueden realizar estudios bastante atípicos, por ejemplo, con qué frecuencia los números que no están en la base de datos marcan la extensión correcta o qué porcentaje de las llamadas salientes se desvían a un teléfono móvil.

¿El resultado?

Para el mantenimiento de la centralita no es necesario un especialista, lo puede hacer el administrador más común, probado en la práctica.

Para realizar modificaciones no se necesitan especialistas con calificaciones serias, basta con conocimientos de PHP, ya que Ya se han escrito módulos para el protocolo SIP, para la cola, para llamar a un empleado y otros. Hay una clase contenedora para asterisco. Para desarrollar un módulo, un programador puede (y en el buen sentido debería) llamar a módulos ya preparados. Y el conocimiento asterisco son completamente innecesarios si el cliente solicita agregar una página con algún informe nuevo. Pero la práctica demuestra que, aunque los programadores externos pueden arreglárselas, se sienten inseguros sin documentación y sin una cobertura normal de los comentarios, por lo que todavía hay margen de mejora.

Los módulos pueden:

  • crear nuevas capacidades de procesamiento de llamadas,
  • agregar nuevos bloques a la interfaz web,
  • heredar de cualquiera de los módulos existentes, redefinir funciones y reemplazarlo, o simplemente ser una copia ligeramente modificada,
  • agregue su configuración a la plantilla de configuración de otros módulos y mucho más.

Configuración de PBX vía API. Como se describió anteriormente, todas las configuraciones se almacenan en la base de datos y se leen en el momento de la llamada, por lo que puede cambiar todas las configuraciones de PBX a través de la API. Al llamar a la API, la configuración no se recrea y los módulos no se reinician, por lo tanto, no importa cuántas configuraciones y empleados tenga. Las solicitudes de API se ejecutan rápidamente y no se bloquean entre sí.

La centralita almacena todas las operaciones clave con llamadas con duración (espera/conversación), anidadas y en términos de centralita (empleado, grupo, línea externa, no canal, número). Esto le permite crear varios informes para clientes específicos y la mayor parte del trabajo consiste en crear una interfaz fácil de usar.

El tiempo dirá qué pasará a continuación. Todavía hay muchos matices que hay que rehacer, todavía hay muchos planes, pero ha pasado un año desde la creación de la 3ª versión y ya podemos decir que la idea está funcionando. La principal desventaja de la versión 3 son los recursos de hardware, pero esto suele ser por lo que hay que pagar para facilitar el desarrollo.

Fuente: habr.com

Añadir un comentario