Sicuramente molti di voi, proprio come me, hanno avuto l'idea di realizzare qualcosa di unico. In questo articolo descriverò i problemi tecnici e le soluzioni affrontate durante lo sviluppo del centralino. Spero che possa aiutare qualcuno a prendere coraggio per la propria idea, mentre per altri potrà essere una guida nel percorso già tracciato, dato che anch'io ho beneficiato dell'esperienza dei pionieri.

L'idea e i requisiti chiave
Tutto è iniziato con la semplice passione per Asterisk (framework per costruire applicazioni di comunicazione), automazione della telefonia e installazioni di FreePBX (interfaccia web per Asterisk). Se le esigenze dell'azienda erano comuni e rientravano nelle possibilità FreePBX tutto andava alla grande. L'installazione richiedeva solo un giorno, l'azienda riceveva un centralino configurato, un'interfaccia user-friendly e un breve addestramento, oltre a supporto su richiesta.
Ma le sfide più interessanti erano quelle non standard, e allora non era tutto così semplice. Asterisk può fare molto, ma per mantenere funzionante l'interfaccia web, è stato necessario investire molto più tempo. Così, una piccola modifica ha richiesto molto più tempo rispetto all'installazione dell'intero sistema telefonico. Non si tratta che la scrittura dell'interfaccia web richieda tempo, ma piuttosto delle peculiarità dell'architettura. FreePBXApprocci e metodi architettonici FreePBX erano stati progettati ai tempi di php4, mentre in quel periodo era già disponibile php5.6, su cui tutto poteva essere fatto in modo più semplice e comodo.
L'ultima goccia sono stati i diagrammi grafici delle chiamate in forma di schema. Quando ho provato a costruire qualcosa di simile per FreePBX, ho capito che sarebbe stato necessario riscrivere in modo sostanziale e sarebbe stato più semplice costruire qualcosa di nuovo.
I requisiti chiave sono stati:
- una configurazione semplice, intuitivamente accessibile anche a un amministratore alle prime armi. In questo modo, le aziende non hanno bisogno di gestire il sistema telefonico da parte nostra,
- facile sviluppo, in modo che i compiti possano essere risolti in un tempo ragionevole,
- comodità di integrazione con il sistema telefonico. Non FreePBX aveva API per cambiare le impostazioni, cioè non era possibile, ad esempio, creare gruppi o menu vocali da un'applicazione esterna, solo l'API della stessa Asterisk,
- opensource – per i programmatori è estremamente importante per le modifiche personalizzate.
L'idea di uno sviluppo più rapido era che tutte le funzionalità fossero composte da moduli sotto forma di oggetti. Tutti gli oggetti dovevano avere una classe genitore comune, il che significa che i nomi di tutte le funzioni principali erano già noti e quindi c'erano già implementazioni predefinite. Gli oggetti consentiranno di ridurre drasticamente il numero di argomenti sotto forma di array associativi con chiavi stringa, dei quali si poteva scoprire in FreePBX modo esplorando l'intera funzione e le funzioni nidificate. Nel caso degli oggetti, il banale completamento automatico mostrerà tutte le proprietà, rendendo la vita molto più semplice. Inoltre, l'ereditarietà e l'override risolvono già molti problemi nelle modifiche.
La seguente cosa che rallentava i tempi di modifica e che andava evitata era la duplicazione. Se esiste un modulo responsabile per contattare un dipendente, tutti gli altri moduli che devono inviare una chiamata al dipendente devono utilizzare proprio quello, e non creare copie proprie. Così, se è necessario cambiare qualcosa, si dovrà modificare solo un posto e cercare "come funziona" in un solo luogo, anziché effettuare la ricerca in tutto il progetto.
La prima versione e i primi errori
Il primo prototipo era pronto già dopo un anno. L'intero sistema di telefonia, come previsto, era modulare, e i moduli potevano non solo aggiungere nuove funzionalità per la gestione delle chiamate, ma anche modificare l'interfaccia web.

Sì, l'idea di costruire il piano di chiamata come uno schema del genere non è mia, ma è piuttosto comoda e ho fatto lo stesso per Asterisk.

