Tõenäoliselt oli paljude seas, nagu ka minul, idee luua midagi unikaalset. Selles artiklis kirjeldan tehnilisi probleeme ja lahendusi, millega pidin silmitsi seisma telefonisüsteemi arendamisel. Loodetavasti aitab see kedagi oma mõttega edasi liikuda või minna mööda juba välja käidud teed, sest mina kasutasin ka pioneeride kogemust.

Idee ja põhivajadused
Kõik algas banaalselt armastusest Asterisk ({framework, et ehitada kommunikatsioonirakendusi}), telefoniteenuste automatiseerimise ja FreePBX ({veebiliides, et Asterisk}). Kui ettevõtte vajadused olid tavalised ja mahtusid olemasolevatesse võimalustesse FreePBX – kõik oli suurepärane. Kogu installatsioon toimus ühe päeva jooksul, ettevõte sai seadistatud telefonisüsteemi, mugava liidese ning lühikese koolituse koos täiendava toe soovi korral.
Aga kõige huvitavad ülesanded olid ebatavalised ja siis polnud seda nii muinasjutuline. Asterisk saab palju, kuid et säilitada veebiliidese töökorras olek, tuli kulutada palju rohkem aega. Nii väikese asja jaoks võis aega kuluda oluliselt rohkem kui kogu ülejäänud telefonijaama seadistamiseks. Probleem ei olnud selles, et veebiliidese kirjutamine võtaks kaua aega, vaid pigem arhitektuuri eripärades. FreePBXArhitektuuri lähenemised ja meetodid FreePBX olid alustatud php4 ajal, samal ajal kui php5.6 oli juba olemas, millel oleks kõik lihtsam ja mugavam välja töötada.
Viimane piisk oli graafilised dialoopid skeemidena. Kui proovisin midagi sellist ehitada FreePBX, sain aru, et pean selle oluliselt ümber kirjutama ja on lihtsam luua midagi uut.
Peamisteks nõudmisteks said:
- lihtne seadistamine, mis on intuitiivselt kergesti ligipääsetav isegi algajale administraatorile. Seega ei pea ettevõtted meie poolel telefonijaama hooldust korraldama.
- lihtne kohandamine, et ülesanded saaksid lahendatud mõistlikus ajas.
- mugav integreerimine telefonijaamaga. U FreePBX ei olnud API-d seadete muutmiseks, s.t. ei saanud näiteks luua gruppe või häälemenüüsid kolmanda osapoole rakendusest, ainult vastava API kaudu. Asterisk,
- avatud lähtekoodiga – see on programmeerijatele äärmiselt oluline kliendi kohandamiste jaoks.
Kiirema arendamise idee seisnes selles, et kogu funktsionaalsus koosneb moodulitest objektide kujul. Kõigil objektidel pidi olema ühine vanemklass, mis tähendab, et kõikide põhi funktsioonide nimed on juba teada ja seega on olemas ka vaikimisi rakendused. Objektid võimaldavad dramaatiliselt vähendada argumentide arvu assotsiatiivsete massiivide kujul stringi võtmetega, mille üle saab teada, uurides kogu funktsiooni ja sisse põimitud funktsioone. FreePBX Objektide puhul näitab lihtne automaatne täiendamine kõiki omadusi ning see teeb elu palju lihtsamaks. Lisaks lahendab pärand ja ülekirjutamine juba palju probleeme, mis on seotud täiustamisega.
Järgmine asi, mis aeglustas täiustamise aega ja mida tuleks vältida, on dubleering. Kui on olemas moodul, mis vastutab töötajaga helistamise eest, siis kõik teised moodulid, kellel on vaja helistada töötajale, peaksid kasutama just seda moodulit, mitte looma oma koopiaid. Nii, kui tuleb midagi muuta, siis tuleb muuta ainult ühes kohas ja 'kuidas see töötab' otsimine toimub ühes kohas, mitte ei pea otsima kogu projektist.
Esimene versioon ja esimesed vead
Esimene prototüüp valmis juba aasta pärast. Kogu PBX, nagu planeeritud, oli moodulaarne ning moodulid said mitte ainult lisada uusi funktsioone kõnede töötlemiseks, vaid ka muuta veebiliidest.

