0,0
рейтинг
20 марта 2015 в 20:05

Разработка → Пишем обработку Asterisk AMI своими руками. Часть первая: создаем класс на PHP для обращения к звездочке или как сделать php socket клиент своими руками

Вместо предисловия


Приветствую сообщество!

Моя первая статья Пример реализации обращения к Asterisk CLI на PHP. Структуризация ответа звездочки наконец то перешла из модерации в песочницу и модератор сразу дал мне полные права для публикации на хабре.

Я хочу начать цикл статей о том как можно реализовать собственное API для обращения с Астериском через AMI. Если у Вас имеются время, желание и самое главное опыт, поддержать меня и попытаться составить команду которая на данном портале создаст полноценное php API для взаимодействия с AMI Астериска велком к диалогу.

Задача очень интересная так как многие сервисы посвященные Звездочке сразу прячут всю информацию о попытках разработки собственных интерфейсов для Asterisk. Хотя мой опыт который я здесь изложу может стать для многих «коддеров» попыткой потешить свое «эго», но вариантов создать собственное веб приложение, которое не будет глючить каждую секунду не даст (правда если вы виртуоз языка javascript то очень даже получится какой нибудь грамотный заменитель буржуйской веб-морды для астера).

В этой части я покажу что написать собственный класс для обращения к AMI звездочки проще простого, главное идти от простого к сложному.

Для особо ленивых в конце статьи готовый (рабочий) модуль с классом Asterisk AMI. С примерами запросов. Класс полностью рабочий и готов к использованию.

Матчасть


Я решил реализовать механизм работы сокета на fsockopen.

Прежде чем начать коддинг маленький ликбез.

Функции и ключевые слова php встречающиеся в коде
.
__destruct
Деструктор класса. Основная задача деструктора, при уничтожении экземпляра класса (при любом варианте даже при аварийном завершении работы скрипта) произвести действия заданные пользователем.

Пример:
function __destruct()
    {
       echo "Завершение работы";
       // другие действия определенные пользователем.
    }


count
Возвращает количество элементов в массиве.
Пример:
var $a = array ("a","b","c");
echo count($a);
// count вернет 3 (количество элементов)


die
Завершение работы скрипта в данной точке и вывод строки. Обычно используется для остановки скрипта в теле скрипта.
Пример:
die ("Завершение скрипта");
// скрипт запершит работу после вывода строки: "Завершение скрипта"


explode
Разбивает строку на массив используя маркер.
Пример:
$a = explode(" ","Маша любит быть наездницей");
// Результат: $a[0]="Маша", $a[1]="любит", $a[2]="быть", $a[3]="наездницей"


fclose
Закрывает дескриптор связанный с файлом/сокетом.
Пример:
fclose($a);
// Закрывает соединение с файлом/сокетом связанным с $a дескриптором


fread
Читает данные из файла/сокета.
Пример:
$b = fread($a,100);
// Прочитает 100 байтов из файла/сокета связанного с $a дескриптором


fsockopen
Открывает соединение с сокетом.
Пример:
$m= fsockopen(host, port,$a,$b,2);
// $m будет содержать дескриптор(ссылку) на открытый сокет или false если соединение не получилось установить.


function
Объявление функции в php.
Пример:
function example()
{
echo "function";
}
// Объявит функцию example


fwrite
Запись в файл/сокет, используя бинарно-безопасный режим.
Пример:
fwrite($a, "Строка для записи");
// Запишет в файл/сокет с дескриптором $a строку "Строка для записи"


public
Объявление переменных, констант, функций внутри класса общедоступными. Ко всему, что объявлено словом public, можно будет обратиться из основного кода.
Пример:
class TempClass
{
    public $a = 'Здесь строка к которой можно обратиться вне класса';
}
$b = new TempClass();
echo $b->a;
// Выведет строку 'Здесь строка к которой можно обратиться вне класса


rand
Выдает случайное целое число. Если заданы максимальное и минимальное значение, то число будет находиться внутри заданного диапазона.
Пример:
echo rand(100,999);
// Выведет случайное число между 100 и 999


return
Генерирует выход из функции. Если после return стоит значение/переменная/любой объект, то функция возвращает данную сущность.
Пример:
function ad ()
{
return 5;
}
echo ad();
// Выведет 5


set_time_limit
Задает максимальное время выполнения скрипта в секундах. Если в параметре указано число 0, то максимальное время сбрасывается и скрипт может выполняться вечно, пока алгоритм внутри него не закончит свое выполнение.
Пример:
set_time_limit (10);
echo ad();
// Максимальное время работы будет 5 секунд.


sleep
Приостанавливает выполнение скрипта в секундах.
Пример:
sleep (10);
echo "Не бойся, у тебя все получится";
// Задержит время выполнения вывода строки "Не бойся, у тебя все получится" на 10 секунд.


socket_get_status
Извлекает метаданные из сокета
Пример:
socket_get_status ($a);
// Получим метаданные(свойства) сокета связанного с дескриптором $a.


stream_set_timeout
Устанавливает значение таймаута для сокета
Пример:
stream_set_timeout ($a,0,1000);
// Установит таймауты у сокета связанного с дескриптором $a.


trim
Удаляет пробелы вначале и конце строки.
Пример:
echo trim ("      Маша любит      на Саше             ");
// Выведет строку "Маша любит      на Саше";


var_dump
Выдает всю структуру объекта
Пример:
var_dump(5);
// Выведет int(5);


->
Обращение к объектам определенных внутри класса
Пример:
class TempClass
{
    public $a = 'Маше нравится темперамент Саши';
}
$b = new TempClass();
echo $b->a;
// Выведет строку 'Маше нравится темперамент Саши'


