Compreendendo o FreePBX e integrando-o com o Bitrix24 e mais

Bitrix24 é uma grande combinação que combina CRM, fluxo de trabalho, contabilidade e muitas outras coisas que os gerentes realmente gostam e a equipe de TI realmente não gosta. O portal é utilizado por muitas pequenas e médias empresas, incluindo pequenas clínicas, fabricantes e até salões de beleza. A principal função que os gerentes “amam” é a integração de telefonia e CRM, quando qualquer chamada é imediatamente registrada no CRM, são criados cartões de clientes, ao entrar, são exibidas informações sobre o cliente e você pode ver imediatamente quem ele é, o que ele pode vender e quanto deve. Mas a telefonia do Bitrix24 e sua integração com o CRM custam dinheiro, às vezes muito. No artigo vou contar a experiência de integração com ferramentas abertas e o popular IP PBX FreePBX, e também considerar a lógica do trabalho de várias partes

Trabalho como outsourcer em uma empresa que vende e configura, integra telefonia IP. Quando me perguntaram se poderíamos oferecer algo a esta e esta empresa para integrar o Bitrix24 com PBXs que os clientes possuem, bem como com PBXs virtuais em várias empresas de VDS, fui ao Google. E é claro que ele me deu um link para artigo em habr, onde há uma descrição e github, e tudo parece funcionar. Mas ao tentar usar essa solução, descobriu-se que o Bitrix24 não é mais o mesmo de antes e muito precisa ser refeito. Além disso, o FreePBX não é um asterisco simples para você, aqui você precisa pensar em como combinar a facilidade de uso e um plano de discagem hardcore nos arquivos de configuração.

Estudamos a lógica do trabalho

Então, para começar, como tudo deve funcionar. Quando uma chamada é recebida de fora no PBX (evento SIP INVITE do provedor), começa o processamento do plano de discagem (plano de discagem, plano de discagem) - as regras do que e em que ordem fazer a chamada. Desde o primeiro pacote, você pode obter muitas informações, que podem ser usadas nas regras. Uma excelente ferramenta para estudar os componentes internos do SIP é o analisador sngrep (link) que é simplesmente instalado em distribuições populares via apt install/yum install e similares, mas também pode ser construído a partir do código-fonte. Vamos ver o registro de chamadas no sngrep

Compreendendo o FreePBX e integrando-o com o Bitrix24 e mais

De forma simplificada, o dialplan trata apenas do primeiro pacote, às vezes também durante a conversa, chamadas são transferidas, pressionamentos de botões (DTMF), várias coisas interessantes como FollowMe, RingGroup, IVR e outros.

O que há dentro do Pacote de Convites

Compreendendo o FreePBX e integrando-o com o Bitrix24 e mais

Na verdade, os planos de discagem mais simples funcionam com os dois primeiros campos, e toda a lógica gira em torno de DID e CallerID. DID - para onde estamos ligando, CallerID - quem está ligando.

Afinal, temos uma empresa e não um telefone - o que significa que o PBX provavelmente possui grupos de chamadas (toques simultâneos / consecutivos de vários dispositivos) em números de cidades (Ring Group), IVR (Alô, você ligou ... Pressione um para...), Secretárias Eletrônicas ( Frases), Condições de Tempo, Encaminhamento para outros números ou para um celular (FollowMe, Forward). Isso significa que é muito difícil determinar de forma inequívoca quem realmente receberá uma chamada e com quem conversará quando uma chamada chegar. Aqui está um exemplo do início de uma chamada típica no PBX de nossos clientes

Compreendendo o FreePBX e integrando-o com o Bitrix24 e mais

Após a chamada entrar com sucesso no PBX, ela percorre o dialplan em diferentes "contextos". O contexto do ponto de vista do Asterisk é um conjunto numerado de comandos, cada um dos quais contém um filtro pelo número discado (é chamado de exten, para uma chamada externa no estágio inicial exten=DID). Os comandos na linha dialplan podem ser qualquer coisa - funções internas (por exemplo, chamar um assinante interno - Dial(), Abaixe o telefone - Hangup()), operadores condicionais (IF, ELSE, ExecIF e afins), transições para outras regras deste contexto (Goto, GotoIF), transição para outros contextos na forma de uma chamada de função (Gosub, Macro). Uma diretiva separada include имя_контекста, que adiciona comandos de outro contexto ao final do contexto atual. Os comandos incluídos via include são sempre executados depois comandos do contexto atual.