Jah, ideed dialooge skeemina ehitamiseks ei ole mina loonud, kuid see on väga mugav ja tegin sama ka Asterisk.

Mooduli kirjutamise abil said programmeerijad juba:
- luua oma funktsioone kõne töötlemiseks, mida sai paigutada skeemile ja ka vasakpoolse elementide menüüsse,
- luua oma lehti veebiliidese jaoks ja lisada oma malle olemasolevatele lehtedele (kui lehe arendaja on seda ette näinud),
- lisada oma seadeid põhiseadete vahekaardile või luua oma seadete vahekaart,
- programmeerija saab pärida olemasolevalt moodulilt, muuta osa funktsionaalsusest ning registreerida uue nime all või asendada algne moodul.
Näiteks, nii saab luua oma kõnemenüü:
......
class CPBX_MYIVR extends CPBX_IVR
{
function __construct()
{
parent::__construct();
$this->_module = "myivr";
}
}
.....
$myIvrModule = new CPBX_MYIVR();
CPBXEngine::getInstance()->registerModule($myIvrModule,__DIR__); // Registreeri uus moodul
CPBXEngine::getInstance()->registerModuleExtension($myIvrModule,'ivr',__DIR__); // Asenda olemasolev moodulEsimesed keerukad rakendused tõid kaasa esimesed uhkusehetked ja esimesed pettumused. Rõõmustas see, et see töötas, et sain juba taastada peamised funktsioonid. FreePBX. Rõõmustas, et idee skeemist meeldis inimestele. Veel oli palju võimalusi arendust lihtsustada, kuid tol ajal toimusid juba osa ülesandeid lihtsamalt.
Pettumuseks sai API, et telefoni konfiuratsiooni muuta – see ei tulnud üldse selline nagu soovitud. Kasutasin sama põhimõtet, mis FreePBX, nuppu Apply vajutades luuakse kogu konfiguratsioon uuesti ja moodulid käivitatakse uuesti.
Näeb välja nii:

*Dialplaan – reegel (algoritm), mille alusel kõne töödeldakse.
Kuid sellise variandi korral ei ole võimalik kirjutada normaalseid API-sid telefoni seadete muutmiseks. Esiteks, muudatuste rakendamise protsess on Asterisk liialt aeglane ja ressursimahukas.
Teiseks ei saa kaht funktsiooni üheaegselt kutsuda, sest mõlemad loovad konfiguratsiooni.
Kolmandaks, rakendab kõik seaded, sealhulgas administraatori tehtud muudatused.
Selles versioonis, nagu ka Askozia, oli võimalik genereerida konfiguratsioon ainult muudetud moodulite jaoks ja taaskäivitada ainult vajalikke mooduleid, kuid see kõik oli vaevu piisav. Oli vajalik lähenemist muuta.
Teine versioon. Nina tõi saba välja
Probleemi lahendamise ideeks sai mitte luua konfiguratsiooni ega dialplaani uuesti Asterisk, vaid salvestada info andmebaasi ja lugeda andmebaasist otse kõne töötlemise ajal. Asterisk juba oskas lugeda konfiguratsioone andmebaasist, piisab, kui muuta väärtust andmebaasis ja järgmine kõne töödeldakse juba muudatustega, ning dialplaani parameetrite lugemiseks sobis suurepäraselt funktsioon REALTIME_HASH.
Kokkuvõttes ei olnud isegi vajalik taaskäivitamine Asterisk seadete muutmise korral ja kõik seaded hakkasid kohe kehtima Asterisk.