* Пояснения составлены лично мной, не являются энциклопедическими данными или стандартами, и служат лишь для пояснения кода на данной странице. Если Вас не устраивает мое описание просьба отписаться в комментах. В случае если Ваше описание будет более ясно описывать приведенные выше понятия для объяснения кода примеров,то я внесу в статью изменения. Корректировки будут указаны с Вашим авторством.


Получаем логин и пароль для Asterisk AMI используя WinSCP
1. Качаем с официального сайта программу WinSCP



2. Запускаем программу и заходим на сервер заполняя поля указанные красным маркером.



Нажимаем «да» на все вопросы и если потребуется заново вводим пароль.

3. Далее ищем файл manager.conf. Стандартное расположение у файла /etc/asterisk/.



Открываем файл двойным щелчком мышки.

4. Внутри файла находим секцию [admin]



Записываем логин и пароль от Вашего Астериск AMI. В моем случае это admin и amp11

Учимся подключаться через SSH тунель к Asterisk AMI используя putty
1. Загружаем putty с официального сайта



2. Запускаем putty. Выделенное красным на рисунке отмечаем. В строке выделенной красным с надписью 1, пишем IP адрес сервера Asterisk. SSH протокол по которому будет работать putty. Never запретит putty закрываться в случае разрыва соединения.



3. На приглашение системы вводим пользователя(обычно это рут) и нажимаем Enter.



4. Вводим пароль и нажимаем Enter.



5. Если авторизация прошла успешно, получаем примерно такой экран.



6. Вводим: telnet 127.0.0.1 5038 и получаем приглашение Астериска: Asterisk Call Manager



7. Вводим следующие строки:
Action: Login // Нажимаем Enter
Username: Имя_которое_мы_нашли_через_WinSCP // Нажимаем Enter
Secret: Пароль_который_мы_нашли_через_WinSCP // Нажимаем 2 раза Enter



Получаем сообщение Астериска: Message: Authentication accepted

8. Вводим следующие строки:
Action: Logoff // Нажимаем 2 раза Enter



Получаем сообщение системы
Response: Goodbye
Message: Thanks for all the fish.

* Вот мы научились подключаться через putty по SSH к AMI Астериска


Определение обязательного функционала класса


Составим таблицу, чтобы понять какой функционал необходимо добавлять в класс.
Задача. Необходимость. Функционал класса.
Первоначальная инициализация переменных класса. Обязательное условие. Конструктор __construct();
Глобальный массив $this->ini в котором мы будем хранить все переменные класса.
Закрытие класса. Обязательное условие. Деструктор __destruct();
Соединение с сокетом. Обязательное условие. Функция connect();
Разъединение с сокетом Обязательное условие. Функция disconnect();
Авторизация в AsterIsk AMI. Обязательное условие. Функция init();
Запись в сокет. Обязательное условие. Функция write();
Считывание из сокета синхронно. Не обязательное условие. Функция read_syn();
Считывание из сокета асинхронно. Обязательное условие. Функция read();


Это грубое ТЗ которое позволит написать класс для взаимодействия через PHP с Asterisk AMI.

Коддинг


Регламент написания кода в классе для внесения ясности при его чтении.


При написании данного класса я использовал следующий регламент:
  • $i, $ii,$iii,$iiii — все начинающиеся с $i переменные являются переменными цикла for
  • $s, $ss, $sss, $ssss — все начинающиеся с $s переменные являются строками
  • $m, $mm, $mmm, $mmmm — все начинающиеся с $m переменные являются массивами
  • Каждый уровень вложенности кода отступает от родительского на четыре пробела.
  • Каждая переменная объявленная в функции и не являющейся глобальной, обязательно требует прямого уничтожения функцией unset для того чтобы демоны на PHP не кушали память.


__construct(),__destruct() и $this->ini



Основная задача конструктора объявить все переменные которые будут использоваться в классе. Все переменные у нас будут храниться в массиве $this->ini.

<?php
class Asterisk_ami
{
    /*  Переменные класса     */
    public $ini = array();

    /*  Первоначальная инициализация переменных класса.    */
    function __construct ()
    {
        /* Настройки класса по умолчанию */
        $this->ini["con"] = false;              /* ссылка на соединение */
    }
    /*  Деструктор класса  */
    function __destruct()
    {
        unset ($this->ini);  /* Так как мы во всем классе использовали всего одну переменную $this->ini то и уничтожать будем только ее одну. Некоторые критики могут сказать что php сам может почистить память за классом, и я с этим соглашусь почти во всех случаях кроме как использования памяти в демонах */
    }
}
?>


Остальные переменные в данный массив мы будем добавлять по мере необходимости, и будем детально рассматривать каждый элемент по функциональности.

connect() и disconnect()


Для соединения с сокетом я решил использовать функцию fsockopen.

fsockopen(host, port,$a,$b,$c);
где
host - IP адрес хотса к которому мы будем соединяться,
port - порт сервера на который будет идти соединение,
$a - номер ошибки в случае неудачного соединения,
$b - текстовое описание ошибки в случае неудачного соединения,
$c - таймаут сокета(очень интересный параметр, но вообще не работающий параметр).


Из описания функции мы можем понять, что нам нужно ввести как минимум 2 переменные в глобальный массив. Это IP адрес сервера и порт для коннекта. Так как $a,$b возвращаемые значения (а мы не справочное бюро чтобы хранить справки) то их мы проигнорируем. А $c параметр вообще не работает, поэтому держать его в памяти не будем.

