A história de um projeto ou como passei 7 anos criando um PABX baseado em Asterisk e Php

Certamente muitos de vocês, como eu, tiveram a ideia de fazer algo único. Neste artigo descreverei os problemas técnicos e soluções que tive que enfrentar ao desenvolver o PABX. Talvez isso ajude alguém a decidir sobre sua própria ideia e a seguir o caminho já trilhado, porque também me beneficiei da experiência de pioneiros.

A história de um projeto ou como passei 7 anos criando um PABX baseado em Asterisk e Php

Ideia e requisitos principais

E tudo começou simplesmente com amor por asterisco (framework para construção de aplicações de comunicação), automação de telefonia e instalações FreePBX (interface web para asterisco). Se as necessidades da empresa não fossem específicas e estivessem dentro das capacidades FreePBX - tudo é bom. Toda a instalação ocorreu em XNUMX horas, a empresa recebeu um PBX configurado, uma interface amigável e um breve treinamento além de suporte se desejar.

Mas as tarefas mais interessantes não eram padronizadas e não eram tão fabulosas. asterisco pode fazer muito, mas para manter a interface web em funcionamento foi necessário gastar muito mais tempo. Portanto, um pequeno detalhe pode demorar muito mais do que instalar o restante do PABX. E a questão não é que leva muito tempo para escrever uma interface web, mas sim os recursos arquitetônicos FreePBX. Abordagens e métodos de arquitetura FreePBX foi desenhado na época do php4, e naquele momento já existia o php5.6 no qual tudo poderia ser mais simples e conveniente.

A gota d’água foram os dialplans gráficos na forma de um diagrama. Quando tentei construir algo assim para FreePBX, percebi que teria que reescrevê-lo significativamente e seria mais fácil construir algo novo.

Os principais requisitos foram:

  • configuração simples, intuitivamente acessível até mesmo para um administrador iniciante. Assim, as empresas não exigem manutenção de PABX de nossa parte,
  • fácil modificação para que as tarefas sejam resolvidas em tempo adequado,
  • facilidade de integração com PABX. você FreePBX não havia API para alterar as configurações, ou seja, Você não pode, por exemplo, criar grupos ou menus de voz a partir de um aplicativo de terceiros, apenas a própria API asterisco,
  • código aberto - para programadores isso é extremamente importante para modificações no cliente.

A ideia de um desenvolvimento mais rápido era fazer com que todas as funcionalidades consistissem em módulos na forma de objetos. Todos os objetos deveriam ter uma classe pai comum, o que significa que os nomes de todas as funções principais já são conhecidos e, portanto, já existem implementações padrão. Os objetos permitirão reduzir drasticamente o número de argumentos na forma de matrizes associativas com chaves de string, que você pode descobrir em FreePBX Isso foi possível examinando toda a função e funções aninhadas. No caso de objetos, o preenchimento automático banal mostrará todas as propriedades e, em geral, simplificará muitas vezes a vida. Além disso, a herança e a redefinição já resolvem muitos problemas com modificações.

A próxima coisa que atrasou o tempo de retrabalho e que valeu a pena evitar foi a duplicação. Caso exista um módulo responsável por discar para um funcionário, todos os demais módulos que necessitem enviar uma ligação para um funcionário deverão utilizá-lo, e não criar suas próprias cópias. Então, se precisar mudar alguma coisa, então você terá que mudar apenas em um lugar e a busca por “como funciona” deverá ser feita em um só lugar, e não pesquisada ao longo de todo o projeto.

Primeira versão e primeiros erros

O primeiro protótipo ficou pronto em um ano. Todo o PABX, conforme planejado, era modular, e os módulos poderiam não apenas agregar novas funcionalidades para processamento de chamadas, mas também alterar a própria interface web.

A história de um projeto ou como passei 7 anos criando um PABX baseado em Asterisk e Php
Sim, a ideia de construir um dialplan na forma de tal esquema não é minha, mas é muito conveniente e fiz o mesmo para asterisco.

A história de um projeto ou como passei 7 anos criando um PABX baseado em Asterisk e Php

Ao escrever um módulo, os programadores já poderiam:

  • crie sua própria funcionalidade para processamento de chamadas, que poderá ser colocada no diagrama, bem como no menu de elementos à esquerda,
  • crie suas próprias páginas para a interface da web e adicione seus modelos às páginas existentes (se o desenvolvedor da página tiver fornecido isso),
  • adicione suas configurações à guia de configurações principal ou crie sua própria guia de configurações,
  • o programador pode herdar de um módulo existente, alterar parte da funcionalidade e registrá-lo com um novo nome ou substituir o módulo original.

Por exemplo, é assim que você pode criar seu próprio menu 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__); //Подменить существующий модуль

