Wie viele von euch hatte, wie ich, die Idee, etwas Einzigartiges zu schaffen? In diesem Artikel beschreibe ich die technischen Herausforderungen und Lösungen, mit denen ich bei der Entwicklung einer Telefonanlage konfrontiert war. Vielleicht hilft es dem einen oder anderen, sich für die eigene Idee zu entscheiden oder den bereits beschrittenen Weg zu gehen, denn auch ich habe von den Pionieren profitiert.

Idee und Hauptanforderungen
Alles begann einfach aus der Liebe zu Asterisk (Framework zur Erstellung von Kommunikationsanwendungen), zur Automatisierung der Telefonie und der Installationen FreePBX (Webschnittstelle für Asterisk). Wenn die Anforderungen des Unternehmens nicht besonders waren und in die Möglichkeiten von FreePBX passten, lief alles reibungslos. Die gesamte Installation erfolgte innerhalb eines Tages, das Unternehmen erhielt eine konfigurierte Telefonanlage, eine benutzerfreundliche Oberfläche und eine kurze Schulung sowie ggf. Unterstützung.
Die spannendsten Aufgaben waren jedoch die unkonventionellen, und damals war das nicht so märchenhaft. Asterisk kann viel, aber um die Web-Oberfläche funktionsfähig zu halten, musste ich viel mehr Zeit investieren. Eine kleine Kleinigkeit konnte mehr Zeit in Anspruch nehmen als die Installation des gesamten anderen PBX-Systems. Es liegt nicht daran, dass die Erstellung einer Weboberfläche lange dauert, sondern eher an den architektonischen Besonderheiten. FreePBXArchitekturansätze und -methoden FreePBX wurden zu Zeiten von php4 festgelegt, obwohl zu diesem Zeitpunkt bereits php5.6 existierte, in dem alles einfacher und benutzerfreundlicher hätte realisiert werden können.
Der letzte Tropfen waren die grafischen Dialplans in Form von Diagrammen. Als ich versuchte, etwas Ähnliches für FreePBXzu erstellen, wurde mir klar, dass ich es erheblich umschreiben musste, und es einfacher wäre, etwas Neues zu bauen.
Die Schlüsselanforderungen waren:
- einfache Einrichtung, die selbst für einen Anfänger-Administrator intuitiv zugänglich ist. Damit benötigen die Unternehmen keinen Support für das PBX auf unserer Seite.
- leichte Anpassungsmöglichkeiten, damit Aufgaben in angemessener Zeit gelöst werden können.
- benutzerfreundliche Integration mit PBX. Es FreePBX gab keine API zum Ändern von Einstellungen, d.h. es war beispielsweise nicht möglich, Gruppen oder Sprachmenüs aus einer externen Anwendung zu erstellen, nur das API selbst. Asterisk,
- Open Source – für Programmierer ist dies äußerst wichtig für Anpassungen nach Kundenwunsch.
Die Idee einer schnelleren Entwicklung war, dass die gesamte Funktionalität aus Modulen in Form von Objekten bestehen sollte. Alle Objekte müssen eine gemeinsame Elternklasse haben, was bedeutet, dass die Namen aller grundlegenden Funktionen bereits bekannt sind und es somit Standardimplementierungen gibt. Objekte ermöglichen es, die Anzahl der Argumente in Form von assoziativen Arrays mit String-Schlüsseln erheblich zu reduzieren, deren Kenntnis durch die Untersuchung der gesamten Funktion und der verschachtelten Funktionen erlangt werden kann. FreePBX Bei Objekten wird die einfache Autovervollständigung alle Eigenschaften anzeigen und insgesamt das Leben erheblich erleichtern. Zudem decken Vererbung und Überschreibung bereits viele Probleme mit Anpassungen ab.
Das nächste, was die Entwicklungszeit verlangsamte und vermieden werden sollte, war die Duplication. Wenn es ein Modul gibt, das für das Anrufen eines Mitarbeiters verantwortlich ist, sollten alle anderen Module, die einen Anruf an den Mitarbeiter senden müssen, genau dieses verwenden und keine eigenen Kopien erstellen. So, wenn etwas geändert werden muss, muss man nur an einem Ort ändern und die Suche nach 'wie das funktioniert' erfolgt an einem Ort und nicht an mehreren Stellen im gesamten Projekt.
Die erste Version und die ersten Fehler
Der erste Prototyp war bereits ein Jahr später fertig. Die gesamte Telematik war modular, wie geplant, und die Module konnten nicht nur neue Funktionen zur Anrufbearbeitung hinzufügen, sondern auch das Web-Interface selbst ändern.