Почему я пишу что не работает? Потому что это реальный опыт работы данной функции в демонах, которым глубоко по
барабану на собственные же спецификации PHP.


Для двух новых переменных введем 2 элемента массива:

$this->ini["host"] = "127.0.0.1";       /* IP сервера */
$this->ini["port"] = "5038";              /* Порт для соединения */


Напишем функцию:

<?php
class Asterisk_ami
{
    /*  Переменные класса     */
    public $ini = array();

    /*  Первоначальная инициализация переменных класса.    */
    function __construct ()
    {
        /* Настройки класса по умолчанию */
        $this->ini["con"] = false;              /* ссылка на соединение */
        $this->ini["host"] = "127.0.0.1";       /* IP сервера */
        $this->ini["port"] = "5038";            /* Порт для соединения */
    }
    /*  Деструктор класса  */
    function __destruct()
    {
        unset ($this->ini);  /* Так как мы во всем классе использовали всего одну переменную $this->ini то и уничтожать будем только ее одну. Некоторые критики могут сказать что php сам может почистить память за классом, и я с этим соглашусь почти во всех случаях кроме как использования памяти в демонах */
    }
    /*  Функция для поднятия сокета  */
    function connect()
    {
        $this->ini["con"] = fsockopen($this->ini["host"], $this->ini["port"],$a,$b,10);
        if ($this->ini["con"])
        {
            stream_set_timeout($this->ini["con"], 0, 400000);         /* Вот честно я понятия не имею почему данная функция работает с этим сокетом, но без нее асинхронность не получиться и у нас будет сыпаться обычный мусор, или сокет будет вообще спать, а вместе с ним и класс */
        }
    }
    /*  Функция для закрытия сокета  */
    function connect()
    {
        if ($this->ini["con"])    /* Если есть соединение */
        {
            fclose($this->ini["con"]);  /* То мы закрываем сокет */
        }
    }

}
?>

write();


Для работы с Астериск АМИ мы должны записывать в сокет пакеты строк, в ответ на которые мы будем также получать строки о статусе наших запросов. Для записи мы будем использовать php функцию fwrite.При этом в с каждым пакетом мы можем отправить индикатор ActionID нашего запроса, чтобы получив пакеты информации от Астериска мы могли классифицировать это пакеты по отправленным заранее событиям.
    /* Функция для записи в сокет */
    function write($a)      /* $a это строка которую мы должны записать в сокет */
    {
        $m = rand (10000000000000000,99999999900000000);        /* Рандомое число, для генерации рандомного ActionID */
        fwrite($this->con, "ActionID: $this->action_id$m\r\n$a\r\n\r\n"); /* Записываем в сокет */
        $this->sleepi();        /* Суть этой фунции я расскажу в следующем параграфе. */
        return $m;     /* Возвращаем ActionID */
    }


sleepi();


Каждый раз, когда наш скрипт отправляет пакет строк в сокет, возвращается для исполнения следующего кода. Проследим что же происходит с нашим пакетом. Наш пакет попадает в очередь для чтения со стороны сервера контролирующего сокет. Сервер в свою очередь в данный момент может быть занят другой операцией, и прочитает наши строки немного позже. В связи с этим, по моему мнению, лучше дать небольшое время для того, чтобы сервер успел прочитать наши строки и начать обработку нашего пакета. Для этого я ввел в класс функцию, которая будет заставлять скрипт засыпать на некоторое время.
class Asterisk_ami
{

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    function __construct ()
    {
        /* Настройки класса по умолчанию */
        $this->ini["con"] = false;              /* ссылка на соединение */
        $this->ini["host"] = "127.0.0.1";       /* IP сервера */
        $this->ini["port"] = "5038";            /* Порт для соединения */
        $this->ini["sleep_time"]=1500;          /* В демонах эта цифра намного меньше, в моем тестовом сервере, всего "5" */
    }


~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    /*  Задает стандартную паузу между функциями записи и чтения    */
    function sleepi ()
    {
        sleep($this->ini["sleep_time"]);        /* Скрипт будет засыпать на заданное время */
    }
}

init() и read();


Здесь все просто. Смотрим код.

    /*  Считывает данные из сокета  */
    function read()
    {
        $mm = array();
        $b = array();
        $k = 0;
        $s = "";
        $this->sleepi();
        do
        {
            $s.= fread($this->con,1024);
            sleep($this->read_sleep_time);
            $mmm=socket_get_status($this->con);
        }   while ($mmm['unread_bytes']);
        $mm = explode ("\r\n",$s);
        for ($i=0;$i<count($mm);$i++)
        {
            if ($mm[$i]=="")
            {
                $k++;
            }
            $m = explode(":",$mm[$i]);
            if (isset($m[1]))
            {
                $this->ini["lastRead"][$k][trim($m[0])] = trim($m[1]);
            }
        }
        return $this->ini["lastRead"];
    }


В итоге мы получили класс для работы с Астериск AMI:

<?php
class Asterisk_ami
{
    public $ini = array();

    function __construct ()
    {
        $this->ini["con"] = false;
        $this->ini["host"] = "127.0.0.1";
        $this->ini["port"] = "5038";
        $this->ini["lastActionID"] = 0;
        $this->ini["lastRead"] = array ();
        $this->ini["sleep_time"]=1.5;
        $this->ini["login"] = "admin";
        $this->ini["password"] = "amp111";
    }

    function __destruct()
    {
        unset ($this->ini);
    }