As primeiras implementações complexas trouxeram os primeiros orgulhos e as primeiras decepções. Fiquei feliz por ter funcionado, por já ter conseguido reproduzir as principais características FreePBX. Fiquei feliz que as pessoas gostaram da ideia do esquema. Ainda havia muitas opções para simplificar o desenvolvimento, mas mesmo naquela época algumas tarefas já estavam sendo facilitadas.

A API para alterar a configuração do PBX foi uma decepção - o resultado não foi nada do que queríamos. Eu tomei o mesmo princípio que em FreePBX, clicando no botão Aplicar, toda a configuração é recriada e os módulos são reiniciados.

Parece assim:

A história de um projeto ou como passei 7 anos criando um PABX baseado em Asterisk e Php
*Dialplan é uma regra (algoritmo) pela qual uma chamada é processada.

Mas com esta opção, é impossível escrever uma API normal para alterar as configurações do PBX. Primeiro, a operação de aplicar alterações a asterisco demasiado longo e intensivo em recursos.
Em segundo lugar, você não pode chamar duas funções ao mesmo tempo, porque ambos criarão a configuração.
Em terceiro lugar, aplica todas as configurações, incluindo aquelas feitas pelo administrador.

Nesta versão, como em Askozia, foi possível gerar a configuração apenas dos módulos alterados e reiniciar apenas os módulos necessários, mas tudo isso são meias medidas. Foi necessário mudar a abordagem.

Segunda versão. Nariz puxado para fora, cauda presa

A ideia para resolver o problema não era recriar a configuração e o dialplan para asterisco, mas salve as informações no banco de dados e leia-as diretamente durante o processamento da chamada. asterisco Eu já sabia ler as configurações do banco de dados, basta alterar o valor no banco de dados e a próxima chamada será processada levando em consideração as alterações, e a função ficou perfeita para ler os parâmetros do dialplan REALTIME_HASH.

No final, não houve necessidade nem de reiniciar asterisco ao alterar as configurações e todas as configurações começaram a ser aplicadas imediatamente para asterisco.

A história de um projeto ou como passei 7 anos criando um PABX baseado em Asterisk e Php

As únicas alterações no plano de discagem são a adição de números de ramal e dicas. Mas essas foram pequenas mudanças pontuais

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

Você pode facilmente adicionar ou alterar uma linha no dialplan usando A mim (interface de controle asterisco) e nenhuma reinicialização de todo o dialplan é necessária.

Isso resolveu o problema com a API de configuração. Você pode até entrar diretamente no banco de dados e adicionar um novo grupo ou alterar, por exemplo, o tempo de discagem no campo “dialtime” do grupo e a próxima chamada já durará o tempo especificado (Isso não é uma recomendação para ação, já que algumas operações da API exigem A mim chamadas).

As primeiras implementações difíceis trouxeram novamente o primeiro orgulho e decepção. Fiquei feliz que funcionou. O banco de dados passou a ser um elo crítico, a dependência do disco aumentou, havia mais riscos, mas tudo funcionou de forma estável e sem problemas. E o mais importante, agora tudo o que poderia ser feito através da interface web poderia ser feito através da API, e os mesmos métodos foram usados. Além disso, a interface da web eliminou o botão “aplicar configurações ao PBX”, que os administradores muitas vezes esqueciam.

A decepção foi que o desenvolvimento ficou mais complicado. Desde a primeira versão, a linguagem PHP gera um dialplan na linguagem asterisco e parece completamente ilegível, além do próprio idioma asterisco para escrever um dialplan é extremamente primitivo.

Como era:

$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'))

Na segunda versão, o dialplan tornou-se universal, incluía todas as opções de processamento possíveis dependendo dos parâmetros e seu tamanho aumentou significativamente. Tudo isso retardou muito o tempo de desenvolvimento, e só de pensar que mais uma vez era necessário interferir no dialplan me entristeceu.

Terceira versão

A ideia para resolver o problema não era gerar asterisco dialplan do php e usar FastAGI e escreva todas as regras de processamento no próprio PHP. FastAGI permite asterisco, para processar a chamada, conecte-se ao soquete. Receba comandos de lá e envie resultados. Assim, a lógica do dialplan já está fora dos limites asterisco e pode ser escrito em qualquer linguagem, no meu caso em PHP.

Houve muita tentativa e erro. O principal problema era que eu já tinha muitas classes/arquivos. Demorou cerca de 1,5 segundos para criar objetos, inicializá-los e registrar-se entre si, e esse atraso por chamada não é algo que pode ser ignorado.