Ja, die Idee, den Dialplan in Form eines solchen Schemas zu erstellen, stammt nicht von mir, aber sie ist sehr praktisch und ich habe dasselbe für Asterisk.

Durch die Erstellung eines Moduls konnten die Programmierer bereits:
- eigene Funktionen zur Anrufbearbeitung erstellen, die im Schema sowie im linken Elementmenü platziert werden konnten,
- eigene Seiten für das Web-Interface erstellen und eigene Vorlagen auf bestehenden Seiten hinzufügen (sofern der Seitenentwickler dies vorgesehen hat),
- eigene Einstellungen im Tab für die allgemeinen Einstellungen hinzufügen oder einen eigenen Tab mit Einstellungen erstellen,
- der Programmierer kann von einem bestehenden Modul erben, einen Teil der Funktionalität ändern und unter einem neuen Namen registrieren oder das originale Modul ersetzen.
So kann man beispielsweise ein eigenes Sprachmenü erstellen:
......
class CPBX_MYIVR extends CPBX_IVR
{
function __construct()
{
parent::__construct();
$this->_module = "myivr";
}
}
.....
$myIvrModule = new CPBX_MYIVR();
CPBXEngine::getInstance()->registerModule($myIvrModule,__DIR__); // Neues Modul registrieren
CPBXEngine::getInstance()->registerModuleExtension($myIvrModule,'ivr',__DIR__); // Vorhandenes Modul ersetzenDie ersten komplexen Implementierungen brachten sowohl Stolz als auch erste Enttäuschungen. Es war erfreulich zu sehen, dass es funktionierte und ich bereits die grundlegenden Funktionen reproduzieren konnte. FreePBX. Es freute mich, dass die Menschen die Idee des Schemas mochten. Es gab noch viele Möglichkeiten, die Entwicklung zu vereinfachen, aber auch zu diesem Zeitpunkt wurden bereits einige Aufgaben einfacher erledigt.
Die Enttäuschung war die API zur Änderung der Konfiguration der TK-Anlage — es kam ganz anders heraus, als ich es mir gewünscht hatte. Ich nahm dasselbe Prinzip, das ich in FreePBX, beim Drücken der Taste 'Übernehmen' wird die gesamte Konfiguration neu erstellt und die Module werden neu gestartet.
So sieht es aus:

*Der Dialplan ist die Regel (Algorithmus), nach der der Anruf verarbeitet wird.
Bei dieser Variante ist es jedoch unmöglich, eine gute API zur Änderung der Einstellungen der TK-Anlage zu schreiben. Erstens ist der Vorgang der Anwendung von Änderungen Asterisk zu langwierig und ressourcenintensiv.
Zweitens können nicht zwei Funktionen gleichzeitig aufgerufen werden, da beide die Konfiguration erstellen würden.
Drittens wendet es alle Einstellungen an, einschließlich der vom Administrator vorgenommenen.
In dieser Version, wie auch in Askozia, konnte die Konfiguration nur für geänderte Module generiert werden, und es war nur möglich, die notwendigen Module neu zu starten, aber das war alles nur ein halber Schritt. Es musste der Ansatz geändert werden.
Zweite Version. Die Nase hat den Schwanz herausgezogen.
Die Idee zur Lösung des Problems bestand darin, die Konfiguration und den Dialplan für Asterisk, nicht neu zu erstellen, sondern die Informationen in der Datenbank zu speichern und während der Anrufbearbeitung direkt aus der Datenbank zu lesen. Asterisk konnte bereits Konfigurationen aus der Datenbank lesen; es genügt, den Wert in der Datenbank zu ändern, und der nächste Anruf wird bereits unter Berücksichtigung der Änderungen bearbeitet. Für das Auslesen der Dialplanparameter eignet sich hervorragend die Funktion REALTIME_HASH.
Infolgedessen war es nicht einmal nötig, neu zu starten Asterisk bei der Änderung von Einstellungen, und alle Einstellungen wurden sofort auf Asterisk.

