The history of one project or how I created a PBX based on Asterisk and Php for 7 years

Surely many of you, like me, had an idea to do something unique. In this article, I will describe the technical problems and solutions that I encountered in the development of PBX. Perhaps this will help someone to decide on their idea, and someone to follow the beaten path, because I also used the experience of the pioneers.

The history of one project or how I created a PBX based on Asterisk and Php for 7 years

Idea and key requirements

And it all started corny with love for Asterisk (framework for building communication applications), telephony automation and installations freepbx (web interface for Asterisk). If the needs of the company were without features and fit into the possibilities freepbx - everything is super. The entire installation took place in a day, the company received a customized PBX, a user-friendly interface and a short training plus optional support.

But the most interesting tasks were non-standard and then it was not so fabulous. Asterisk can do a lot, but to keep the web interface in working order, you had to spend many times more time. So a small detail could take much more time than installing the rest of the PBX. And it's not that it takes a long time to write a web interface, but rather it's a matter of architecture features freepbx. Approaches and methods of architecture freepbx was laid down in the days of php4, and at that moment there was already php5.6 on which everything could be made easier and more convenient.

The last straw was graphical dialplans in the form of a diagram. When I tried to build something like this for freepbx, I realized that I would have to significantly rewrite it and it would be easier to build something new.

The key requirements were:

  • simple setup, intuitive even for a novice administrator. Thus, companies do not need PBX service on our side,
  • easy revision so that tasks are solved in adequate time,
  • ease of integration with PBX. At freepbx there was no API to change settings, i.e. you cannot, for example, create groups or voice menus from a third-party application, only the API of the Asterisk,
  • opensource - for programmers, this is extremely important for customization.

The idea for faster development was to have all the functionality in modules as objects. All objects had to have a common parent class, which means that the names of all the main functions are already known and, therefore, there are already default implementations. Objects will allow you to drastically reduce the number of arguments in the form of associative arrays with string keys, which you can find out in freepbx It was possible by examining the entire function and nested functions. In the case of objects, a banal auto-completion will show all the properties, and in general, it will simplify life many times over. Plus, inheritance and redefinition already closes a lot of problems with improvements.

The next thing that slowed down development time and that should have been avoided was duplication. If there is a module responsible for dialing an employee, then all other modules that need to send a call to the employee should use it, and not create their own copies. So, if you need to change something, then you will have to change only in one place and the search for “how it works” should be carried out in one place, and not search the entire project.

First version and first errors

The first prototype was ready in a year. The entire PBX, as planned, was modular, and the modules could not only add new call processing functionality, but also change the web interface itself.

The history of one project or how I created a PBX based on Asterisk and Php for 7 years
Yes, the idea of ​​building a dialplan in the form of such a scheme is not mine, but it is very convenient and I did the same for Asterisk.

The history of one project or how I created a PBX based on Asterisk and Php for 7 years

By writing a module, programmers could already:

  • create your own functionality for handling the call, which could be placed on the diagram, as well as in the menu of elements on the left,
  • create your own pages for the web interface and add your own templates to existing pages (if the page developer has provided for this),
  • add your settings to the main settings tab or create your own settings tab,
  • the programmer can inherit from an existing module, change some of the functionality and register under a new name, or replace the original module.

For example, this is how you can create your own voice menu:

......
class CPBX_MYIVR extends CPBX_IVR
{
 function __construct()
 {
 parent::__construct();
 $this->_module = "myivr";
 }
}
.....
$myIvrModule = new CPBX_MYIVR();
CPBXEngine::getInstance()->registerModule($myIvrModule,__DIR__); //Зарегистрировать новый модуль
CPBXEngine::getInstance()->registerModuleExtension($myIvrModule,'ivr',__DIR__); //Подменить существующий модуль

The first complex implementations brought the first pride and the first disappointments. I was glad that it worked, that I was already able to reproduce the main features freepbx. I was glad that people liked the idea of ​​\uXNUMXb\uXNUMXbthe scheme. There were many more options to simplify development, but even at that time some of the tasks were already being made easier.

The disappointment was the API for changing the configuration of the PBX - it turned out not at all what we wanted. I took the same principle as in freepbx, by clicking the Apply button, the entire configuration is recreated and the modules are restarted.

It looks like this:

The history of one project or how I created a PBX based on Asterisk and Php for 7 years
*Dialplan is a rule (algorithm) according to which the call is processed.

But with this option, it is impossible to write a normal API to change the PBX settings. First, the operation of applying changes to Asterisk too long and resource intensive.
Secondly, you cannot call two functions at the same time, because both will create a configuration.
Thirdly, it applies all settings, including those made by the administrator.