Toda a lógica do FreePBX é construída sobre a inclusão de diferentes contextos entre si por meio de inclusão e chamada por meio de manipuladores Gosub, Macro e Handler. Considere o contexto das chamadas recebidas do FreePBX

Compreendendo o FreePBX e integrando-o com o Bitrix24 e mais

A chamada percorre todos os contextos de cima para baixo sucessivamente, em cada contexto pode haver chamadas para outros contextos como macros (Macro), funções (Gosub) ou apenas transições (Goto), então a árvore real do que é chamado só pode ser rastreado nos logs.

Um diagrama de configuração típico para um PBX típico é mostrado abaixo. Ao ligar, o DID é pesquisado nas rotas de entrada, as condições temporárias são verificadas, se tudo estiver em ordem, o menu de voz é iniciado. A partir dele, pressionando o botão 1 ou timeout, saia para o grupo de operadoras de discagem. Após o término da chamada, a macro hangupcall é chamada, após a qual nada pode ser feito no dialplan, exceto manipuladores especiais (hangup handler).

Compreendendo o FreePBX e integrando-o com o Bitrix24 e mais

Onde neste algoritmo de chamada devemos fornecer informações sobre o início da chamada para o CRM, onde iniciar a gravação, onde terminar a gravação e enviar junto com as informações sobre a chamada para o CRM?

Integração com sistemas externos

O que é integração de PBX e CRM? São configurações e programas que convertem dados e eventos entre essas duas plataformas e os enviam uma para a outra. A forma mais comum de comunicação de sistemas independentes é por meio de APIs, e a forma mais popular de acessar APIs é HTTP REST. Mas não para o asterisco.

Dentro do Asterisk está:

  • AGI - chamada síncrona para programas/componentes externos, utilizada principalmente no dialplan, existem bibliotecas como phpagi, PAGI

  • AMI - um soquete TCP de texto que funciona com base no princípio de assinar eventos e inserir comandos de texto, assemelha-se ao SMTP por dentro, pode rastrear eventos e gerenciar chamadas, existe uma biblioteca PAMI - o mais popular para criar uma conexão com o Asterisk

Exemplo de saída de AMI

Evento: novo canal
Privilégio: ligar, tudo
Canal: PJSIP/VMS_pjsip-0000078b
Estado do Canal: 4
ChannelStateDesc: Anel
Número de ID do chamador: 111222
Nome do ID do chamador: 111222
ConnectedLineNum:
nome da linha conectada:
Idioma: en
Código de conta:
Contexto: from-pstn
Extensão: s
Prioridade: 1
Identificação única: 1599589046.5244
Linkedid: 1599589046.5244

  • ARI é uma mistura de ambos, tudo via REST, WebSocket, no formato JSON - mas com novas bibliotecas e wrappers, não muito bons, encontrados de improviso (phparia, phpari) que se tornou em seu desenvolvimento há cerca de 3 anos.

Exemplo de saída ARI quando uma chamada é iniciada