Con la scrittura del modulo, i programmatori potevano già:
- creare funzionalità proprie per la gestione delle chiamate, che potevano essere posizionate nello schema e anche nel menu degli elementi a sinistra,
- creare pagine proprie per l'interfaccia web e aggiungere i propri modelli alle pagine esistenti (se il creatore della pagina lo ha previsto),
- aggiungere le proprie impostazioni nella scheda delle impostazioni principali o creare una propria scheda con le impostazioni,
- il programmatore può ereditare da un modulo esistente, modificare parte della funzionalità e registrarlo con un nuovo nome o sostituire il modulo originale.
Ad esempio, ecco come si può creare il proprio menu vocale:
......
class CPBX_MYIVR extends CPBX_IVR
{
function __construct()
{
parent::__construct();
$this->_module = "myivr";
}
}
.....
$myIvrModule = new CPBX_MYIVR();
CPBXEngine::getInstance()->registerModule($myIvrModule,__DIR__); //Registrare un nuovo modulo
CPBXEngine::getInstance()->registerModuleExtension($myIvrModule,'ivr',__DIR__); //Sostituire il modulo esistenteLe prime complesse implementazioni hanno portato orgoglio e anche delusioni. Era soddisfacente sapere che funzionava, che ero già riuscito a riprodurre le funzionalità di base. FreePBX. Era gratificante vedere che l'idea del diagramma piaceva alle persone. C'erano ancora molte opzioni per semplificare lo sviluppo, ma anche in quel momento alcune attività erano già più semplici da realizzare.
Una delusione è stata l'API per la modifica della configurazione della centrale telefonica: non era affatto quello che desideravo. Ho adottato lo stesso principio che in FreePBX, premendo il pulsante Applica si ricrea tutta la configurazione e si riavviano i moduli.
Si presenta così:

*Il piano di chiamata è una regola (algoritmo) secondo cui viene gestita la chiamata.
Ma con questa opzione non è possibile scrivere una buona API per modificare le impostazioni della centrale telefonica. In primo luogo, l'operazione di applicazione delle modifiche è Asterisk troppo lunga e dispendiosa in termini di risorse.
In secondo luogo, non è possibile chiamare due funzioni contemporaneamente, poiché entrambe creerebbero la configurazione.
In terzo luogo, applica tutte le impostazioni, comprese quelle effettuate dall'amministratore.
In questa versione, così come in Askozia, era possibile generare la configurazione solo dei moduli modificati e riavviare solo i moduli necessari, ma tutto ciò era solo un palliativo. Era necessario cambiare approccio.
Seconda versione. Il naso ha estratto la coda.
L'idea per risolvere il problema è stata quella di non ricreare la configurazione e il piano di chiamata per Asterisk, ma di salvare le informazioni nel database e leggerle direttamente durante la gestione della chiamata. Asterisk già sapeva leggere le configurazioni dal database, è sufficiente cambiare il valore nel database e la prossima chiamata verrà gestita tenendo conto delle modifiche, mentre per la lettura dei parametri del piano di chiamata si adattava perfettamente la funzione REALTIME_HASH.
Di conseguenza, non era nemmeno necessario riavviare Asterisk quando si modificavano le impostazioni e tutte le impostazioni venivano applicate immediatamente a Asterisk.