In this version, as in Askozia, it was possible to generate the configuration of only changed modules and restart only the necessary modules, but these are all half measures. It was necessary to change the approach.

Second version. Nose pulled tail stuck

The idea to solve the problem was not to recreate the configuration and dialplan for Asterisk, but save information to the database and read from the database directly during call processing. Asterisk already knew how to read configurations from the database, it is enough to change the value in the database and the next call will already be processed taking into account the changes, and the function REALTIME_HASH.

In the end, I didn't even need to restart. Asterisk when changing the settings and all settings began to be applied immediately to Asterisk.

The history of one project or how I created a PBX based on Asterisk and Php for 7 years

The only changes to the dialplan are the addition of extensions and hints. But these were small point changes.

exten=>101,1,GoSub(‘sub-callusers’,s,1(1)); - точечное изменение, добавляется/изменяется через ami

; sub-callusers – универсальная функция генерится при установке модуля.
[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)
...

You can easily add or change a line in the dialplan through To me (control interface Asterisk) and reloading the entire dialplan is not required.

This solved the problem with the configuration API. It was even possible to directly enter the database and add a new group or change, for example, the dialing time in the “dialtime” field of the group and the next call will already last the specified time (This is not a recommendation for action, because some API operations require To me calls).

The first complex implementations again brought the first pride and disappointment. Pleased that it works. The database became a critical link, the dependence on the disk grew, there were more risks, but everything worked stably and without problems. And most importantly, now everything that could be done through the web interface could be done through the API and the same methods were used. Additionally, the web interface got rid of the "apply settings to PBX" button, which administrators often forgot about.

The frustration was the complexity of development. Since the first version, the php language generates a dialplan in the language Asterisk and it looks completely unreadable, plus the language itself Asterisk for writing a dialplan is extremely primitive.

How it looked:

$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 the second version, the dialplan became universal, it included all possible processing options depending on the parameters, and its size increased significantly. All this greatly slowed down the development time, and the very idea that once again it was necessary to intervene in the dialplan made me sad.

The third version

The idea for solving the problem was not to generate Asterisk dialplan from php and use FastAGI and write all processing rules already in php itself. FastAGI Allows Asterisk, to handle the call, connect to the socket. Receive commands from there and send results. Thus, the dialplan logic is already out of bounds Asterisk and can be written in any language, in my case php.

There was a lot of trial and error here. The main problem was that I already had a lot of classes/files. It took about 1,5 seconds to create objects, initialize and mutually register between them, and this delay per call is not something that can be ignored.

Initialization was supposed to be only 1 time and therefore the search for a solution began with writing a service in php using Pthreads. After a week of experimentation, this option was shelved due to the intricacies of how this extension works. After a month of tests, I also had to abandon asynchronous programming in php, I needed something simple, familiar to any php beginner, and many extensions for php are synchronous.

The solution was my own multi-threaded service in 'C', which was compiled with PHPLIB. It loads all ATS php files, waits for all modules to be initialized, add a callback to each other, and when everything is ready, it caches. When requested by FastAGI a stream is created, a copy from the cache of all classes and data is reproduced in it, and the request is passed to the php function.

With this solution, the time from sending a call to our service to the first command Asterisk decreased from 1,5s to 0,05s, and this time is weakly dependent on the size of the project.

The history of one project or how I created a PBX based on Asterisk and Php for 7 years

As a result, the time for the development of the dialplan was reduced significantly, and I can appreciate this because I had to rewrite the entire dialplan of all modules in php. Firstly, methods for obtaining an object from the database should already be written in php, they were needed to be displayed in the web interface, and secondly, and this is the main thing, it finally became possible to conveniently work with strings with numbers with arrays with database plus many php extensions.

To process the dialplan in the module class, you need to implement the function dialplanDynamicCall and argument pbxCallRequest will contain an object to interact with Asterisk.

The history of one project or how I created a PBX based on Asterisk and Php for 7 years

In addition, it became possible to debug the dialplan (php has xdebug and it works for our service), you can move step by step looking at the values ​​of variables.

Call data

Any analytics and reports require correctly collected data, and this PBX block also went through a lot of trial and error from the first to the third version. Often, call data is a plate. One call = one record: who called, who answered, how much they talked. In more interesting options, there is also an additional sign, which of the PBX employees called during the call. But all this covers only part of the needs.

The initial requirements were:

  • save not only whom the PBX called, but also who answered, because there are interceptions and this will need to be taken into account when analyzing calls,
  • time before connecting with the employee. In freepbx and some other PBXs, the call is considered answered as soon as the PBX picks up the phone. But for the voice menu, you already need to pick up the phone, so all calls become answered and the response time becomes 0-1 second. Therefore, it was decided to save not only the time until the answer, but the time until the connection with the key modules (the module itself sets this flag for itself. Now it is “Employee”, “External Line”),
  • for a more complex dialplan, when a call walks between different groups, it was necessary to be able to examine each element separately.