Ainsad dialplaani muutused olid sisemiste numbrite lisamine ja hints. Kuid need olid väikesed punktmuudatused.
exten=>101,1,GoSub(‘sub-callusers’,s,1(1)); - täiendav muutmine, lisatakse/muutatakse läbi ami
; sub-callusers – universaalne funktsioon, mis genereeritakse mooduli paigaldamisel.
[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)
...Rida dialplaani lisamine või muutmine on lihtne läbi Ami (haldamise liides Asterisk) ja kogu dialplaani taaskäivitamine ei ole vajalik.
Nii lahendati probleem API-ga konfiguratsiooni jaoks. Saadi isegi otse andmebaasi minna ja lisada uus grupp või muuta, näiteks, helistamise aega väljal “dialtime” grupi jaoks ja järgmine kõne kestab juba määratud aja (see ei ole soovitus, kuna mõnede API toimingute jaoks on vajalik Ami kõned kõned).
Esimene keeruline rakendus tõi taas esile nii uhkuse kui pettumuse. Olin rõõmus, et see töötab. Andmebaasist sai kriitiliselt oluline lüli, sõltuvus diskist kasvas, riske tuli juurde, kuid kõik toimis stabiilselt ja probleemideta. Eriti tore oli see, et kõik, mida sai teha veebiliidese kaudu, oli nüüd võimalik ka API kaudu, kasutades samu meetodeid. Lisaks vabanes veebiliides nupust „rakenda seaded PBX-ile“, millele haldurid sageli unustasid.
Pettumuseks osutus arenduse keerukuse kasv. Juba esimesest versioonist alates genereerib PHP dialplaani keeles Asterisk ja see näeb täiesti loetamatuna välja, lisaks on keel Asterisk dialplaani kirjutamiseks äärmiselt primitiivne.
Nii see nägi välja:
$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'))Teises versioonis muutus dialplan universaalseks, sellesse sisestati kõikvõimalikud töötlemise variandid sõltuvalt parameetritest ja selle suurus kasvas märkimisväärselt. Kõik see aeglustas arendusaega ja vaid mõte, et taas tuleb dialplani sekkuda, valmistas kurbust.
Kolmas versioon
Probleemi lahendamise ideeks oli mitte genereerida Asterisk dialplani PHP-st, vaid kasutada FastAGI ja kõik töötlemise reeglid kirjutada otse PHP-sse. FastAGI lubab Asterisk, et helistamiseks ühenduda soketiga. Sealt saada käske ja saata tulemusi. Nii on dialplani loogika juba väljaspool Asterisk ja seda saab kirjutada mistahes keeles, minu puhul PHP-s.
Siin oli palju katsetusi ja vigu. Peamine probleem oli see, et mul oli juba palju klasse/failide. Objektide loomisele, initsialiseerimisele ja vastastikusele registreerimisele nende vahel kulus umbes 1,5 sekundit ning see viivitus iga kõne puhul pole midagi, mida saaks ignoreerida.
Initsialiseerimine pidi olema ainult 1 kord ja seetõttu hakkasid lahenduse otsingud PHP-s teenuse kirjutamisega, kasutades PthreadsPärast nädalast katsetamist jäeti see variant kõrvale, kuna selle laienduse töö keerukus oli suur. Asünkroonsest programmeerimisest PHP-s tuli pärast kuud katsetusi samuti loobuda, vajalik oli midagi lihtsat, tuttavat iga PHP algajale, ja paljud PHP laiendused on sünkroonsed.
Lahenduseks sai oma multiteemaline teenus 'C' keeles, mis kompileeriti koos PHPLIB. See laadib alla kõik PHP failid ATEst, ootab, kuni kõik moodulid initsialiseeritakse, lisanavad üksteisele tagasisidele ja kui kõik on valmis – vahemällu salvestab. Kõne meie teenusesse saamisel FastAGI loodakse lõng, mille sees esitatakse vahemälust koopia kõigist klassidest ja andmetest ning päring edastatakse PHP funktsiooni.
Selle lahenduse korral vähenes aeg, mis kulus kõne saatmisest meie teenusesse kuni esimese käsu saatmiseni Asterisk 1,5 sekundilt 0,05 sekundile, ja see aeg sõltub vähe projekti suurusest.

Kokkuvõttes on dialplaani väljatöötamiseks vajalik aeg oluliselt vähenenud, ja ma oskan seda hinnata, kuna pidin ümber kirjutama kogu vaatleja dialplaani PHP-s. Esiteks, PHP-s peaksid olema juba kirjutatud meetodid objekti saamiseks andmebaasist, mis on vajalikud veebiliideses kuvamiseks, ja teiseks, mis on kõige olulisem – lõpuks on saanud mugavalt töötada numbreid ja andmebaasi massiive käsitlevate stringidega.
Dialplaani töötlemiseks tuleb mooduli klassis rakendada funktsioon dialplanDynamicCall ja argument pbxCallRequest sisaldab objekti, et suhelda Asterisk.