A inicialização deveria ter acontecido apenas uma vez e portanto a busca por uma solução começou escrevendo um serviço em php usando PthreadsGenericName. Após uma semana de experimentação, esta opção foi arquivada devido às complexidades de como esta extensão funciona. Depois de um mês de testes, também tive que abandonar a programação assíncrona em PHP; precisava de algo simples, familiar para qualquer iniciante em PHP, e muitas extensões para PHP são síncronas.

A solução foi nosso próprio serviço multithread em C, que foi compilado com PHPLIB. Ele carrega todos os arquivos php ATS, aguarda a inicialização de todos os módulos, adiciona um retorno de chamada entre si e, quando tudo estiver pronto, armazena-os em cache. Ao perguntar por FastAGI um fluxo é criado, uma cópia do cache de todas as classes e dados é reproduzida nele e a solicitação é passada para a função php.

Com esta solução, o tempo desde o envio de uma chamada ao nosso serviço até ao primeiro comando asterisco diminuiu de 1,5s para 0,05s e esse tempo depende um pouco do tamanho do projeto.

A história de um projeto ou como passei 7 anos criando um PABX baseado em Asterisk e Php

Como resultado, o tempo para o desenvolvimento do dialplan foi reduzido significativamente, e posso apreciar isso, pois tive que reescrever todo o dialplan de todos os módulos em PHP. Em primeiro lugar, os métodos já deveriam estar escritos em php para obter um objeto do banco de dados, eram necessários para exibição na interface web e, em segundo lugar, e isso é o principal, finalmente é possível trabalhar convenientemente com strings com números e arrays com banco de dados e muitas extensões PHP.

Para processar o dialplan na classe do módulo você precisa implementar a função dialplanDynamicCall e argumento pbxCallRequest conterá um objeto para interagir asterisco.

A história de um projeto ou como passei 7 anos criando um PABX baseado em Asterisk e Php

Além disso, tornou-se possível depurar o dialplan (o php tem xdebug e funciona para o nosso serviço), você pode avançar passo a passo visualizando os valores das variáveis.

Dados de chamada

Quaisquer análises e relatórios exigem dados coletados corretamente, e esse bloco PBX também passou por muitas tentativas e erros da primeira à terceira versão. Freqüentemente, os dados da chamada são um sinal. Uma ligação = uma gravação: quem ligou, quem atendeu, quanto tempo conversaram. Nas opções mais interessantes, há uma placa adicional indicando qual funcionário do PABX foi chamado durante a ligação. Mas tudo isso cobre apenas parte das necessidades.

Os requisitos iniciais eram:

  • salve não só quem ligou para o PABX, mas também quem atendeu, pois existem interceptações e isso deverá ser levado em consideração na análise das chamadas,
  • tempo antes de se conectar com um funcionário. Em FreePBX e alguns outros PABXs, a chamada é considerada atendida assim que o PABX atende o telefone. Mas para o menu de voz você já precisa pegar o telefone, assim todas as ligações são atendidas e o tempo de espera por atendimento passa a ser de 0 a 1 segundo. Portanto, decidiu-se economizar não apenas o tempo antes de uma resposta, mas o tempo antes de conectar-se aos módulos principais (o próprio módulo define este sinalizador. Atualmente é “Funcionário”, “Linha externa”),
  • para um plano de discagem mais complexo, quando uma chamada viaja entre grupos diferentes, era necessário poder examinar cada elemento separadamente.

A melhor opção acabou sendo quando os módulos PABX enviam informações sobre si mesmos nas chamadas e, por fim, salvam as informações em forma de árvore.

É assim:

Primeiro, informações gerais sobre a chamada (como todo mundo - nada de especial).

A história de um projeto ou como passei 7 anos criando um PABX baseado em Asterisk e Php

  1. Recebi uma chamada em uma linha externa"Para a massa"às 05:55:52 do número 89295671458 para o número 89999999999, no final foi atendido por um funcionário"Secretário2» com o número 104. O cliente esperou 60 segundos e falou 36 segundos.
  2. Funcionário "Secretário2"faz uma chamada para o 112 e um funcionário atende"Gerente1» após 8 segundos. Eles conversam por 14 segundos.
  3. O Cliente é transferido para o Funcionário "gerente1"onde eles continuam conversando por mais 13 segundos

Mas esta é a ponta do iceberg: para cada registro você pode obter um histórico detalhado de chamadas através do PABX.

A história de um projeto ou como passei 7 anos criando um PABX baseado em Asterisk e Php