Le uniche modifiche al piano di chiamata sono state l'aggiunta di numeri interni e hints. Ma si trattava di piccole modifiche puntuali.
exten=>101,1,GoSub('sub-callusers',s,1(1)); - modifica puntuale, aggiunto/modificato tramite ami
; sub-callusers – funzione universale generata durante l'installazione del modulo.
[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)
...Aggiungere o modificare una riga nel piano di chiamata è facile tramite Ami (interfaccia di gestione Asterisk) e non è necessario riavviare l'intero piano di chiamata.
Così è stato risolto il problema con l'API per la configurazione. Era anche possibile accedere direttamente al database e aggiungere un nuovo gruppo o modificare, ad esempio, il tempo di composizione nel campo “dialtime” del gruppo e la prossima chiamata durerà già il tempo specificato (questo non è un consiglio d'azione, poiché per alcune operazioni API sono richiesti Ami chiamate).
Le prime implementazioni complesse hanno nuovamente portato orgoglio e delusione. Il fatto che tutto funzionasse era confortante. Il database è diventato un anello critico, la dipendenza dal disco è aumentata, i rischi sono cresciuti, ma tutto funzionava in modo stabile e senza problemi. Inoltre, ora tutto ciò che poteva essere fatto tramite l'interfaccia web poteva essere realizzato anche tramite API, utilizzando gli stessi metodi. Inoltre, l'interfaccia web ha eliminato il pulsante «applica impostazioni all'ATS», di cui gli amministratori spesso si scordavano.
La delusione è stata la complessità aumentata dello sviluppo. Fin dalla prima versione, il linguaggio PHP genera il piano di chiamata in un linguaggio Asterisk e appare completamente illeggibile, inoltre il linguaggio Asterisk per scrivere il piano di chiamata è estremamente primitivo.
Ecco come si presentava:
$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'))Nella seconda versione, il piano dialettico è diventato universale, incorporando tutte le possibili varianti di elaborazione in base ai parametri, e le sue dimensioni sono cresciute notevolmente. Tutto ciò rallentava significativamente il tempo di sviluppo, e l'idea di dover intervenire ancora una volta nel piano dialettico diventava fonte di tristezza.
Terza versione
L'idea per risolvere il problema è stata quella di non generare Asterisk il piano dialettico da php, ma di utilizzare FastAGI e scrivere tutte le regole di elaborazione direttamente in php. FastAGI permette Asterisk, per gestire la chiamata, connettersi al socket. Ricevere comandi da lì e inviare risultati. In questo modo, la logica del piano dialettico si trova già al di fuori Asterisk e può essere scritta in qualsiasi linguaggio, nel mio caso in php.
Ci sono stati molti tentativi ed errori. Il principale problema era che avevo già molti classi/file. Creare oggetti, inizializzarli e registrarsi reciprocamente richiedeva circa 1,5 secondi, e questo ritardo per ogni chiamata non era qualcosa che si potesse ignorare.
L'inizializzazione doveva avvenire solo una volta e quindi la ricerca di una soluzione è iniziata scrivendo un servizio in php utilizzando Pthreads. Dopo una settimana di esperimenti, questa opzione è stata accantonata a causa delle complessità di funzionamento di questa estensione. Anche la programmazione asincrona in PHP è stata abbandonata dopo un mese di test; era necessario qualcosa di semplice, familiare a qualsiasi principiante di PHP, e molte estensioni per PHP sono sincrone.
La soluzione è stata un proprio servizio multithread in ‘C’, che si compilava con PHPLIB. Questo carica tutti i file PHP della PBX, aspetta che tutti i moduli siano inizializzati, che si aggiungano callback tra loro e quando tutto è pronto – memorizza nella cache. Quando si riceve una richiesta, FastAGI viene creato un thread, in cui viene riprodotta una copia dalla cache di tutte le classi e dati, e la richiesta viene passata a una funzione PHP.
Con questa soluzione, il tempo dall'invio della chiamata al nostro servizio fino al primo comando Asterisk è sceso da 1,5s a 0,05s e questo tempo dipende poco dalle dimensioni del progetto.

Alla fine, il tempo per lo sviluppo del piano di chiamata è diminuito notevolmente, e posso apprezzarlo poiché ho dovuto riscrivere l'intero piano di chiamata di tutti i moduli in php. In primo luogo, in php dovrebbero già essere stati scritti metodi per ottenere l'oggetto dal database, necessari per la visualizzazione nell'interfaccia web, e in secondo luogo, e questo è il punto principale – finalmente è stata data la possibilità di lavorare comodamente con stringhe di numeri e array del database, insieme a numerosi estensioni di php.
Per elaborare il piano di chiamata nella classe del modulo è necessaria l'implementazione di una funzione dialplanDynamicCall e l'argomento pbxCallRequest conterrà un oggetto per interagire con Asterisk.