Lisaks on tekkinud võimalus dialplaani tõrkeotsinguks (PHP-s on xdebug ja meie teenus töötab selle raamistikuga), saab liikuda samm-sammult, vaadates muutujaid.
Kõnede andmed
Iga analüütika ja aruannete jaoks on vajalikud õigesti kogutud andmed, ja see PSTN plokk on läbinud palju katsetusi ja vigu esimesest kolmandast versioonist. Tihti on kõnede andmed tabelivormis. Üks kõne = üks kirje: kes helistas, kes vastas, kui kaua räägiti. Huvitavamates variantides on veel täiendav tabel, milliseid töötajaid PSTN kutsus kõne ajal. Kuid see katab vaid osa vajadustest.
Esialgseteks nõudmisteks olid:
- salvestada mitte ainult, kellele PSTN helistas, vaid ka kes vastas, sest esinevad kuulamised ja kõnede analüüsimisel tuleb seda arvesse võtta,
- aeg kuni töötajaga ühenduse saamiseni. Mõnes teises PSTN-s, loetakse kõne vastatuks, niipea kui PSTN tõstab telefoni. Kuid häälemenu jaoks tuleb telefon siiski tõsta, mistõttu kõik kõned loetakse vastatuteks ja ootamise aeg muutub 0-1 sekundiks. Seetõttu otsustati salvestada mitte ainult vastuseooteaeg, vaid ka aeg ühenduse saamiseks võtme moodulitega (moodul seondub endale selle lipuga. Praegu on see "Töötaja", "Väline liin"), FreePBX и некоторых других АТС, звонок считается отвеченным, как только АТС поднимет трубку. Но для голосового меню уже нужно поднять трубку, таким образом все звонки становятся отвеченными и время ожидания ответа становится 0-1 секунду. Поэтому решено было сохранять не только время до ответа, но время до соединения с ключевыми модулями (модуль сам устанавливает у себя это флаг. Сейчас это «Сотрудник», «Внешняя линия»),
- kompleksse dialeedi plaanide jaoks, kui kõne liigub erinevate gruppide vahel, oli vajalik uurida iga elementi eraldi.
Parim variant leidus, kui PBX moodulid saatsid ise kõnede teavet ja lõpuks salvestasid info puu kujul.
See näeb välja järgmine:
Alguses on üldine teave kõne kohta (nagu kõigil — midagi erilist ei ole).

- Tulnud kõne väliselt liinilt „Katses” kell 05:55:52 numbrilt 89295671458 numbrile 89999999999, millele vastas töötaja „Sekretär2” numbriga 104. Klient ootas 60 sekundit ja rääkis 36 sekundit.
- Töötaja „Sekretär2” helistab numbrile 112 ja sellele vastab töötaja „Müügiesindaja1” 8 sekundi pärast. Räägivad 14 sekundit.
- Klient suunatakse töötajale „müügiesindaja1”, kus nad jätkavad vestlemist veel 13 sekundit.
Kuid see on jäämäe tipp, iga salvestuse kohta saab saada üksikasjaliku ülevaate kõne kulgemisest PBX-is.