Todas as informações são apresentadas como um aninhamento de chamadas:

  1. Recebi uma chamada em uma linha externa"Para a massa» às 05:55:52 do número 89295671458 para o número 89999999999.
  2. Às 05:55:53 a linha externa envia uma chamada para o circuito de entrada "teste»
  3. Ao processar uma chamada de acordo com o esquema, o módulo “chamada do gerente", em que a chamada dura 16 segundos. Este é um módulo desenvolvido para o cliente.
  4. Módulo "chamada do gerente" envia uma ligação para o funcionário responsável pelo número (cliente) "Gerente1”E espera 5 segundos por uma resposta. O gerente não respondeu.
  5. Módulo "chamada do gerente"envia uma chamada para o grupo"Gerentes CORP" São outros gestores da mesma direção (sentados na mesma sala) e aguardando 11 segundos por uma resposta.
  6. Grupo "Gerentes CORP"chama funcionários"Gerente1, Gerente2, Gerente3"simultaneamente por 11 segundos. Nenhuma resposta.
  7. A ligação do gerente termina. E o circuito envia uma chamada para o módulo”Selecionando uma rota de 1c" Também um módulo escrito para o cliente. Aqui a chamada foi processada por 0 segundos.
  8. O circuito envia uma chamada para o menu de voz "Básico com discagem adicional" O cliente esperou 31 segundos, não houve discagem adicional.
  9. O esquema envia uma chamada para o Grupo "Secretários", onde o cliente esperou 12 segundos.
  10. Num grupo, 2 funcionários são chamados ao mesmo tempo”Secretário1"E"Secretário2" e após 12 segundos o funcionário responde "Secretário2" A resposta à chamada é duplicada nas chamadas dos pais. Acontece que no grupo ele respondeu “Secretário2", ao chamar o circuito atendeu"Secretário2" e atendeu a chamada na linha externa com "Secretário2".

É o salvamento das informações sobre cada operação e seu aninhamento que facilitará a elaboração de relatórios. Um relatório no menu de voz vai te ajudar a saber o quanto isso ajuda ou atrapalha. Construir um relatório de chamadas perdidas pelos funcionários, levando em consideração que a chamada foi interceptada e, portanto, não é considerada perdida, e levando em consideração que se trata de uma chamada em grupo, e outra pessoa atendeu antes, o que significa que a chamada também não foi perdida.

Esse armazenamento de informações permitirá que você considere cada grupo separadamente e determine a eficácia com que ele funciona, e construa um gráfico de grupos atendidos e perdidos por hora. Você também pode verificar a precisão da conexão com o gestor responsável analisando as transferências após a conexão com o gestor.

Você também pode realizar estudos bastante atípicos, por exemplo, com que frequência números que não estão no banco de dados discam para o ramal correto ou qual porcentagem de chamadas de saída são encaminhadas para um telefone celular.

O resultado?

Não é necessário um especialista para manter o PABX, o administrador mais comum pode fazer isso - testado na prática.

Para modificações não são necessários especialistas com qualificação séria, basta conhecimento de PHP, pois Já foram escritos módulos para o protocolo SIP, e para a fila, e para ligar para um funcionário, entre outros. Existe uma classe wrapper para asterisco. Para desenvolver um módulo, um programador pode (e no bom sentido deve) chamar módulos prontos. E conhecimento asterisco são completamente desnecessários se o cliente solicitar para adicionar uma página com algum novo relatório. Mas a prática mostra que, embora os programadores terceirizados possam lidar com isso, eles se sentem inseguros sem documentação e cobertura normal de comentários, portanto ainda há espaço para melhorias.

Os módulos podem:

  • criar novos recursos de processamento de chamadas,
  • adicione novos blocos à interface web,
  • herdar de qualquer um dos módulos existentes, redefinir funções e substituí-las, ou simplesmente ser uma cópia ligeiramente modificada,
  • adicione suas configurações ao modelo de configurações de outros módulos e muito mais.

Configurações de PABX via API. Conforme descrito acima, todas as configurações são armazenadas no banco de dados e lidas no momento da chamada, portanto você pode alterar todas as configurações do PABX através da API. Ao chamar a API, a configuração não é recriada e os módulos não são reiniciados, portanto, não importa quantas configurações e funcionários você possui. As solicitações de API são executadas rapidamente e não bloqueiam umas às outras.

O PABX armazena todas as principais operações com chamadas com durações (espera/conversação), aninhamento e em termos do PABX (funcionário, grupo, linha externa, não canal, número). Isso permite que você crie vários relatórios para clientes específicos e a maior parte do trabalho consiste em criar uma interface amigável.

O tempo dirá o que acontecerá a seguir. Ainda há muitas nuances que precisam ser refeitas, ainda há muitos planos, mas já se passou um ano desde a criação da 3ª versão e já podemos dizer que a ideia está funcionando. A principal desvantagem da versão 3 são os recursos de hardware, mas geralmente é por isso que você tem que pagar para facilitar o desenvolvimento.

Fonte: habr.com

Adicionar um comentário