Die einzigen Änderungen im Dialplan bestehen in der Hinzufügung interner Nummern und hints. Aber das waren kleine punktuelle Änderungen.
exten=>101,1,GoSub('sub-callusers',s,1(1)); - gezielte Anpassung, wird über ami hinzugefügt/verändert
; sub-callusers – universelle Funktion, die bei der Installation des Moduls generiert wird.
[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)
...Es ist einfach, eine Zeile im Dialplan hinzuzufügen oder zu ändern über Ami (Steuerungsschnittstelle Asterisk) und ein vollständiger Neustart des Dialplans ist nicht erforderlich.
So wurde das Problem mit der API zur Konfiguration gelöst. Man konnte sogar direkt in die Datenbank gehen und eine neue Gruppe hinzufügen oder beispielsweise die Anrufdauer im Feld “dialtime” der Gruppe ändern, und der nächste Anruf wird schon die angegebene Zeit dauern (Das ist keine Handlungsempfehlung, da für einige API-Operationen erforderlich sind Ami Anrufe).
Die ersten komplexen Implementierungen brachten erneut Stolz und Enttäuschung. Es war erfreulich zu sehen, dass es funktionierte. Die Datenbank wurde zu einem kritischen Element, die Abhängigkeit vom Speicher stieg, die Risiken nahmen zu, aber es lief stabil und ohne Probleme. Und vor allem konnte nun alles, was über die Web-Oberfläche erreichbar war, auch über die API erledigt werden, wobei dieselben Methoden verwendet wurden. Zusätzlich wurde der Web-Interface von der Schaltfläche „Einstellungen auf die Telefonanlage anwenden“ befreit, an die die Administratoren häufig vergaßen.
Die Enttäuschung über die Komplexität in der Entwicklung war groß. Bereits seit der ersten Version generiert die Sprache PHP den Dialplan in einer Sprache, Asterisk und es sieht völlig unleserlich aus, dazu ist die Sprache Asterisk zum Schreiben des Dialplans extrem primitiv.
So sah es aus:
$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'))In der zweiten Version wurde der Dialplan universell, es wurden alle möglichen Varianten der Verarbeitung je nach Parametern integriert und seine Größe wuchs erheblich. All das verlangsamte die Entwicklungszeit stark und der Gedanke, dass man erneut in den Dialplan eingreifen müsste, machte traurig.
Dritte Version
Die Idee zur Lösung des Problems war, den Dialplan nicht aus PHP zu generieren, sondern Asterisk FastAGI und alle Verarbeitungsregeln bereits in PHP zu schreiben. und alle Verarbeitungsregeln bereits in echtem PHP schreiben. und alle Verarbeitungsregeln bereits in PHP zu schreiben. ermöglicht es Asterisk, um den Anruf zu verarbeiten, sich mit dem Socket zu verbinden. Befehle von dort zu empfangen und Ergebnisse zu senden. Auf diese Weise liegt die Logik des Dialplans bereits außerhalb Asterisk und kann in jeder Sprache geschrieben werden, in meinem Fall in PHP.
Es gab viele Versuche und Irrtümer. Das Hauptproblem war, dass ich bereits viele Klassen/Dateien hatte. Die Erstellung von Objekten, die Initialisierung und die gegenseitige Registrierung zwischen ihnen dauerte etwa 1,5 Sekunden und diese Verzögerung bei jedem Anruf ist nicht etwas, das man ignorieren kann.
Die Initialisierung musste nur einmal erfolgen, und daher begann die Suche nach einer Lösung mit der Erstellung eines Dienstes in PHP unter Verwendung von Pthreads. Nach einer Woche Experimenten wurde diese Variante aufgrund der Feinheiten der Funktionsweise dieser Erweiterung verworfen. Auch von der asynchronen Programmierung in PHP musste nach einem Monat Tests Abstand genommen werden; es musste etwas Einfaches gefunden werden, das jedem PHP-Neuling vertraut ist, und viele PHP-Erweiterungen sind synchron.
Die Lösung war ein eigener multithreaded Service in C, der mit PHPLIB. Er lädt alle PHP-Dateien der TK-Anlage, wartet, bis alle Module initialisiert sind, fügt Callbacks miteinander hinzu und wenn alles bereit ist – wird es im Cache gespeichert. Bei einer Anfrage durch und alle Verarbeitungsregeln bereits in PHP zu schreiben. wird ein Thread erstellt, in dem eine Kopie aus dem Cache aller Klassen und Daten reproduziert wird und die Anfrage an die PHP-Funktion übergeben wird.
Mit dieser Lösung verkürzte sich die Zeit von der Absendung des Anrufs an unseren Service bis zum ersten Befehl Asterisk von 1,5 s auf 0,05 s, und diese Zeit hängt nur schwach von der Größe des Projekts ab.

