Bitrix24 ist ein riesiger Harvester, der CRM, Dokumentenfluss, Buchhaltung und viele andere Dinge kombiniert, die Manager wirklich mögen und IT-Mitarbeiter nicht wirklich mögen. Das Portal wird von vielen kleinen und mittleren Unternehmen genutzt, darunter kleine Kliniken, Fertigungsarbeiter und sogar Schönheitssalons. Die Hauptfunktion, die Manager "lieben", ist die Integration von Telefonie und CRM. Wenn ein Anruf sofort in CRM aufgezeichnet wird, Kundenkarten erstellt werden, Informationen über den Kunden beim Eingang angezeigt werden und Sie sofort sehen können, wer er ist, was er verkaufen kann und wie viel er schuldet. Die Telefonie von Bitrix24 und die Integration in CRM kostet jedoch manchmal viel Geld. In dem Artikel werde ich Ihnen die Erfahrung der Integration mit offenen Tools und der beliebten IP PBX FreePBX erläutern und auch die Logik der Arbeit verschiedener Teile berücksichtigen
Ich arbeite als Outsourcing-Unternehmen in einem Unternehmen, das IP-Telefonie verkauft und konfiguriert und integriert. Als ich gefragt wurde, ob wir diesem und diesem Unternehmen etwas anbieten könnten, um Bitrix24 in PBX-Anlagen von Kunden sowie in virtuelle PBX-Anlagen in verschiedenen VDS-Unternehmen zu integrieren, ging ich zu Google. Und er gab mir natürlich einen Link zu einem Artikel in Habr , wo es eine Beschreibung und einen Github gibt, und alles scheint zu funktionieren. Bei dem Versuch, diese Lösung zu verwenden, stellte sich jedoch heraus, dass Bitrix24 nicht mehr das ist, was es früher war, und dass noch viel überarbeitet werden muss. Außerdem ist FreePBX für Sie kein nacktes Sternchen. Hier müssen Sie darüber nachdenken, wie Sie Benutzerfreundlichkeit und einen Hardcore-Wählplan in den Konfigurationsdateien kombinieren können.
Wir studieren die Logik der Arbeit
Also zuerst, wie soll es funktionieren. Wenn ein Anruf von außerhalb an die TK-Anlage kommt (SIP INVITE-Ereignis vom Anbieter), beginnt die Verarbeitung des Wählplans (Wählplan, Wählplan) - die Regeln, was mit dem Anruf zu tun ist und in welcher Reihenfolge. Aus dem ersten Paket können viele Informationen gewonnen werden, die dann in den Regeln verwendet werden können. Ein hervorragendes Werkzeug zum Untersuchen der Innenseiten von SIP ist der sngrep- Analysator ( Link ), der einfach in gängigen Distributionen über apt install / yum install und dergleichen installiert wird. Sie können ihn jedoch auch aus dem Quellcode erstellen. Sehen wir uns das Anrufprotokoll in sngrep an

In vereinfachter Form behandelt der Wählplan nur das erste Paket, manchmal werden während des Gesprächs Anrufe weitergeleitet, Tasten gedrückt (DTMF), verschiedene interessante Dinge wie FollowMe, RingGroup, IVR und andere.
Was ist im Invite-Paket enthalten?

DID CallerID. DID - , CallerID - .
- (/ ) (Ring Group), IVR (, ... ...), (Phrases), (Time Conditions), (FollowMe, Forward). , .

"". Asterisk - , ( exten, exten=DID). - ( - Dial(), - Hangup()), (IF, ELSE, ExecIF ), (Goto, GotoIF), (Gosub, Macro). include _, . , include .
FreePBX include Gosub, Macro Handler. FreePBX

, (Macro), (Gosub) (Goto), , .
. DID, , - . 1 . hangupcall, , (hangup handler).

CRM, , CRM?
CRM? , . API, API HTTP REST. asterisk.
Asterisk :
AMI
Event: Newchannel Privilege: call,all Channel: PJSIP/VMS_pjsip-0000078b ChannelState: 4 ChannelStateDesc: Ring CallerIDNum: 111222 CallerIDName: 111222 ConnectedLineNum: ConnectedLineName: Language: en AccountCode: Context: from-pstn Exten: s Priority: 1 Uniqueid: 1599589046.5244 Linkedid: 1599589046.5244
ARI
{ "variable":"CallMeCallerIDName", "value":"111222", "type":"ChannelVarset", "timestamp":"2020-09-09T09:38:36.269+0000", "channel":{ "id":"1599644315.5334", "name":"PJSIP/VMSpjsip-000007b6", "state":"Ring", "caller":{ "name":"111222", "number":"111222" }, "connected":{ "name":"", "number":"" }, "accountcode":"", "dialplan":{ "context":"from-pstn", "exten":"s", "priority":2, "appname":"Stasis", "appdata":"hello-world" }, "creationtime":"2020-09-09T09:38:35.926+0000", "language":"ru" }, "asteriskid":"48:5b:aa:aa:aa:aa", "application":"hello-world" }
, API , . CRM :
, , CallerID, DID, , ( CRM)
, ,
( ), ,
: CRM, FollowME ( CRM)
AMI ARI, ARI , , , AMI ( , , ). , - AMI ( ). ( , ) - ( ) PAMI. * ARI, .
, FreePBX AMI , , , , , - . PAMI -.
(s - , DID)
[ext-did-custom]
exten => s,1,Set(CallStart=${STRFTIME(epoch,,%s)})AMI
Event: Newchannel
Privilege: call,all
Channel: PJSIP/VMS_pjsip-0000078b
ChannelState: 4
ChannelStateDesc: Ring
CallerIDNum: 111222
CallerIDName: 111222
ConnectedLineNum:
ConnectedLineName:
Language: en
AccountCode:
Context: from-pstn
Exten: s
Priority: 1
Uniqueid: 1599589046.5244
Linkedid: 1599589046.5244
Application: Set AppData:
CallStart=1599571046
FreePBX extention.conf extention_additional.conf, extention_custom.conf
extention_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-
.conf, FreePBX ( .ael, )
exten=>h hangup_handler, FreePBX
, ExtNum
_custom FreePBX - [ext-did-custom], [outbound-allroutes-custom]
-
AMI - FreePBX _custom
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/etc/asterisk, ( )
# astrisk -rv
Connected to Asterisk 16.6.2 currently running on freepbx (pid = 31629)
#freepbx*CLI> dialplan reload
Dialplan reloaded.
#freepbx*CLI> exitPHP
24, AMI , . AMI . , . , PAMI , , ..
, NewExten [from-pstn], . _custom CallMeCallerIDName CallStart
UserID, , . ? , ( ) ? Fisrt Available, , .
24, CallID, . UserID

, (, , ), mp3 ( ).
CallMeIn.php , SystemD callme.service, /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.targetsystemctl service
# systemctl enable callme
# systemctl start callme( ). , php ( FeePBX). ( https) .
. CallMeOut.php :
php ( "" ). , HTTP POST,
, . Asterisk [sub-call-internal-ended]

- ( HTTPS) CallMeOut.php. FreePBX, /var/www/html, .
(, , ). , FreeDomain( https://www.freenom.com/ru/index.html), IP ( 80, 443 , ). DNS , ( 15 48 ) . - 1 .
github , . - , , , . (
Docker
- Docker - , , ( LetsEncrypt , , FreePBX ( - 88), LetsEncrypt
( git clone), ( asterisk) URL
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:
docker-compose.yaml,
docker-compose up -d
nginx , nginx/ssl_docker.conf
CRM , . API CRM, - ShugarCRM Vtiger, ! , . , .
: ,