È stata inoltre introdotta la possibilità di fare debug del piano di chiamata (in php esiste xdebug e per il nostro servizio funziona), è possibile procedere passo dopo passo visualizzando i valori delle variabili.
Dati sulle chiamate
Per qualsiasi analisi e report sono necessarie dati correttamente raccolti e questo blocco PBX ha attraversato molte prove e errori dalla prima alla terza versione. Spesso i dati sulle chiamate si presentano come una tabella. Una chiamata = una registrazione: chi ha chiamato, chi ha risposto, quanto è durata la conversazione. In opzioni più interessanti c'è anche un'ulteriore tabella che mostra quali dipendenti del PBX sono stati chiamati durante la conversazione. Tuttavia, tutto ciò copre solo una parte delle esigenze.
I requisiti iniziali sono stati:
- salvare non solo a chi ha chiamato il PBX, ma anche chi ha risposto, poiché ci sono call grabbing e nell'analisi delle chiamate questo deve essere considerato.
- il tempo fino alla connessione con l'operatore. In FreePBX alcuni altri PBX, una chiamata è considerata risposta non appena il PBX alza la cornetta. Ma per i menu vocali è necessario alzare la cornetta, in questo modo tutte le chiamate vengono considerate risposte e il tempo di attesa per la risposta diventa 0-1 secondo. Pertanto, si è deciso di registrare non solo il tempo di risposta, ma anche il tempo fino alla connessione con i moduli chiave (il modulo stesso imposta questo flag. Attualmente si tratta di "Operatore", "Linea esterna").
- per un piano di dial combinato più complesso, era necessaria la possibilità di esaminare ogni elemento singolarmente.
L'opzione migliore si è rivelata quella in cui i moduli dell'ATS inviano autonomamente informazioni sulle chiamate, permettendo infine di conservare le informazioni in forma ad albero.
Ecco come appare:
Iniziamo con le informazioni generali sulla chiamata (come per tutti — nulla di speciale).

- È stata ricevuta una chiamata sulla linea esterna «Per il test» alle 05:55:52 dal numero 89295671458 al numero 89999999999, alla quale ha risposto l'operatore «Segretario2» con il numero 104. Il cliente ha atteso per 60 secondi e ha parlato per 36 secondi.
- L'operatore «Segretario2» effettua una chiamata al numero 112 e l'operatore «Manager1» risponde dopo 8 secondi. Parlano per 14 secondi.
- Il cliente viene trasferito all'operatore «manager1» dove continuano a parlare per altri 13 secondi.
Ma questa è solo la punta dell'iceberg; per ogni registrazione è possibile ottenere un resoconto dettagliato della chiamata tramite l'ATS.