Letztendlich wurde die Entwicklungszeit für den Dialplan erheblich verkürzt, und ich kann das bewerten, da ich den gesamten Dialplan aller Module in PHP neu schreiben musste. Erstens müssen in PHP Methoden zur Abfrage von Objekten aus der Datenbank geschrieben sein, da diese zur Anzeige im Web-Interface benötigt werden. Zweitens – und das ist das Wichtigste – es ist endlich möglich, bequem mit Zeichenfolgen, Zahlen, Arrays und der Datenbank sowie zahlreichen PHP-Erweiterungen zu arbeiten.
Um den Dialplan in der Modulklasse zu bearbeiten, muss eine Funktion implementiert werden dialplanDynamicCall und das Argument pbxCallRequest wird ein Objekt enthalten, um mit Asterisk.

Zusätzlich gibt es die Möglichkeit, den Dialplan zu debuggen (in PHP gibt es Xdebug, und es funktioniert mit unserem Service), man kann schrittweise vorgehen und die Werte der Variablen einsehen.
Daten zu Anrufen
Für jede Analyse und Berichterstattung sind korrekt gesammelte Daten erforderlich, und dieser ACD-Bereich hat ebenfalls viele Tests und Fehler von der ersten bis zur dritten Version durchlaufen. Häufig handelt es sich bei den Anrufdaten um eine Tabelle. Ein Anruf = ein Eintrag: Wer angerufen hat, wer geantwortet hat, wie lange gesprochen wurde. In interessanteren Varianten gibt es eine zusätzliche Tabelle, die zeigt, welcher Mitarbeiter der ACD während des Anrufs kontaktiert wurde. Aber all dies deckt nur einen Teil der Anforderungen ab.
Die ursprünglichen Anforderungen waren:
- nicht nur zu speichern, an wen die ACD angerufen hat, sondern auch, wer geantwortet hat, da es Abhörungen gibt und dies bei der Analyse von Anrufen berücksichtigt werden muss.
- Die Zeit bis zur Verbindung mit dem Mitarbeiter. In FreePBX einigen anderen ACDs wird ein Anruf als beantwortet angesehen, sobald die ACD den Hörer abnimmt. Aber für das Sprachmenü muss der Hörer ebenfalls abgehoben werden, sodass alle Anrufe als beantwortet gelten und die Wartezeit bis zur Antwort zwischen 0 und 1 Sekunde liegt. Daher wurde beschlossen, nicht nur die Zeit bis zur Antwort zu speichern, sondern auch die Zeit bis zur Verbindung mit den Schlüsselmodulen (das Modul setzt dieses Flag selbst. Aktuell sind das "Mitarbeiter", "Externe Leitung").
- Für komplexere Dialplans, bei denen der Anruf zwischen verschiedenen Gruppen wechselt, war es notwendig, jedes Element einzeln untersuchen zu können.
Die beste Lösung war, dass die PBX-Module Informationen über Anrufe selbstständig versenden und diese Informationen letztendlich in Form eines Baums speichern.
So sieht es aus:
Zunächst allgemeine Informationen über den Anruf (wie bei allen – nichts Besonderes).