    public function connect()
    {
        $this->ini["con"] = fsockopen($this->ini["host"], $this->ini["port"],$a,$b,10);
        if ($this->ini["con"])
        {
            stream_set_timeout($this->ini["con"], 0, 400000);
        }
    }

    public function disconnect()
    {
        if ($this->ini["con"])
        {
            fclose($this->ini["con"]);
        }
    }

    public function write($a)
    {
        $this->ini["lastActionID"] = rand (10000000000000000,99999999900000000);
        fwrite($this->ini["con"], "ActionID: ".$this->ini["lastActionID"]."\r\n$a\r\n\r\n");
        $this->sleepi();
        return $this->ini["lastActionID"];
    }

    public function sleepi ()
    {
        sleep($this->ini["sleep_time"]);
    }

    public function read()
    {
        $mm = array();
        $b = array();
        $k = 0;
        $s = "";
        $this->sleepi();
        do
        {
            $s.= fread($this->ini["con"],1024);
            sleep(0.005);
            $mmm=socket_get_status($this->ini["con"]);
        }   while ($mmm['unread_bytes']);
        $mm = explode ("\r\n",$s);
        $this->ini["lastRead"] = array();
        for ($i=0;$i<count($mm);$i++)
        {
            if ($mm[$i]=="")
            {
                $k++;
            }
            $m = explode(":",$mm[$i]);
            if (isset($m[1]))
            {
                $this->ini["lastRead"][$k][trim($m[0])] = trim($m[1]);
            }
        }
        unset ($b);
        unset ($k);
        unset ($mm);
        unset ($mm);
        unset ($mmm);
        unset ($i);
        unset ($s);
        return $this->ini["lastRead"];
    }

    public function init()
    {
    return $this->write("Action: Login\r\nUsername: ".$this->ini["login"]."\r\nSecret: ".$this->ini["login"]."\r\n\r\n");
    }
}

$a = new Asterisk_ami();
$a->connect();
if ($a->ini["con"])
{
    $a->init();
    $a->write("Action: ListCommands");
    var_dump($a->read());
    $a->disconnect();
}
unset($a);
?>