Tutte le informazioni sono presentate in forma di nidificazione delle chiamate:
- È stata ricevuta una chiamata sulla linea esterna «Per il test» alle 05:55:52 dal numero 89295671458 al numero 89999999999.
- Alle 05:55:53 la linea esterna invia la chiamata allo schema Inbound «test»
- Durante la gestione della chiamata viene attivato il modulo «chiamata del manager», in cui la chiamata è rimasta per 16 secondi. Questo è un modulo sviluppato su misura per il cliente.
- Il modulo «chiamata del manager» inoltra la chiamata all'impiegato responsabile del numero (cliente) «Manager1» e attende una risposta per 5 secondi. Il manager non ha risposto.
- Il modulo «chiamata del manager» inoltra la chiamata al gruppo «Manager KORP». Questi sono altri manager dello stesso settore (lavorano nella stessa stanza) e attendono una risposta per 11 secondi.
- Il gruppo «Manager KORP» richiama contemporaneamente gli impiegati «Manager1, Manager2, Manager3» per 11 secondi. Non c'è risposta.
- La chiamata del manager termina. E lo schema della chiamata inoltra al modulo «Selezione del percorso da 1s». Anche questo è un modulo sviluppato per il cliente. Qui la chiamata è stata elaborata per 0 secondi.
- Lo schema inoltra la chiamata al menu vocale «Base con chiamata aggiuntiva». Il cliente ha atteso 31 secondi, senza chiamata aggiuntiva.
- Lo schema inoltra la chiamata al Gruppo «Secretari», dove il cliente ha atteso 12 secondi.
- Nel gruppo vengono richiamati simultaneamente 2 impiegati «Segretario1» e «Segretario2» e dopo 12 secondi risponde l'impiegato «Segretario2». La risposta alla chiamata viene duplicata nelle chiamate genitore. Risulta che nel gruppo ha risposto «Segretario2», alla chiamata del modulo ha risposto «Segretario2» e alla chiamata su linea esterna ha risposto «Segretario2».
La conservazione delle informazioni su ogni operazione e sulla loro nidificazione consentirà di generare report dettagliati. Il report sul menu vocale aiuterà a capire quanto questo strumento sia utile o meno. È possibile costruire un report sulle chiamate perse dai dipendenti tenendo conto dei casi in cui la chiamata è stata presa in carico, quindi non conteggiata come persa, e considerando anche le chiamate di gruppo, in cui un altro partecipante ha risposto prima, quindi anche quella non è considerata persa.
Questa modalità di archiviazione delle informazioni permetterà di analizzare ogni singolo gruppo e determinare quanto sia efficace, costruendo un grafico delle chiamate risposte e perse nel corso delle ore. Si può anche verificare quanto bene riesca a connettersi con il responsabile, analizzando i trasferimenti dopo la connessione con il manager.
Si possono inoltre condurre ricerche piuttosto particolari, ad esempio, con quale frequenza vengono composti numeri non presenti nel database, o quale percentuale delle chiamate in uscita è una deviazione verso un cellulare.
Quali sono i risultati finali?
Per la gestione di un centralino non è necessaria una figura specializzata; è sufficiente un amministratore comune – provato nella pratica.
Non servono esperti altamente qualificati per le modifiche, bastano delle conoscenze di PHP, poiché sono già stati scritti moduli per il protocollo SIP, per le code, per la chiamata di un dipendente e altro. Esiste una classe wrapper per Asterisk. Un programmatore per lo sviluppo di un modulo può (e dovrebbe) richiamare già dei moduli pronti. E le conoscenze Asterisk non sono affatto necessarie, se il cliente richiede di aggiungere una pagina con un nuovo report. Ma la pratica dimostra che i programmatori esterni, sebbene possano gestirlo, si sentono insicuri senza documentazione e un'adeguata copertura di commenti, quindi c'è ancora molto spazio per migliorare.
I moduli possono:
- creare nuove funzionalità per la gestione delle chiamate,
- aggiungere nuovi blocchi all'interfaccia web,
- ereditarie da uno qualsiasi dei moduli esistenti, ridefinire funzioni e sostituirlo o semplicemente essere una copia leggermente modificata,
- aggiungere le proprie impostazioni nel modello di configurazione di altri moduli e molto altro.
Impostazioni della centrale telefonica tramite API. Come descritto sopra, tutte le impostazioni sono memorizzate nel database e lette al momento della chiamata, quindi tramite API è possibile modificare tutte le impostazioni della centrale. Quando si chiama l'API, la configurazione non viene ricreata e i moduli non vengono riavviati, quindi non importa quante impostazioni e dipendenti avete. Le richieste API vengono eseguite rapidamente e non si bloccano a vicenda.
La centrale telefona registra tutte le operazioni chiave relative alle chiamate, con durate (attesa/conversazione), annidamenti e in termini di centrale (dipendente, gruppo, linea esterna, non canale, numero). Questo consente di creare vari report per clienti specifici e gran parte del lavoro consiste nel realizzare un'interfaccia comoda.
Cosa accadrà in seguito lo dirà il tempo. Ci sono ancora molte sfide da rivedere, ci sono molti piani, ma è già passato un anno dalla creazione della terza versione e possiamo già dire che l'idea funziona. Il principale svantaggio della terza versione sono le risorse hardware, ma per la comodità dello sviluppo di solito si deve pagare in questo modo.
Fonte: habr.com
