Bitrix24 is a huge harvester that combines CRM, document flow, accounting and many other things that managers really like and IT staff don't really like. The portal is used by a lot of small and medium-sized companies, including small clinics, manufacturing workers and even beauty salons. The main function that managers "love" is the integration of telephony and CRM, when any call is immediately recorded in CRM, customer cards are created, information about the client is displayed when incoming, and you can immediately see who he is, what he can sell and how much he owes. But telephony from Bitrix24 and its integration with CRM costs money, sometimes a lot. In the article I will tell you the experience of integration with open tools and the popular IP PBX FreePBX , and also consider the logic of the work of various parts
I work as an outsourcing company in a company that sells and configures, integrates IP telephony. When I was asked if we could offer this and this company something to integrate Bitrix24 with PBXs that customers have, as well as with virtual PBXs on various VDS companies, I went to Google. And he, of course, gave me a link to an article in Habr , where there is a description, and github, and everything seems to work. But when trying to use this solution, it turned out that Bitrix24 is not what it used to be, and a lot needs to be redone. In addition, FreePBX is not a naked asterisk for you, here you need to think about how to combine usability and a hardcore dialplan in the config files.
We study the logic of work
So first, how is it supposed to work. When a call comes from outside to the PBX (SIP INVITE event from the provider), processing of the dialplan (dialplan, dialplan) starts - the rules of what to do with the call and in what order. A lot of information can be obtained from the first package, which can then be used in the rules. An excellent tool for studying the insides of SIP is the sngrep analyzer ( link ), which is simply installed in popular distributions via apt install / yum install and the like, but you can also build it from source. Let's see the call log in sngrep
In a simplified form, the dialplan deals with only the first package, sometimes during the conversation, calls are transferred, buttons pressed (DTMF), various interesting things like FollowMe, RingGroup, IVR and others.
What's inside the Invite package
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> exit
PHP
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.target
systemctl 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, ! , . , .
: ,