Результат работы скрипта
array(4) {
[0]=>
array(2) {
[«Response»]=>
string(7) «Success»
[«Message»]=>
string(23) «Authentication accepted»
}
[1]=>
array(3) {
[«Event»]=>
string(11) «FullyBooted»
[«Privilege»]=>
string(10) «system,all»
[«Status»]=>
string(12) «Fully Booted»
}
[2]=>
array(2) {
[«Response»]=>
string(7) «Success»
[«Events»]=>
string(2) «On»
}
[3]=>
array(115) {
[«Response»]=>
string(7) «Success»
[«ActionID»]=>
string(17) «67932087789661188»
[«WaitEvent»]=>
string(34) «Wait for an event to occur. (Priv»
[«DeviceStateList»]=>
string(44) «List the current known device states. (Priv»
[«PresenceStateList»]=>
string(46) «List the current known presence states. (Priv»
[«QueueReset»]=>
string(30) «Reset queue statistics. (Priv»
[«QueueReload»]=>
string(71) «Reload a queue, queues, or any sub-section of a queue or queues. (Priv»
[«QueueRule»]=>
string(19) «Queue Rules. (Priv»
[«QueueMemberRingInUse»]=>
string(50) «Set the ringinuse value for a queue member. (Priv»
[«QueuePenalty»]=>
string(42) «Set the penalty for a queue member. (Priv»
[«QueueLog»]=>
string(38) «Adds custom entry in queue_log. (Priv»
[«QueuePause»]=>
string(52) «Makes a queue member temporarily unavailable. (Priv»
[«QueueRemove»]=>
string(35) «Remove interface from queue. (Priv»
[«QueueAdd»]=>
string(30) «Add interface to queue. (Priv»
[«QueueSummary»]=>
string(26) «Show queue summary. (Priv»
[«QueueStatus»]=>
string(25) «Show queue status. (Priv»
[«Queues»]=>
string(14) «Queues. (Priv»
[«ControlPlayback»]=>
string(64) «Control the playback of a file being played to a channel. (Priv»
[«StopMixMonitor»]=>
string(86) «Stop recording a call through MixMonitor, and free the recording's file handle. (Priv»
[«MixMonitor»]=>
string(178) «Record a call and mix the audio during the recording. Use of StopMixMonitor is required to guarantee the audio file is available for processing during dialplan execution. (Priv»
[«MixMonitorMute»]=>
string(44) «Mute / unMute a Mixmonitor recording. (Priv»
[«VoicemailRefresh»]=>
string(51) «Tell Asterisk to poll mailboxes for a change (Priv»
[«VoicemailUsersList»]=>
string(43) «List All Voicemail User Information. (Priv»
[«PlayDTMF»]=>
string(46) «Play DTMF signal on a specific channel. (Priv»
[«MuteAudio»]=>
string(28) «Mute an audio stream. (Priv»
[«ConfbridgeSetSingleVideoSrc»]=>
string(94) «Set a conference user as the single video source distributed to all other participants. (Priv»
[«ConfbridgeStopRecord»]=>
string(46) «Stop recording a Confbridge conference. (Priv»
[«ConfbridgeStartRecord»]=>
string(47) «Start recording a Confbridge conference. (Priv»
[«ConfbridgeLock»]=>
string(36) «Lock a Confbridge conference. (Priv»
[«ConfbridgeUnlock»]=>
string(38) «Unlock a Confbridge conference. (Priv»
[«ConfbridgeKick»]=>
string(30) «Kick a Confbridge user. (Priv»
[«ConfbridgeUnmute»]=>
string(32) «Unmute a Confbridge user. (Priv»
[«ConfbridgeMute»]=>
string(30) «Mute a Confbridge user. (Priv»
[«ConfbridgeListRooms»]=>
string(31) «List active conferences. (Priv»
[«ConfbridgeList»]=>
string(41) «List participants in a conference. (Priv»
[«MeetmeListRooms»]=>
string(31) «List active conferences. (Priv»
[«MeetmeList»]=>
string(41) «List participants in a conference. (Priv»
[«MeetmeUnmute»]=>
string(28) «Unmute a Meetme user. (Priv»
[«MeetmeMute»]=>
string(26) «Mute a Meetme user. (Priv»
[«PJSIPNotify»]=>
string(63) «Send a NOTIFY to either an endpoint or an arbitrary URI. (Priv»
[«PJSIPShowRegistrationsOutbound»]=>
string(42) «Lists PJSIP outbound registrations. (Priv»
[«PJSIPUnregister»]=>
string(43) «Unregister an outbound registration. (Priv»
[«PJSIPShowRegistrationsInbound»]=>
string(41) «Lists PJSIP inbound registrations. (Priv»
[«PRIDebugFileUnset»]=>
string(50) «Disables file output for PRI debug messages (Priv»
[«PRIDebugFileSet»]=>
string(53) «Set the file used for PRI debug message output (Priv»
[«PRIDebugSet»]=>
string(38) «Set PRI debug levels for a span (Priv»
[«PRIShowSpans»]=>
string(32) «Show status of PRI spans. (Priv»
[«DAHDIRestart»]=>
string(55) «Fully Restart DAHDI channels (terminates calls). (Priv»
[«DAHDIShowChannels»]=>
string(37) «Show status of DAHDI channels. (Priv»
[«DAHDIDNDoff»]=>
string(54) «Toggle DAHDI channel Do Not Disturb status OFF. (Priv»
[«DAHDIDNDon»]=>
string(53) «Toggle DAHDI channel Do Not Disturb status ON. (Priv»
[«DAHDIDialOffhook»]=>
string(45) «Dial over DAHDI channel while offhook. (Priv»
[«DAHDIHangup»]=>
string(28) «Hangup DAHDI Channel. (Priv»
[«DAHDITransfer»]=>
string(30) «Transfer DAHDI Channel. (Priv»
[«SIPpeerstatus»]=>
string(54) «Show the status of one or all of the sip peers. (Priv»
[«SIPnotify»]=>
string(25) «Send a SIP notify. (Priv»
[«SIPshowregistry»]=>
string(44) «Show SIP registrations (text format). (Priv»
[«SIPqualifypeer»]=>
string(25) «Qualify SIP peers. (Priv»
[«SIPshowpeer»]=>
string(35) «show SIP peer (text format). (Priv»
[«SIPpeers»]=>
string(36) «List SIP peers (text format). (Priv»
[«IAXregistry»]=>
string(30) «Show IAX registrations. (Priv»
[«IAXnetstats»]=>
string(25) «Show IAX Netstats. (Priv»
[«IAXpeerlist»]=>
string(22) «List IAX Peers. (Priv»
[«IAXpeers»]=>
string(22) «List IAX peers. (Priv»
[«Park»]=>
string(22) «Park a channel. (Priv»
[«ParkedCalls»]=>
string(25) «List parked calls. (Priv»
[«Parkinglots»]=>
string(33) «Get a list of parking lots (Priv»
[«FAXStats»]=>
string(35) «Responds with fax statistics (Priv»
[«FAXSession»]=>
string(67) «Responds with a detailed description of a single FAX session (Priv»
[«FAXSessions»]=>
string(32) «Lists active FAX sessions (Priv»
[«PJSIPShowResourceLists»]=>
string(55) «Displays settings for configured resource lists. (Priv»
[«PJSIPShowSubscriptionsOutbound»]=>
string(27) «Lists subscriptions. (Priv»
[«PJSIPShowSubscriptionsInbound»]=>
string(27) «Lists subscriptions. (Priv»
[«UnpauseMonitor»]=>
string(39) «Unpause monitoring of a channel. (Priv»
[«PauseMonitor»]=>
string(37) «Pause monitoring of a channel. (Priv»
[«ChangeMonitor»]=>
string(47) «Change monitoring filename of a channel. (Priv»
[«StopMonitor»]=>
string(33) «Stop monitoring a channel. (Priv»
[«Monitor»]=>
string(25) «Monitor a channel. (Priv»
[«PJSIPQualify»]=>
string(37) «Qualify a chan_pjsip endpoint. (Priv»
[«PJSIPShowEndpoint»]=>
string(53) «Detail listing of an endpoint and its objects. (Priv»
[«PJSIPShowEndpoints»]=>
string(29) «Lists PJSIP endpoints. (Priv»
[«BridgeKick»]=>
string(36) «Kick a channel from a bridge. (Priv»
[«BridgeDestroy»]=>
string(24) «Destroy a bridge. (Priv»
[«BridgeInfo»]=>
string(38) «Get information about a bridge. (Priv»
[«BridgeList»]=>
string(43) «Get a list of bridges in the system. (Priv»
[«BlindTransfer»]=>
string(57) «Blind transfer channel(s) to the given destination (Priv»
[«Filter»]=>
string(63) «Dynamically add filters for the current manager session. (Priv»
[«ModuleCheck»]=>
string(33) «Check if module is loaded. (Priv»
[«ModuleLoad»]=>
string(25) «Module management. (Priv»
[«CoreShowChannels»]=>
string(38) «List currently active channels. (Priv»
[«LoggerRotate»]=>
string(45) «Reload and rotate the Asterisk logger. (Priv»
[«Reload»]=>
string(27) «Send a reload event. (Priv»
[«CoreStatus»]=>
string(38) «Show PBX core status variables. (Priv»
[«CoreSettings»]=>
string(44) «Show PBX core settings (version etc). (Priv»
[«UserEvent»]=>
string(31) «Send an arbitrary event. (Priv»
[«UpdateConfig»]=>
string(34) «Update basic configuration. (Priv»
[«SendText»]=>
string(36) «Send text message to channel. (Priv»
[«ListCommands»]=>
string(39) «List available manager commands. (Priv»
[«MailboxCount»]=>
string(35) «Check Mailbox Message Count. (Priv»
[«MailboxStatus»]=>
string(21) «Check mailbox. (Priv»
[«AbsoluteTimeout»]=>
string(28) «Set absolute timeout. (Priv»
[«PresenceState»]=>
string(27) «Check Presence State (Priv»
[«ExtensionState»]=>
string(30) «Check Extension Status. (Priv»
[«Command»]=>
string(36) «Execute Asterisk CLI Command. (Priv»
[«Originate»]=>
string(24) «Originate a call. (Priv»
[«Atxfer»]=>
string(25) «Attended transfer. (Priv»
[«Redirect»]=>
string(34) «Redirect (transfer) a call. (Priv»
[«ListCategories»]=>
string(45) «List categories in configuration file. (Priv»
[«CreateConfig»]=>
string(60) «Creates an empty file in the configuration directory. (Priv»
[«Status»]=>
string(27) «List channel status. (Priv»
[«GetConfigJSON»]=>
string(44) «Retrieve configuration (JSON format). (Priv»
[«GetConfig»]=>
string(30) «Retrieve configuration. (Priv»
[«Getvar»]=>
string(49) «Gets a channel variable or function value. (Priv»
[«Setvar»]=>
string(49) «Sets a channel variable or function value. (Priv»
[«ShowDialPlan»]=>
string(22) «Show dialplan contexts»
}
}


Резюме


Создать собственный класс для работы с сокетами в асинхронном режиме не сложно. И не важно, будет ли это работа с Астериск АМИ или другим серверным приложением.

Отдельное спасибо HiNeX за ошибки найденные в промежуточном коде.

Изменил готовый класс в связи с рекомендациями AotD и RUgaleFF.

Спасибо всем кто дочитал. И до встречи на порталах ТМ.

(Если вы считаете что стоит продолжить, то какой из функционалов Астериск АМИ вы хотите, чтобы я показал в следующей части? Пишите в комментариях.)

Убедительная просьба для тех кто минусует статью, если Вам не трудно, пожалуйста, напишите причину.
Продолжить развивать данную тему?

Проголосовало 137 человек. Воздержалось 29 человек.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (20)

  • +2
    У вас $ini в конструкторах у некоторых примеров без $this, т.е. объявляются внутри функции.
    • –5
      Спасибо HiNeX. Не могу плюсануть моя карма не позволяет.
  • –3
    Ребята, Звездочка открытое решение, и я только начал описывать начало начал, зачем меня «минусовать»? С форумов удаляете данную инфу хотя это же все очень просто делается. Любой человек разбирающийся в Астере может себя реализовать и написать собственное API, а затем еще и интегрировать его в приложение. Вы еще и с Хабра удалите эту статью, и вообще станет как в политике Америки, только Американцы и Европейцы имеют право писать СОФТ. А остальные должны лишь рукоплескать их уму. А я татарин, который живет в Казахстане. И я считаю что мы тоже можем реализовать собственную «открытую» обертку для Астера.

    Извините наболело, особенно когда на форумах по астеру мои статьи с фишками для Астера уничтожаются в течении часа. Я даже на хабре пишу на ночь глядя, а вдруг и здесь статью сольют.
    • +1
      Судя по голосованию, интерес есть, поэтому создавайте проект на github, добавляйте ссылку в статью.

      Сможете продуктивно работать с единомышленниками и собирать фидбек в виде issues/pull requests.
      Ну а там видно будет взлетел проект и стагнировал — в любом случае вы в плюсе. Удачи!

      • –2
        kalessil, пока по голосованию на данном этапе интерес имеется. Но надо дождаться результатов хотя бы недельного голосования.

        На гитхаб у меня лично нет желания выкладывать свои или общие наработки. Поясню: я сам плохо разбираюсь в техническом английском, и мне не нравится в международных проекта ненавязчивый регламент вести описание на не родном мне языке. Согласен это ведет к моей ограниченности, но я не хочу чтобы мои мысли были неправильно интерпретированы в связи с моим плохим английским. Ведь даже когда на русском комментируешь тот или иной участок кода, два разных человека могу совершенно по разному это понять. Проще работать с бывшими «совками» есть связующее звено, русский, и найти взаимопонимание становится проще.

        Что то я сам запутался в своем комменте.
        • 0
          Тогда bitbucket, делаете основное описание на русском и все, вопросов нет.

          И поменьше рефлексии, вам комьюнити нужно — это мощная поддержка, в т.ч. моральная.
          • –2
            Ну в начале хотя бы одного художника найти. А там посмотрим.
    • 0
      То что вы рассказали про связку Asterisk и php — достойно похвалы и если бы статья вышла года полтора назад я был бы несказанно счастлив её прочитать и понять в какую сторону копать, но «всё уже написано за нас», нужно просто правильно искать:
      github.com/marcelog/PAMI
      • –1
        AotD Спасибо за комментарий. К сожалению статья вышла вчера, а не полтора года назад. По поводу ПАМИ, скажу сразу, я не описал готовое решение, я описал механизм по которому можно работать. И кто сказал, что взяв просто код ПАМИ можно сразу нарисовать колл центр? Особенно когда сама библиотека не документирована для русскоговорящей аудитории, и механизм ее работы даже для англоязычной аудитории не раскрыт? В статье описан механика используя которую любой начинающий(или практикующий) разработчик может понять с чего начать работу. А для небольших приложений так вообще готовый модуль, который просто нужно переработать под свои нужды.

        Если у Вас нет желания про это читать, велком к голосованию.
        • +1
          Собственно PAMI тоже не есть готовое решение, а библиотека предоставляющая удобную абстракцию для общения с Asterisk не погружаясь в его кишки =)
          В случае вашей реализации «Для работы с Астериск АМИ мы должны записывать в сокет пакеты строк, в ответ на которые мы будем также получать строки о статусе наших запросов.», нужно разбирать ответы и контролировать всё в ручную. В PAMI всё разложено по полочкам, выкидывает соответствующие Exception'ы, покрыто тестами и превращается действительно в программирование на php:
          $outgoing = new OriginateAction('/SIP/someprovider/' . $phone);
          $outgoing->setContext('outgoing');
          $outgoing->setExtension(sprintf("%04d",$digits));
          $outgoing->setPriority('1');
          $outgoing->setAsync(true);
          $this->ami->send($outgoing)
          


          Зачем документировать библиотеку для русских/белорусов/казахов/мексиканцев? Хотите — дело ваше, fork, translate, merge, repeat. Английский — отличный способ международного общения и главное «ЗА» этого языка в том что не только определенная замкнутая группа людей может, хочет и будет поддерживать продукт и вносить в него полезные изменения, но вообще любой кто имеет компетенцию в данной области и знает язык на уровне чуть выше elementary. Извините, за этот выпад на ваш «вообще станет как в политике Америки, только Американцы и Европейцы имеют право писать СОФТ. А остальные должны лишь рукоплескать их уму», но автор вышеупомянутой библиотеки Аргентинец и я вообще не понимаю разделения программистов по дислокации и расовой принадлежности. Так что ответ на голосование: «Да, тема интересная» и хочется больше материалов о способах взаимодействия (но не деталей реализации, которая увы делается без оглядки на общемировые практики и стандарты), потому как «Нет, уже имеются готовые решения»
          • –1
            AotD Спасибо еще раз за Ваши комментарии.

            Отвечу по мере способностей =)

            Ну во первых не у всех имеется такой багаж опыта как у Вас. Поэтому я и спрашиваю сообщество стоит ли продолжать или закончить на этом. Ведь эта статья, как бы первая ласточка, которая показывает вариант как можно самим начать работу с АМИ звездочки. Если в ПАМИ есть готовый функционал, это еще не значит что допустим некто, начавший работать с Астером недавно не имея полноценной документации сразу же начнет писать приложение основываясь на ПАМИ. Если статья, ее подача и полезность ее информации не интересует Вас то нет проблем. Я ведь жду что скажет сообщество.

            Поясню почему я начал писать эту статью, когда первый клиент обратился к нам с просьбой связать его CRM систему с Астером, мы пришли к ПАМИ также как и Вы. поюзав данный фреймворк, отдел разработки пришел к выводу что там все сильно запущено. То есть она написана для абстрактной системы которая не может контролировать большие колл центры, реальность вернула нас к началу начал. Пришлось изучить исходники и перерыть гору информации, которой в рунете почти нет.

            По поводу написания софта, да у меня действительно наболело, вот опубликуешь статью на специализированном форуме, вроде движение начинается по статье, но только до прихода модератора, как только модератор появляется статья вместе с аккаунтом исчезает. Поднимать собственные форумы это к сожалению не мое, не хочу тратить свое время на еще одну помойку. Хотя допустим даже эта статья приведет на хабр огромное количество постигающих Астериск из поисковых систем.

            По поводу Аргертинца, ну я же отписался, что ограничен в своем развитии =).

            А во вторых оказывается и нету.
          • –1
            AotD еще раз спасибо за Ваши комментарии.
            Отвечу по поводу
            общемировые практики и стандарты


            В Евросоюзе и США имеются «практика и стандарт и даже законы социальной защиты детей» диктующие что в 70% в семья жителей бывших «совков» издеваются (именно издеваются) над детьми, и на основании этого нужно проверять каждую такую семью для защиты детей от родителей. Давайте применим эти же законы и стандарты у себя. У это уже будет нонсенс.

            Также мировая практика говорит что специалист должен в совершенстве знать только свою специализацию. Но мы живем в другом мире, где каждый мужчина может сам выкрутить лампочку и заменить ее, не вызывая электрика, не смотря на то что он допустим учитель литературы.

            Я конечно немного утрировал, и не во всем это так, но примеры увы вполне из реальности.

            И давайте придем к логическому мнению что:
            И Вы и Я будем применять те стандарты и спецификации которые действительно полезны и необходимы нам, а не кому то там кто сидит в сертифицирующем органе и кто далек от наших потребностей. Если Ваша команда ориентирована на западного клиента и западную сертификацию, то это Ваше право работать по стандартам которые приняты на тех территориях. Но у нас имеются собственные сертификационные центры, которые выпускают собственные стандарты, которые в свою очередь могут легко идти в разрез с американскими или европейскими стандартами.

            И такого понятия как общемировой стандарт или общемировая практика не существует. Также как общемирового сертификационного центра. В каждой стране имеются свои наработки в сертификации. Даже определенное ИСО в каждой стране может быть разным.

            Возьмите даже вроде бы самую большую мировую куммунити — науку.Так даже здесь, нет единых стандартов. Даже теория Дарвина и то идет в разрез с Научным обществом Ватикана, а ведь докторантура Ватикана считается одной из самых сильнейших в мире. Примеров масса. Оглянитесь. Нету мировых стандартов. Есть стандарты которые Вы или Я или группа людей договорилась соблюдать, на определенной территории или в определенном направлении.
          • –1
            AotD Извините за резкость моих ответов на Ваш комментарий.
  • +1
        /*  Переменные класса     */
        public $ini = array();
    

    Не нужно так делать. Используйте различные переменные для хранения разнородных данных. Вообще, следовало сделать хоть какой-нибудь рефакторинг, прежде чем советовать использовать свой код. Читайте книги и откройте для себя PSR-2.

    P.S.: Если честно, я не понял что вы имели ввиду под асинхронностью, в коде я её не нашёл
    • –1
      RUgaleFF спасибо за комментарий. Поясняю по массиву. Массив проще использовать так как он динамически изменяется. То есть я просто упрощаю продакшн. Декларировать же каждую переменную это хорошо когда в самом начале имеется полное представление по переменным которые будут использованы в конечном коде.
      По поводу PSR-2, мы в нашей команде работаем по переработанной версии регламента, которая упрощает нам работу по чтению кода между разработчиками, что удобно для нас. Ведь регламент принятый для сертификации кода в одном органе может не полностью соответствовать нашим реалиям и потребностям. Здесь подходит «Сколько людей столько и мнений».

      зы: Асинхронность не в потоках, а в варианте чтения из сокета. Пока сокет открыт там нет EOF, поэтому операция чтения может тупо зависнуть пока не дождется полного закрытия сокета или прервется по таймауту. И это всего лишь класс, а не вариант кода с пометкой «следуй моему мнению». Этот класс на простом и доступном языке показывает как можно работать с сокетами, в частности, с Астериск АМИ.
  • 0
    Если я правильно понимаю, то это же telnet обычный. Какой смысл был писать свой велосипед, если достаточно было написать обёртку к существующим библиотекам?
    Которых, кстати, достаточно:

    github.com/bestnetwork/Telnet
    github.com/bosha/PTel
    github.com/tiagobutzke/php-remote-server
    github.com/avin/telnet-commander

    Да и где-то видел библиотеку как раз под ваши нужды.

    UPD: Да, выше написали про PAMI.
    • 0
      bosha Спасибо за комментарий.

      Да это также можно назвать обычным PHP телнетом.
  • 0
    пользуюсь pami нареканий нет, что там нравится так это команды уже как классы представлены, но если продолжать развивать проект то хотелось бы увидеть возможность асинхронного запуска команд (Вы написали что легко, но все равно, как говорят мелочь, а приятно), парсинг ответа в удобочитаемый вариант(в класс(модель) для сохранения в базу например через orm) и интеграция с amqp. Для чего, просто не всегда есть возможность поставить свое оборудывание на стороне хостера, например gsm шлюз, и если у вас проект находится где нибудь в панаме, а сам шлюз в грузии, то представляете какой будет read_timeout. Да еще насчет асинхронности но это только для sms, во многих gsm шлюзах с астериксом внутри есть возможность работать с почтой, то есть конвертировать письма в sms или наоборот, и используя эту возможность Вам тогда не надо подключать к проекту pami или люубую другую библиотеку для работы с ami, просто шлете письмо на указанный ящик, шлюз по pop3 каждые n- секунд проверяет его и конвертит письма в sms, так же можно указывать pin для безопасности.
    • 0
      PaulWeb Спасибо за Ваш комментарий.

      По поводу асинхронности чтения, в ПАМИ есть много проблем так как он заточен под абстрактный малоинтенсивный продакшн. В связи с этим и ответы от астериска приходят в совершенно неудобном формате. В классе режим чтения данных из сокета представлен асинхронно, то есть чтение из сокета может происходить не по мере поступления информации а именно тогда когда требует приложение.
      В следующей части я собираюсь раскрыть потенциал простого Originate-а, с возможностью чтения данных уже из готового массива и с историей по ActionID. Но пока я соберусь походу меня заминусуют =) Видать статья не понравилась сообществу, хотя опрос показывает что тема актуальна, да и количество человек которые добавили статью в избранное тоже заставляет думать что здесь что-то не то.
      На самом деле собрать всю информацию в готовый и классифицированный массив что не так уж и сложно, сложнее бороться с памятью которую жрут демоны. Мы у себя в продуктах нашли решение. Оно связано с применением массивов как динамических объектов, которое также нужно показывать отдельной статьей. Потому что готовый класс ни приведет разработчика к написанию более-менее сложного приложения, пока он не поймет механику класса,.
      Повторюсь, даже в этом классе реализовано «асинхронное» чтение из сокета. Которое не дает зависнуть приложению в случае отсутствия данных на стороне сокета, а просто выбрасывает пустой массив.
  • 0
    У вас в функции init ошибка вместо пароля передаётся 2 раза логин. Исправьте пожалуйста.

    Надо вот так:
    public function init()
    {
        return $this->write("Action: Login\r\nUsername: ".$this->ini["login"]."\r\nSecret: ".$this->ini["password"]."\r\n\r\n");
    }
    


    Вы ещё не завили git репозиторий для этого проекта?

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.