The best option turned out to be the option when the PBX modules themselves send information about themselves by calls and, as a result, save the information in the form of a tree.

It looks like this:

To begin with, general information about the call (like everyone else - nothing special).

The history of one project or how I created a PBX based on Asterisk and Php for 7 years

  1. Received a call on an outside lineFor the dough” at 05:55:52 from the number 89295671458 to the number 89999999999, as a result, the employee answered it “2 Secretary” with number 104. The client waited 60 seconds and talked for 36 seconds.
  2. Employee "2 Secretary» makes a call to number 112 and an employee answers it «Manager1» after 8 seconds. They talk for 14 seconds.
  3. The client is transferred to the Employee "manager1» where they continue to talk for another 13 seconds

But this is the tip of the iceberg, for each entry you can get a detailed call through the PBX.

The history of one project or how I created a PBX based on Asterisk and Php for 7 years

All information is presented as nested calls:

  1. Received a call on an outside lineFor the dough» at 05:55:52 from the number 89295671458 to the number 89999999999.
  2. At 05:55:53, the external line sends a call to the Incoming scheme "test»
  3. During call processing according to the scheme, the module "manager call”, in which the call is 16 seconds. This is a custom built module.
  4. Module "manager call» sends a call to the employee responsible for the number (client) «Manager1” and waits for a response for 5 seconds. The manager didn't answer.
  5. Module "manager call» sends a call to the group «CORP managers". These are other managers of the same direction (sitting in the same room) and waiting for an answer for 11 seconds.
  6. Group "CORP managers» calling employees «Manager1, Manager2, Manager3» at the same time for 11 seconds. No answer.
  7. The call to the manager ends. And the circuit call sends to the module "Route selection from 1s". Also a module written for the client. Here the call was processed for 0 seconds.
  8. The circuit sends a call to the voice menu "Basic with additional set". The client waited 31 seconds in it, there was no additional dialing.
  9. The scheme sends a call to the Group "Secretaries”, where the client waited 12 seconds.
  10. In a group, 2 employees are called at the same time "1 Secretary" and "2 Secretary"and after 12 seconds the employee answers"2 Secretary". The answer to the call is duplicated in the parent calls. It turns out and in the group answered “2 Secretary”, when calling the scheme, answered “2 Secretary” and answered the call on the external line “2 Secretary».

It is the preservation of information about each operation and their nesting that will make it easy to make reports. The voice menu report will help you find out how much it helps or hinders. Build a report on calls missed by employees, taking into account that the call was intercepted and, therefore, is not considered missed, and taking into account that it was a group call, and someone else took it earlier, which means that the call is also not missed.

Such storage of information will allow you to take each group separately and determine how efficiently it works, build a graph of answered and missed groups by the hour. You can also check the guesswork of the connection with the responsible manager by analyzing the transfers after connecting with the manager.

In particular, it is possible to conduct rather atypical studies, for example, how often numbers that are not in the database dial the correct extension or what percentage of outgoing calls are forwarded to mobile.

The result?

To maintain the PBX, a specialist is not required, the most ordinary administrator can handle this - it has been tested in practice.

For improvements, specialists with serious qualifications are not needed; knowledge of php is enough, tk. modules have already been written for the sip protocol, and for the queue, and for calling an employee, and others. There is a wrapper class for Asterisk. To develop a module, a programmer can (and in a good way should) call ready-made modules. And knowledge Asterisk are completely unnecessary if the client asks to add a page with some new report. But practice shows that although third-party programmers can cope, they feel insecure without documentation and normal coverage with comments, so there is still room to move.

Modules can:

  • create new call processing capabilities,
  • add new blocks to the web interface,
  • inherit from any of the existing modules, redefine functions and replace it, or just be a slightly modified copy,
  • add your settings to the settings template of other modules and much more.

PBX settings via API. As described above, all settings are stored in the database and read at the time of the call, so you can change all PBX settings through the API. When calling the API, the configuration is not recreated and the modules are not restarted, therefore, it does not matter how many settings and employees you have. API requests are fast and do not block each other.

The PBX saves all key call operations with durations (wait/talk), nestings and in PBX terms (employee, group, outside line, not channel, number). This allows you to build various reports for specific clients and most of the work is to make a user-friendly interface.

What will happen next time will tell. There are still many nuances that should be redone, there are still many plans, but a year has passed since the creation of the 3rd version and we can already say that the idea is working. The main disadvantage of the 3rd version is hardware resources, but for the convenience of development, this is usually always exactly what you have to pay.

Source: habr.com

Add a comment