Kogu teave esitatakse kõnede pesastamise kujul:
- Tulnud kõne väliselt liinilt „Katses” kell 05:55:52 numbrilt 89295671458 numbrile 89999999999.
- Kell 05:55:53 saadab väline liin kõne siseneva skeemi „test»
- Во время обработки звонка по схеме вызывается модуль «вызов менеджера», в котором звонок находится 16 секунд. Это разработанный под клиента модуль.
- Модуль «вызов менеджера» отправляет звонок на ответственного за номер (клиента) сотрудника «Müügiesindaja1» и ожидает ответа 5 секунд. Менеджер не ответил.
- Модуль «вызов менеджера» отправляет звонок на группу «Менеджеры КОРП». Это другие менеджеры такого же направления (сидят в одной комнате) и ожидает ответа 11 секунд.
- Группа «Менеджеры КОРП» вызывает сотрудников «Müügiesindaja1, Менеджер2, Менеджер3» одновременно по 11 секунд. Ответа нет.
- Вызов менеджера завершается. И схема звонок отправляет на модуль «Выбор маршрута из 1с». Тоже написанный под клиента модуль. Тут звонок обрабатывался 0 секунд.
- Схема отправляет звонок на голосовое меню «Осн с донабором». Клиент в нём прождал 31 секунду, донабора не было.
- Схема отправляет звонок на Группу «Секретари», где клиент прождал 12 секунд.
- В группе вызывается одновременно 2 сотрудника «Секретарь1» ja «Sekretär2» и спустя 12 секунд отвечает сотрудник «Sekretär2». Ответ на вызов дублируется в родительские вызовы. Получается и в группе ответил «Sekretär2», при вызове схемы ответил «Sekretär2» ja vastas välistele kõnedele «Sekretär2».
Just info säilitamine iga toimingu ja nende seotuse kohta võimaldab lihtsasti koostada aruanne. Häälsüsteemi aruanne aitab välja selgitada, kui palju see aitab või takistab. Uurida, kui palju töötajate kõnesid jäi vastamata, arvestades, et kõne haarati ja seega ei loeta seda vastamata jäämiseks, ja arvestades, et see oli rühmakõne, kus keegi teine vastas varem, seega ka see ei ole vastamata kõne.
Selline info säilitamine võimaldab võtta iga grupi eraldi ja määrata, kui tõhusalt see töötab, koostada graafik vastatud ja vastamata kõnede kohta grupi ulatuses tundide järgi. Samuti saab kontrollida, kui hästi toimub ühendus vastutava juhiga, analüüsides üleminekuid pärast kontaktitöötlust.
Võib läbi viia ka üsna ebatüüpilisi uuringuid, näiteks kui sageli helistatakse numbritele, mida pole andmebaasis, õige lisakoodi või kui suur protsent väljaminevatest kõnedest on mobiilile suunamine.
Mis on lõpptulemus?
ATS-i haldamiseks ei ole vaja spetsialisti, sellega hakkab toime kõige tavalisem administraator – praktikas tõestatud.
Töötlemiseks ei ole vaja kõrge kvalifikatsiooniga spetsialiste, piisab php teadmistest, kuna moodulid on juba kirjutatud nii sip protokolli, järjekorra kui ka töötaja kõnede tegemiseks ja muuks. On olemas wrapper klass Asterisk. Mooduli arendamiseks võib (ja peaks) programmeerija kasutama juba olemasolevaid mooduleid. Ja teadmised Asterisk ei ole sugugi vajalikud, kui klient palub lisada lehte uue aruandega. Kuid praktika näitab, et kolmandate osapoolte programmid, kuigi saavad hakkama, tunnevad end ilma dokumentatsiooni ja korraliku kommentaaride katvusega ebamugavalt, seega on endiselt arenguruumi.
Moodulid võivad:
- luua uusi võimalusi kõne töötlemiseks,
- lisada uusi plokke veebiliidesesse,
- pärida ükskõik millisest olemasolevast moodulist, määrata ümber funktsioone ja asendada seda või olla lihtsalt kergelt muudetud koopia,
- lisada oma seadeid teiste moodulite seademalli ja palju muud.
ATS-i seadistamine API kaudu. Nagu ülalpool kirjeldatud, salvestatakse kõik seadistused andmebaasi ja loetakse välja kutsumise hetkel, seega on API kaudu võimalik muuta kõiki ATS-i seadistusi. API kutsumise korral ei luleta konfigureerimist uuesti ja mooduleid ei taaskäivitatud, seega ei ole oluline, kui palju teil seadistusi ja töötajaid on. API päringud toimuvad kiiresti ja ei blokeeri üksteist.
ATS salvestab kõik peamised tegevused kõnedes kestvustega (ootamisaeg/vestlus), sügavaid struktuure ja ATS-i mõistetes (töötaja, grupp, väline joon, mitte kanal, number). See võimaldab luua erinevaid aruandeid kindlate klientide jaoks ning enamik tööst - luua mugav kasutajaliides.
Aeg näitab, mis saab edasi. On veel palju nüansse, mida tuleks täiendavalt teha, on veel palju plaane, kuid kolmanda versiooni loomise hetkest on möödunud juba aasta ja on juba võimalik öelda, et idee toimib. Kolmanda versiooni peamine puudus on riistvararessursid, kuid arendamise mugavuse nimel tuleb tavaliselt just nii maksta.
Allikas: habr.com