- Ein Anruf über die externe Leitung „Für den Test“ um 05:55:52 von der Nummer 89295671458 zur Nummer 89999999999 eingegangen, wobei ein Mitarbeiter „Sekretär2“ mit der Nummer 104 geantwortet hat. Der Kunde hat 60 Sekunden gewartet und 36 Sekunden lang gesprochen.
- Der Mitarbeiter „Sekretär2“ initiiert einen Anruf an die Nummer 112, und nach 8 Sekunden antwortet der Mitarbeiter „Manager1“. Sie sprechen 14 Sekunden.
- Der Kunde wird an den Mitarbeiter „manager1“ weitergeleitet, wo sie weitere 13 Sekunden sprechen.
Aber das ist nur die Spitze des Eisbergs; zu jedem Eintrag können detaillierte Informationen über den Anrufverlauf in der PBX abgerufen werden.

Alle Informationen werden in Form von Anrufverschachtelungen präsentiert:
- Ein Anruf über die externe Leitung „Für den Test“ um 05:55:52 von der Nummer 89295671458 zur Nummer 89999999999.
- Um 05:55:53 sendet die externe Leitung den Anruf an das Eingangs-Schema „test»
- Während des Anrufprozesses wird das Modul „Anrufmanager“ aufgerufen, in dem der Anruf 16 Sekunden lang bleibt. Dies ist ein speziell für den Kunden entwickeltes Modul.
- Das Modul „Anrufmanager“ leitet den Anruf an den für die Nummer (Kunden) zuständigen Mitarbeiter „Manager1“ weiter und wartet 5 Sekunden auf eine Antwort. Der Manager hat nicht geantwortet.
- Das Modul „Anrufmanager“ leitet den Anruf an die Gruppe „KORP-Manager“ weiter. Dies sind andere Manager derselben Richtung (sitzen im gleichen Raum) und warten 11 Sekunden auf eine Antwort.
- Die Gruppe „KORP-Manager“ ruft die Mitarbeiter „Manager1, Manager2, Manager3“ gleichzeitig für 11 Sekunden an. Es gibt keine Antwort.
- Der Anrufmanager wird beendet. Das Schema leitet den Anruf an das Modul „Routenwahl aus 1s“ weiter. Auch ein speziell für den Kunden entwickeltes Modul. Hier wurde der Anruf 0 Sekunden lang verarbeitet.
- Das Schema leitet den Anruf an das Sprachmenü „Basis mit Nachwahl“ weiter. Der Kunde hat dort 31 Sekunden gewartet, es gab keine Nachwahl.
- Das Schema leitet den Anruf an die Gruppe „Sekretäre“ weiter, wo der Kunde 12 Sekunden gewartet hat.
- In der Gruppe werden gleichzeitig 2 Mitarbeiter „Sekretär1» und «Sekretär2“ aufgerufen, und nach 12 Sekunden antwortet der Mitarbeiter „Sekretär2“. Die Antwort auf den Anruf wird in die übergeordneten Anrufe dupliziert. Es stellt sich heraus, dass auch in der Gruppe „Sekretär2“ geantwortet hat, als das Schema aufgerufen wurde, hat „Sekretär2» und bei einem Anruf über die externe Leitung antwortete «Sekretär2».
Die Speicherung von Informationen über jede Operation und deren Verschachtelung ermöglicht es, Berichte einfach zu erstellen. Der Bericht über das Sprachmenü hilft herauszufinden, wie es hilft oder hinderlich ist. Ein Bericht über die von den Mitarbeitern verpassten Anrufe kann erstellt werden, wobei berücksichtigt wird, dass der Anruf von jemand anderem angenommen wurde und somit nicht als verpasst gilt, sowie dass es sich um einen Gruppenanruf handelte, und jemand anderes ihn früher angenommen hat, was ebenfalls bedeutet, dass es kein verpasster Anruf war.
Eine solche Speicherung von Informationen ermöglicht es, jede Gruppe einzeln zu betrachten und zu bestimmen, wie effektiv sie arbeitet, und ein Diagramm der angenommenen und verpassten Anrufe der Gruppe nach Stunden zu erstellen. Außerdem kann überprüft werden, wie gut die Verbindung zum zuständigen Manager funktioniert, indem die Übersetzungen nach der Verbindung mit dem Manager analysiert werden.
Man kann auch recht untypische Forschung betreiben, zum Beispiel wie oft Nummern, die nicht in der Datenbank sind, die richtige Durchwahl wählen oder wie viel Prozent der ausgehenden Anrufe eine Weiterleitung auf das Mobiltelefon darstellen.
Was ist das Ergebnis?
Für die Wartung von AMS ist kein Spezialist erforderlich, ein gewöhnlicher Administrator kann dies bewältigen – in der Praxis erprobt.
Für Anpassungen sind keine Fachkräfte mit ernsthaften Qualifikationen notwendig, es genügt PHP-Kenntnisse, da bereits Module für das SIP-Protokoll, die Warteschlange, den Mitarbeiteranruf und andere existieren. Es gibt eine Wrapper-Klasse für Asterisk. Ein Programmierer zur Entwicklung des Moduls kann (und sollte idealerweise) bereits vorhandene Module aufrufen. Und Kenntnisse Asterisk sind völlig unnötig, wenn der Kunde darum bittet, eine Seite mit einem neuen Bericht hinzuzufügen. Die Praxis zeigt jedoch, dass externe Programmierer, auch wenn sie es schaffen, sich ohne Dokumentation und angemessene Kommentare unsicher fühlen, weshalb es noch Entwicklungspotential gibt.
Module können:
- neue Möglichkeiten zur Verarbeitung von Anrufen schaffen,
- neue Blöcke in der Weboberfläche hinzufügen,
- von jedem bestehenden Modul erben, Funktionen überschreiben und es ersetzen oder einfach eine leicht modifizierte Kopie sein,
- eigene Einstellungen in die Einstellungsvorlage anderer Module einfügen und vieles mehr.
ATS-Einstellungen über die API. Wie oben beschrieben, werden alle Einstellungen in der Datenbank gespeichert und zum Zeitpunkt des Zugriffs gelesen, sodass alle ATS-Einstellungen über die API geändert werden können. Bei einem API-Aufruf wird die Konfiguration nicht neu erstellt und die Module werden nicht neu gestartet, sodass es irrelevant ist, wie viele Einstellungen und Mitarbeiter Sie haben. API-Anfragen werden schnell ausgeführt und blockieren sich nicht gegenseitig.
Die ATS speichert alle wichtigen Operationen mit Anrufen, einschließlich Warte- und Gesprächsdauer sowie Verschachtelungen und in den Begriffen der ATS (Mitarbeiter, Gruppe, externe Leitung und nicht Kanal, Nummer). Dies ermöglicht die Erstellung verschiedener Berichte für spezifische Kunden, wobei der Großteil der Arbeit darin besteht, eine benutzerfreundliche Oberfläche zu schaffen.
Was als Nächstes kommt, wird die Zeit zeigen. Es gibt noch viele Feinheiten, die überarbeitet werden müssen, und viele Pläne, aber seit der Erstellung der dritten Version ist ein Jahr vergangen, und man kann bereits sagen, dass die Idee funktioniert. Der Hauptnachteil der dritten Version sind die Hardware-Ressourcen, aber für den Komfort der Entwicklung muss man normalerweise immer diesen Preis zahlen.
Quelle: habr.com