{ "variable":"CallMeCallerIDName", "value":"111222", "type":"ChannelVarset", "timestamp":"2020-09-09T09:38:36.269+0000", "canal":{ "id »:»1599644315.5334″, «nome»:»PJSIP/VMSpjsip-000007b6″, "estado":"Toque", "chamador":{ "nome":"111222″, "número":"111222″ }, "conectado":{ "nome":"", "número" :"" }, "accountcode":"", "dialplan":{ "context":"from-pstn", "exten":"s", "priority":2, "appname":"Estase", "aplicativodata":"hello-world" }, "creationtime":"2020-09-09T09:38:35.926+0000", "language":"en" }, "asteriskid":"48:5b:aa:aa:aa:aa", "aplicativo":"olá-mundo" }

Conveniência ou inconveniência, a possibilidade ou impossibilidade de trabalhar com uma determinada API são determinadas pelas tarefas que precisam ser resolvidas. As tarefas para integração com o CRM são as seguintes:

  • Rastreie o início da chamada, para onde foi transferida, retire CallerID, DID, horários de início e término, talvez dados do diretório (para procurar uma conexão entre o telefone e o usuário do CRM)

  • Inicie e finalize a gravação da chamada, salve no formato desejado, informe ao final da gravação onde se encontra o arquivo

  • Iniciar uma chamada em um evento externo (do programa), ligar para um número interno, um número externo e conectá-los

  • Opcional: integração com CRM, grupos de discagem e FollowME para transferência automática de chamadas na ausência de um local (de acordo com o CRM)

Todas essas tarefas podem ser resolvidas por AMI ou ARI, mas o ARI fornece muito menos informações, não há muitos eventos, muitas variáveis ​​que o AMI ainda possui (por exemplo, chamadas de macro, variáveis ​​de configuração dentro de macros, incluindo gravação de chamadas) não são rastreadas. Portanto, para um rastreamento correto e preciso, vamos escolher AMI por enquanto (mas não completamente). Além disso (bem, onde sem isso somos preguiçosos) - na obra original (artigo em habr) usar PAMI. *Então você precisa tentar reescrever para ARI, mas não o fato de que funcionará.

Reinventando a integração

Para que nosso FreePBX possa informar a AMI de forma simples sobre o início da chamada, horário de término, números, nomes dos arquivos gravados, é mais fácil calcular a duração da chamada usando o mesmo truque dos autores originais - insira suas variáveis ​​e analise a saída para sua presença. A PAMI sugere fazer isso simplesmente por meio de uma função de filtro.

Aqui está um exemplo de configuração de sua própria variável para o horário de início da chamada (s é um número especial no plano de discagem que é executado ANTES de iniciar a pesquisa DID)

[ext-did-custom]

exten => s,1,Set(CallStart=${STRFTIME(epoch,,%s)})

Um exemplo de evento AMI para esta linha

Evento: novo canal

Privilégio: ligar, tudo

Canal: PJSIP/VMS_pjsip-0000078b

Estado do Canal: 4

ChannelStateDesc: Anel

Número de ID do chamador: 111222

Nome do ID do chamador: 111222

ConnectedLineNum:

nome da linha conectada:

Idioma: en

Código de conta:

Contexto: from-pstn

Extensão: s

Prioridade: 1

Identificação única: 1599589046.5244

Linkedid: 1599589046.5244

Aplicação: Definir AppData:

CallStart = 1599571046

Porque o FreePBX substitui os arquivos extension.conf e extensionion_adicional.conf, usaremos o arquivo extensão_personalizadas.conf

Código completo de extension_custom.conf

[globals]	
;; Проверьте пути и права на папки - юзер asterisk должен иметь права на запись
;; Сюда будет писаться разговоры
WAV=/var/www/html/callme/records/wav 
MP3=/var/www/html/callme/records/mp3

;; По этим путям будет воспроизводится и скачиваться запись
URLRECORDS=https://www.host.ru/callmeplus/records/mp3

;; Адрес для калбека при исходящем вызове
URLPHP=https://www.host.ru/callmeplus

;; Да пишем разговоры
RECORDING=1

;; Это макрос для записи разговоров в нашу папку. 
;; Можно использовать и системную запись, но пока пусть будет эта - 
;; она работает
[recording]
exten => ~~s~~,1,Set(LOCAL(calling)=${ARG1})
exten => ~~s~~,2,Set(LOCAL(called)=${ARG2})
exten => ~~s~~,3,GotoIf($["${RECORDING}" = "1"]?4:14)
exten => ~~s~~,4,Set(fname=${UNIQUEID}-${STRFTIME(${EPOCH},,%Y-%m-%d-%H_%M)}-${calling}-${called})
exten => ~~s~~,5,Set(datedir=${STRFTIME(${EPOCH},,%Y/%m/%d)})
exten => ~~s~~,6,System(mkdir -p ${MP3}/${datedir})
exten => ~~s~~,7,System(mkdir -p ${WAV}/${datedir})
exten => ~~s~~,8,Set(monopt=nice -n 19 /usr/bin/lame -b 32  --silent "${WAV}/${datedir}/${fname}.wav"  "${MP3}/${datedir}/${fname}.mp3" && rm -f "${WAV}/${fname}.wav" && chmod o+r "${MP3}/${datedir}/${fname}.mp3")
exten => ~~s~~,9,Set(FullFname=${URLRECORDS}/${datedir}/${fname}.mp3)
exten => ~~s~~,10,Set(CDR(filename)=${fname}.mp3)
exten => ~~s~~,11,Set(CDR(recordingfile)=${fname}.wav)
exten => ~~s~~,12,Set(CDR(realdst)=${called})
exten => ~~s~~,13,MixMonitor(${WAV}/${datedir}/${fname}.wav,b,${monopt})
exten => ~~s~~,14,NoOp(Finish if_recording_1)
exten => ~~s~~,15,Return()


;; Это основной контекст для начала разговора
[ext-did-custom]

;; Это хулиганство, делать это так и здесь, но работает - добавляем к номеру '8'
exten =>  s,1,Set(CALLERID(num)=8${CALLERID(num)})

;; Тут всякие переменные для скрипта
exten =>  s,n,Gosub(recording,~~s~~,1(${CALLERID(number)},${EXTEN}))
exten =>  s,n,ExecIF(${CallMeCallerIDName}?Set(CALLERID(name)=${CallMeCallerIDName}):NoOp())
exten =>  s,n,Set(CallStart=${STRFTIME(epoch,,%s)})
exten =>  s,n,Set(CallMeDISPOSITION=${CDR(disposition)})

;; Самое главное! Обработчик окончания разговора. 
;; Обычные пути обработки конца через (exten=>h,1,чтототут) в FreePBX не работают - Macro(hangupcall,) все портит. 
;; Поэтому вешаем Hangup_Handler на окончание звонка
exten => s,n,Set(CHANNEL(hangup_handler_push)=sub-call-from-cid-ended,s,1(${CALLERID(num)},${EXTEN}))

;; Обработчик окончания входящего вызова
[sub-call-from-cid-ended]

;; Сообщаем о значениях при конце звонка
exten => s,1,Set(CDR_PROP(disable)=true)
exten => s,n,Set(CallStop=${STRFTIME(epoch,,%s)})
exten => s,n,Set(CallMeDURATION=${MATH(${CallStop}-${CallStart},int)})

;; Статус вызова - Ответ, не ответ...
exten => s,n,Set(CallMeDISPOSITION=${CDR(disposition)})
exten => s,n,Return


;; Обработчик исходящих вызовов - все аналогичено
[outbound-allroutes-custom]

;; Запись
exten => _.,1,Gosub(recording,~~s~~,1(${CALLERID(number)},${EXTEN}))
;; Переменные
exten => _.,n,Set(__CallIntNum=${CALLERID(num)})
exten => _.,n,Set(CallExtNum=${EXTEN})
exten => _.,n,Set(CallStart=${STRFTIME(epoch,,%s)})
exten => _.,n,Set(CallmeCALLID=${SIPCALLID})

;; Вешаем Hangup_Handler на окончание звонка
exten => _.,n,Set(CHANNEL(hangup_handler_push)=sub-call-internal-ended,s,1(${CALLERID(num)},${EXTEN}))

;; Обработчик окончания исходящего вызова
[sub-call-internal-ended]

;; переменные
exten => s,1,Set(CDR_PROP(disable)=true)
exten => s,n,Set(CallStop=${STRFTIME(epoch,,%s)})
exten => s,n,Set(CallMeDURATION=${MATH(${CallStop}-${CallStart},int)})
exten => s,n,Set(CallMeDISPOSITION=${CDR(disposition)})

;; Вызов скрипта, который сообщит о звонке в CRM - это исходящий, 
;; так что по факту окончания
exten => s,n,System(curl -s ${URLPHP}/CallMeOut.php --data action=sendcall2b24 --data ExtNum=${CallExtNum} --data call_id=${SIPCALLID} --data-urlencode FullFname='${FullFname}' --data CallIntNum=${CallIntNum} --data CallDuration=${CallMeDURATION} --data-urlencode CallDisposition='${CallMeDISPOSITION}')
exten => s,n,Return

Recurso e diferença do dialplan original dos autores do artigo original -

  • Plano de discagem no formato .conf, como o FreePBX deseja (sim, pode ser .ael, mas não em todas as versões e nem sempre é conveniente)

  • Em vez de processar o final por meio de exten=>h, o processamento foi introduzido por meio de hangup_handler, porque o plano de discagem do FreePBX funcionava apenas com ele

  • String de chamada de script corrigida, aspas adicionadas e número de chamada externa ExtNum

  • O processamento é movido para _contextos personalizados e permite que você não toque ou edite as configurações do FreePBX - recebidas via [ext-customizado], saindo através de [saída-allroutes-customizada]

  • Sem vinculação a números - o arquivo é universal e só precisa ser configurado para o caminho e link para o servidor

Para começar, você também precisa executar scripts no AMI por login e senha - para isso, o FreePBX também possui um arquivo _custom

arquivo manager_custom.conf

;;  это логин
[callmeplus]
;; это пароль
secret = trampampamturlala
deny = 0.0.0.0/0.0.0.0

;; я работаю с локальной машиной - но если надо, можно и другие прописать
permit = 127.0.0.1/255.255.255.255
read = system,call,log,verbose,agent,user,config,dtmf,reporting,cdr,dialplan
write = system,call,agent,log,verbose,user,config,command,reporting,originate

Ambos os arquivos devem ser colocados em /etc/asterisk e reler as configurações (ou reiniciar o asterisco)

# astrisk -rv
  Connected to Asterisk 16.6.2 currently running on freepbx (pid = 31629)
#freepbx*CLI> dialplan reload
     Dialplan reloaded.
#freepbx*CLI> exit

Agora vamos passar para o PHP

Inicializando scripts e criando um serviço

Como o esquema para trabalhar com o Bitrix 24, um serviço para AMI, não é totalmente simples e transparente, deve ser discutido separadamente. O Asterisk, quando o AMI é ativado, simplesmente abre a porta e pronto. Quando um cliente entra, ele solicita autorização e, em seguida, o cliente se inscreve nos eventos necessários. Os eventos vêm em texto simples, que o PAMI converte em objetos estruturados e fornece a capacidade de definir a função de filtragem apenas para eventos de interesse, campos, números, etc.

Assim que a chamada chega, o evento NewExten é acionado a partir do contexto pai [from-pstn], então todos os eventos vão na ordem das linhas nos contextos. Quando as informações são recebidas das variáveis ​​CallMeCallerIDName e CallStart especificadas no _custom dialplan, o

  1. A função de solicitar o UserID correspondente ao número do ramal de onde veio a chamada. E se for um grupo dial-up? A questão é política, você precisa criar uma chamada para todos de uma vez (quando todos ligam ao mesmo tempo) ou criar como eles chamam ao ligar por sua vez? A maioria dos clientes tem a estratégia Primeiro disponível, então não há problema com isso, apenas um liga. Mas a questão precisa ser resolvida.

  2. A função de registro de chamada no Bitrix24, que retorna o CallID, que é necessário para relatar os parâmetros da chamada e um link para a gravação. Requer número de ramal ou UserID

Compreendendo o FreePBX e integrando-o com o Bitrix24 e mais

Após o término da chamada, é acionada a função de download do registro, que informa simultaneamente o status da conclusão da chamada (Ocupado, Sem resposta, Sucesso) e também baixa um link para o arquivo mp3 com o registro (se houver).

Como o módulo CallMeIn.php precisa ser executado continuamente, um arquivo de inicialização do SystemD foi criado para ele ligue para mim.serviço, que deve ser colocado em /etc/systemd/system/callme.service

[Unit]
Description=CallMe

[Service]
WorkingDirectory=/var/www/html/callmeplus
ExecStart=/usr/bin/php /var/www/html/callmeplus/CallMeIn.php 2>&1 >>/var/log/callmeplus.log
ExecStop=/bin/kill -WINCH ${MAINPID}
KillSignal=SIGKILL

Restart=on-failure
RestartSec=10s

#тут надо смотреть,какие права на папки
#User=www-data  #Ubuntu - debian
#User=nginx #Centos

[Install]
WantedBy=multi-user.target

a inicialização e o lançamento do script ocorrem por meio de systemctl ou service

# systemctl enable callme
# systemctl start callme

O serviço será reiniciado conforme necessário (em caso de travamento). O serviço de rastreamento de caixa de entrada não requer a instalação de um servidor web, apenas php é necessário (que definitivamente está no servidor FeePBX). Mas na ausência de acesso aos registos de chamadas através do servidor Web (também com https), não será possível ouvir os registos de chamadas.

Agora vamos falar sobre chamadas de saída. O script CallMeOut.php tem duas funções:

  • Iniciação de uma chamada quando uma solicitação é recebida para um script php (inclusive usando o botão "Chamar" no próprio Bitrix). Não funciona sem um servidor web, a solicitação é recebida via HTTP POST, a solicitação contém um token

  • Mensagem sobre a chamada, seus parâmetros e registros no Bitrix. Disparado pelo Asterisk no plano de discagem [sub-call-internal-ended] quando uma chamada é encerrada

Compreendendo o FreePBX e integrando-o com o Bitrix24 e mais

O servidor web é necessário apenas para duas coisas - baixar arquivos de registro Bitrix (via HTTPS) e chamar o script CallMeOut.php. Você pode usar o servidor FreePBX integrado, cujos arquivos são /var/www/html, pode instalar outro servidor ou especificar um caminho diferente.

servidor web

Vamos deixar a configuração do servidor web para estudo independente (tyts, tyts, tyts). Se você não possui um domínio, pode tentar FreeDomain( https://www.freenom.com/ru/index.html), que lhe dará um nome gratuito para o seu IP branco (não se esqueça de encaminhar as portas 80, 443 pelo roteador se o endereço externo estiver apenas nele). Se você acabou de criar um domínio DNS, terá que esperar (de 15 minutos a 48 horas) até que todos os servidores sejam carregados. De acordo com a experiência de trabalhar com provedores domésticos - de 1 hora a um dia.

Automação de instalação

Um instalador foi desenvolvido no github para tornar a instalação ainda mais fácil. Mas foi tranquilo no papel - enquanto estamos instalando tudo manualmente, pois depois de mexer em tudo isso ficou claro o que é amigo de quem, quem vai para onde e como depurar. Ainda não há instalador

Estivador

Se você quiser experimentar rapidamente a solução - existe uma opção com o Docker - crie rapidamente um contêiner, dê portas para fora, deslize os arquivos de configurações e tente (esta é a opção com o contêiner LetsEncrypt, se você já tiver um certificado , você só precisa redirecionar o proxy reverso para o servidor da Web FreePBX (demos outra porta é 88), LetsEncrypt no docker com base em este artigo

Você precisa executar o arquivo na pasta do projeto baixado (após git clone), mas primeiro entre nas configurações do asterisco (pasta do asterisco) e escreva os caminhos para os registros e a URL do seu site lá

version: '3.3'
services:
  nginx:
    image: nginx:1.15-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/ssl_docker.conf:/etc/nginx/conf.d/ssl_docker.conf
  certbot:
    image: certbot/certbot
  freepbx:
    image: flaviostutz/freepbx
    ports:
      - 88:80 # для настройки
      - 5060:5060/udp
      - 5160:5160/udp
      - 127.0.0.1:5038:5038 # для CallMeOut.php
#      - 3306:3306
      - 18000-18100:18000-18100/udp
    restart: always
    environment:
      - ADMIN_PASSWORD=admin123
    volumes:
      - backup:/backup
      - recordings:/var/spool/asterisk/monitor
      - ./callme:/var/www/html/callme
      - ./systemd/callme.service:/etc/systemd/system/callme.conf
      - ./asterisk/manager_custom.conf:/etc/asterisk/manager_custom.conf
      - ./asterisk/extensions_custom.conf:/etc/asterisk/extensions_custom.conf
#      - ./conf/startup.sh:/startup.sh

volumes:
  backup:
  recordings:

Este arquivo docker-compose.yaml é executado via

docker-compose up -d

Se o nginx não iniciar, algo está errado com a configuração na pasta nginx/ssl_docker.conf

Outras integrações

E por que não colocar algum CRM em scripts ao mesmo tempo, pensamos. Estudamos várias outras APIs de CRM, especialmente o PBX integrado gratuito - ShugarCRM e Vtiger, e sim! sim, o princípio é o mesmo. Mas esta é outra história, que posteriormente carregaremos separadamente no github.

referências

Disclaimer: Qualquer semelhança com a realidade é fictícia e não fui eu.

Fonte: habr.com

Adicionar um comentário