# Глава 17. AMI и файлы вызовов

> Джон Малкович: я видел мир, который не должен видеть ни один человек!
> Крейг Шварц: Правда? Потому что для большинства людей это довольно приятный опыт.
> -- Быть Джоном Малковичем

Интерфейс Asterisk Manager (Asterisk Manager Interface - AMI) - это интерфейс мониторинга и управления системой, предоставляемый Asterisk. Он позволяет в реальном времени отслеживать события, происходящие в системе, а также позволяет запрашивать Asterisk выполнение некоторых действий. Доступные действия имеют широкий диапазон и включают такие вещи, как возврат информации о состоянии или инициирование новых вызовов. На Asterisk было разработано много интересных приложений, использующих AMI в качестве основного интерфейса для Asterisk.

Эта глава также включает документацию по использованию файлов вызовов. Файлы вызовов Asterisk - это простой способ инициировать несколько вызовов. Как только объем исходящих вызовов увеличивается или ваши потребности становятся более сложными, вы можете перейти к использованию AMI. На самом деле, мы находим файлы вызовов достаточно полезными, так что сначала поговорим о них.

## Файлы вызовов

Обычно для инициализации вызовов используется AMI, но во многих ситуациях проще использовать файлы вызовов. Файл вызова - это простой текстовый файл, описывающий вызов, который вы хотите совершить через Asterisk. Когда файл вызова помещается в каталог _/var/spool/asterisk/outgoing_, Asterisk немедленно обнаружит, что файл был помещен туда, и обработает вызов.

Asterisk поставляется с образцом файла вызова, который вы найдете в _~/src/asterisk-15.\<TAB\>/sample.call_ (или там, где находится корневой каталог исходников Asterisk).

### Ваш первый файл вызова

Для вашего первого файла вызова давайте создадим вызов между двумя вашими телефонами. Убедитесь, что хотя бы два ваших телефона зарегистрированы и работают. Для этого примера мы будем использовать `SOFTPHONE_A` и `SOFTPHONE_B`.

Создайте в домашнем каталоге следующий файл:

```
$ vim ~/call-file

Channel: PJSIP/SOFTPHONE_A
Extension: 103
Context: sets
```

Сделайте копию этого файла (так что вам не придется заново создавать его каждый раз, когда захотите запустить его):

```
$ cp ~/call-file docall
```

Измените владельца файла docall на `asterisk`:

```
$ chown asterisk:asterisk docall
```

Переместите файл _docall_ в каталог _outgoing_ Asterisk.

```
$ sudo mv docall /var/spool/asterisk/outgoing
```

Иногда самый простой способ - лучший способ.

<table border="1" width="100%" cellpadding="5">
  <tr>
    <td>
      <p>Вы, вероятно, обнаружите, что делаете несколько правок в исходном файле вызова. Вы можете просто переместить созданный файл, а не делать его копию, но тогда вам придется заново создавать его каждый раз, когда вы его редактируете, и это раздражает. Весь этот набор можно сохранить как однострочный и запустить следующим образом:</p>
<p><pre><code>$ cp ~/call-file docall \
sudo chown asterisk:asterisk docall \
sudo mv docall /var/spool/asterisk/outgoing/</code></pre></p>
      <p>Попробуйте, и вы увидите, насколько это проще, чем каждый раз создавать и перемещать новый файл вызова.</p>
    </td>
  </tr>
</table>

<table border="1" width="100%" cellpadding="5">
  <tr>
    <td>
      <p align="left"><b>Предупреждение</b></p>
      <p>Использование <code>mv</code> вместо <code>cp</code> здесь важно. Asterisk следит за тем, чтобы содержимое отображалось в каталоге <i>spool</i>. Если вы используете копирование - Asterisk может попытаться прочитать новый файл до того, как содержимое будет скопировано в него. Создание файла, а затем его перемещение позволяет избежать этой проблемы.</p>
    </td>
  </tr>
</table>

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

### Заметки о файлах вызова

Компонент `Channel` файла вызова является обязательным. Обычно вызов, поступающий в Asterisk, инициируется конечной точкой (например, вы делаете вызов со своего телефона). В файле вызова это соединение должно происходить наоборот - Asterisk обращается к конечной точке, и только когда она отвечает, вызов может начаться. Планируйте соответственно.

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

Расширение, конечно, также должно быть указано. Обычно это номер телефона, по которому нужно позвонить, но, конечно, это может быть любой допустимый добавочный номер в `Context`.

Остальные параметры файла вызова являются необязательными и подробно описаны в файле _~/src/asterisk-15.\<TAB\>/sample.call_ и на веб-сайте Asterisk wiki.

## AMI Быстрый старт

Этот раздел предназначен для того, чтобы как можно быстрее испачкать руки с помощью AMI. Во-первых, поместите следующую конфигурацию в _/etc/asterisk/manager.conf_:

```
; Включить AMI и указать ему принимать соединения только от localhost.
[general]
enabled = yes
webenabled = yes
bindaddr = 127.0.0.1

; Создайть аккаунт с именем "hello" и паролем "world"
[hello]
secret=world
read=all     ; Получать все типы событий
write=all    ; Разрешить этому пользователю выполнять все действия
```

<table border="1" width="100%" cellpadding="5">
  <tr>
    <td>
      <p align="left"><b>Примечание</b></p>
      <p>Этот пример конфигурации настроен так, чтобы разрешить только локальные подключения к AMI. Если вы собираетесь сделать этот интерфейс доступным по сети, настоятельно рекомендуется использовать только протокол TLS. Использование TLS более подробно рассматривается далее в этой главе.</p>
    </td>
  </tr>
</table>

Как только конфигурация AMI готова, включите встроенный HTTP-сервер, поместив следующее содержимое в _/etc/asterisk/http.conf_:

```
; Включить встроенный HTTP-сервер и слушайть только соединения на localhost.
[general]
enabled = yes
bindaddr = 127.0.0.1
```

Перезагрузите диспетчер и http-серверы из Asterisk CLI:

```
*CLI> manager reload

*CLI> module reload http
```

### AMI через TCP

Существует несколько способов подключения к AMI, но наиболее распространенным является TCP-сокет. Мы будем использовать `telnet` для демонстрации подключения AMI. Для этого нам нужно будет установить `telnet`:

```
$ sudo yum -y install telnet
```

В этом примере показаны следующие шаги:
* Подключение к AMI через TCP-сокет на порту 5038.
* Вход в систему, используя действие `Login`.
* Выполнение действия `Ping`.
* Выход из системы с помощью действия `Logoff`.

Вот как это сделать с помощью `telnet`:

```
$ telnet localhost 5038

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Asterisk Call Manager/4.0.3
```

Вы подключились, но он будет висеть на вас, если вы не подтвердите свою подлинность. Вставьте в окно `telnet` следующее:

```
Action: Login
Username: hello
Secret: world
```

Обратите внимание, что после команд должна быть пустая строка (нажмите Enter после вставки всего, если ничего не происходит).

```
Response: Success
Message: Authentication accepted
```

Ладно, мы ему нравимся. Давайте выполним простую команду, чтобы убедиться, что он действительно говорит с нами:

```
Action: Ping
```

```
Response: Success
Ping: Pong
```

Все идет нормально. Мы просто уберемся и выйдем сейчас.

```
Action: Logoff
```

```
Response: Goodbye
Message: Thanks for all the fish.
Connection closed by foreign host.
```

Вы убедились что AMI принимает соединения через TCP-соединение.

### AMI через HTTP

Также можно использовать AMI через HTTP. Мы будем выполнять те же действия что и раньше, но через HTTP вместо собственного TCP-интерфейса к AMI. АMI через HTTP подробно описаны в [“AMI через HTTP”](#AMI-HTTP).

<table border="1" width="100%" cellpadding="5">
  <tr>
    <td>
      <p align="left"><b>Примечание</b></p>
      <p>Учетные записи, используемые для подключения к AMI через HTTP, являются теми же учетными записями, настроенными в файле <i>/etc/asterisk/manager.conf</i>.</p>
    </td>
  </tr>
</table>

В этом примере показано, как получить доступ к AMI по протоколу HTTP, войти в систему, выполнить действие `Ping` и выйти из системы:

```
$ curl "http://localhost:8088/rawman?action=login&username=hello&secret=world" \
-c /tmp/tempcookie

Response: Success
Message: Authentication accepted
```

```
$ curl "http://localhost:8088/rawman?action=ping" -b /tmp/tempcookie

Response: Success
Ping: Pong
Timestamp: 1538871944.474131
```

```
$ curl "http://localhost:8088/rawman?action=logoff" -b /tmp/tempcookie

Response: Goodbye
Message: Thanks for all the fish.
```

Интерфейс HTTP для AMI позволяет интегрировать управление вызовами Asterisk в веб-службу.

## Конфигурация

Раздел ["AMI быстрый старт"](glava-17.md#ami-быстрый-старт) показал очень простой набор конфигурационных файлов для начала работы. Существует много способов тонкой настройки конфигурации AMI.

### manager.conf

Основной конфигурационный файл для AMI - это _/etc/asterisk/manager.conf_. Раздел `[general]` содержит параметры, управляющие общей работой AMI. Любые другие разделы в _manager.conf_ определяют учетные записи для входа в систему и использования AMI. Пример файла содержит подробные объяснения различных параметров и может быть найден в  _~/src/asterisk-15\<TAB\>/configs/samples/manager.conf.sample_.

<table border="1" width="100%" cellpadding="5">
  <tr>
    <td>
      <p align="left"><b>Предупреждение</b></p>
      <p>Если вы собираетесь выставить свой AMI за пределы машины, на которой он работает, вам потребуется настроить подключение TLS.</p>
    </td>
  </tr>
</table>

Конфигурационный файл _manager.conf_ также содержит конфигурацию учетных записей пользователей AMI. Вы создаете учетную запись, добавляя раздел с именем пользователя в квадратных скобках. В каждом разделе `[username]` есть параметры, которые могут быть установлены, которые будут применяться только к этой учетной записи. Файл _~/src/asterisk-15\<TAB\>/configs/samples/manager.conf.sample_ также содержит подробные объяснения каждого из этих параметров. Наш пользователь по имени `[hello]`, имеет простейшую конфигурацию, которая позволяет все операции чтения и записи. Обычно следует создавать пользователей AMI, которые ограничены только действиями, необходимыми для их функционирования.

В разделе `[username]` параметры `read` и `write` определяют к каким действиям и событиям диспетчера имеет доступ конкретный пользователь. На данный момент есть 20 из них: `all`, `system`, `call`, `log`, `verbose`, `agent`, `user`, `config`, `command`, `dtmf`, `reporting`, `cdr`, `dialplan`, `originate`, `agi`, `cc`, `aoc`, `test`, `security` и `message`. Вы увидите что файл _manager.conf.sample_ содержит ссылку на каждый из них, относящийся к вашему выпуску (и, если какие-либо из них добавлены, которые не были перечислены здесь, они будут в файле примера).

<table border="1" width="100%" cellpadding="5">
  <tr>
    <td>
      <p align="left"><b>Предупреждение</b></p>
      <p>Обратите особое внимание на разрешения <code>system</code>, <code>command</code> и <code>originate</code>. Эти разрешения предоставляют значительные полномочия всем приложениям, которые имеют право их использовать. Предоставляйте эти разрешения только приложениям, над которыми у вас есть полный контроль (и в идеале они работают в одном окне).</p>
    </td>
  </tr>
</table>

### http.conf

Как мы уже видели, интерфейс Asterisk Manager может быть доступен как по протоколу HTTP, так и по протоколу TCP. Для этого в Asterisk встроен очень простой HTTP-сервер. Все параметры, относящиеся к AMI, находятся в разделе [general] файла _/etc/asterisk/http.conf_.

<table border="1" width="100%" cellpadding="5">
  <tr>
    <td>
      <p align="center"><b>Примечание</b></p>
      <p>Включение доступа к AMI по протоколу HTTP требует наличия <i>/etc/asterisk/manager.conf</i> и <i>/etc/asterisk/http.conf</i>. AMI должен быть включен в <i>manager.conf</i> с параметром <code>enabled</code>, установленным в <code>yes</code> и <code>webenabled</code> должен быть установлен в значение <code>yes</code> чтобы разрешить доступ по протоколу HTTP. Наконец, опция <code>enabled</code> в <i>http.conf</i> должна быть установлена в <code>yes</code> чтобы включить сам HTTP-сервер.</p>
    </td>
  </tr>
</table>

Доступные опции будут найдены в вашем файле _~/src/asterisk-15\<TAB\>/configs/samples/http.conf.sample_.

## Обзор протокола

В AMI есть два основных типа сообщений: события диспетчера и действия диспетчера.

_События диспетчера_ - это односторонние сообщения, посылаемые Asterisk клиентам AMI для сообщения о том, что произошло в системе (Рисунок 17-1).

![Рисунок 17-1. События диспетчера](pics/pic17-1.png)

_Рисунок 17-1. События диспетчера_

_Действия диспетчера_ - это запросы от клиента к Asterisk для выполнения некоторого действия и возврата результата (Рисунок 17-2). Например, действие AMI инициирует запросы, чтобы Asterisk создал новый вызов, и, естественно, клиентскому приложению потребуются ответы от Asterisk, чтобы указать ход выполнения этого действия.

![Рисунок 17-2. Действия диспетчера](pics/pic17-2.png)

_Рисунок 17-2. Действия диспетчера_

Другие действия менеджера - это запросы данных. Например, есть действие - получить список всех активных каналов в системе: сведения о каждом канале доставляются как событие. Когда список результатов будет завершен, будет отправлено окончательное сообщение о том, что цель достигнута. См. Рисунок 17-3 для графического представления клиента, отправляющего этот тип управляющего действия и получающего список ответов.

![Рисунок 17-3. Действия диспетчера возвращающие список данных](pics/pic17-3.png)

_Рисунок 17-3. Действия диспетчера возвращающие список данных_

### Кодировка сообщений

Все сообщения AMI, включая события, действия и ответы на действия, кодируются одинаково. Сообщения являются текстовыми, со строками, заканчивающимися возвратом каретки и символом перевода строки. Сообщение завершается пустой строкой:

```
Header1: This is the first header<CR><LF>
Header2: This is the second header<CR><LF>
Header3: This is the last header of this message<CR><LF>
<CR><LF>
```
Если вы запускаете тесты из telnet-клиента - это означает, что после последней строки инструкций вам нужно будет дважды нажать клавишу Enter.


#### События

События всегда имеют заголовок `Event` и заголовок `Privilege`. В заголовке `Event` указывается имя события, а в заголовке `Privilege` - уровни разрешений, связанные с данным событием. Любые другие заголовки, включенные в событие, являются специфичными для данного типа события. Вот вам пример:

```
Event: Hangup
Privilege: call,all
Channel: SIP/0004F2060EB4-00000000
Uniqueid: 1283174108.0
CallerIDNum: 2565551212
CallerIDName: Russell Bryant
Cause: 16
Cause-txt: Normal Clearing
```

CLI Asterisk включает в себя `manager show events` и `manager show event <event>`. Выполните эти команды в CLI Asterisk, чтобы получить список событий или узнать подробности конкретного события.

Не забывайте, что отличным справочником для всех вещей Asterisk, включая AMI, является официальная [Asterisk wiki](https://wiki.asterisk.org/).

#### Действия

При выполнении действия _необходимо_ включить заголовок `Action`. Заголовок `Action` определяет, какое действие выполняется. Остальные заголовки являются аргументами для действия и могут потребоваться или не потребоваться в зависимости от действия.

Чтобы получить список заголовков, связанных с определенным действием, введите в CLI Asterisk команду `manager show command <Action>`. Чтобы получить полный список действий, поддерживаемых используемой версией Asterisk, введите `manager show commands`.

Окончательный ответ на действие обычно представляет собой сообщение, содержащее заголовок `Response`. Значение заголовка `Response` будет `Success`, если действие было выполнено успешно. Если действие не было успешно выполнено, то значение заголовка ответа будет `Error`. Например:

```
Action: Login
Username: hello
Secret: world

Response: Success
Message: Authentication accepted
```

### AMI через HTTP <a name="AMI-HTTP"></a>

Помимо собственного TCP-интерфейса, можно также получить доступ к AMI по протоколу HTTP. Программисты с имеющимся опытом написания приложений, использующие веб-API, скорее всего предпочтут его по сравнению с подключением TCP. В то время как интерфейс TCP предлагает только один тип структуры сообщений, AMI через HTTP предлагает несколько вариантов кодирования. Вы можете получать ответы в том же формате что и в TCP, в формате XML или в виде базовой HTML-страницы. Тип кодировки выбирается на основе поля в URL запросе. Варианты кодирования рассматриваются более подробно далее в этом разделе.

#### Аутентификация и обработка сессии

Существует два метода выполнения аутентификации против AMI через HTTP. Первый - это использование действия `Login`, аналогичного аутентификации с помощью собственного интерфейса TCP. Это метод, который использовался в Примере быстрого запуска, как показано в [AMI через HTTP](glava17.md#ami-через-http).

После успешной аутентификации Asterisk предоставит файл cookie, который идентифицирует аутентифицированный сеанс. Вот пример ответа на действие `Login`, которое включает в себя файл cookie сеанса от Asterisk:

```
$ curl -v "http://localhost:8088/rawman?action=login&username=hello&secret=world"
```

Второй вариант аутентификации - это HTTP-дайджест аутентификации. В этом примере запрошенный тип кодировки, основанный на URL-запросе, является `rawman`. Чтобы указать, что следует использовать дайджест аутентификацию HTTP, префикс типа кодировки в URL-адресе запроса должен содержать `a`:

```
$ curl -v --digest -u hello:world http://127.0.0.1:8088/arawman?action=ping
```

#### Кодирование /rawman (/arawman)

Тип кодирования `rawman` - это то, что до сих пор использовалось во всех примерах AMI через HTTP в этой главе. Ответы, полученные от запросов, использующих `rawman`, форматируются точно так же, как они были бы, если бы запросы были отправлены по прямому TCP-соединению к AMI.

```
curl -v "http://localhost:8088/rawman?action=login&username=hello&secret=world"

curl -v --digest -u hello:world http://127.0.0.1:8088/arawman?action=ping
```

#### Кодирование /manager (/amanager)

Тип кодировки `manager` предоставляет ответ в простой HTML-форме. Этот интерфейс в первую очередь полезен для экспериментов с AMI:

```
$ curl -v "http://localhost:8088/manager?action=login&username=hello&secret=world"

$ curl -v --digest -u hello:world http://localhost:8088/amanager?action=ping
```

#### Кодирование /mxml (/amxml)

Тип кодировки `mxml` предоставляет ответы на действия закодированные в XML:

```
$ curl -v "http://localhost:8088/mxml?action=login&username=hello&secret=world"

$ curl -v --digest -u hello:world http://localhost:8088/amxml?action=ping
```

#### События диспетчера

При подключении к собственному интерфейсу TCP для AMI события доставляются асинхронно. При использовании AMI через HTTP необходимо получить события путем опроса для них. Вы получаете события по протоколу HTTP, выполняя действие `WaitEvent`. В следующем примере показано, как события могут быть извлечены с помощью действия `WaitEvent`. Шаги такие:

1. Запустите сеанс HTTP AMI с помощью действия `Login`.
2. Зарегистрируйте SIP-телефон на Asterisk, чтобы создать событие.
3. Извлеките событие с помощью действия `WaitEvent`.

Взаимодействие выглядит следующим образом:

```
$ wget --save-cookies cookies.txt \
> "http://localhost:8088/mxml?action=login&username=hello&secret=world" -O -

<ajax-response>
<response type='object' id='unknown'>
    <generic response='Success' message='Authentication accepted' />
</response>
</ajax-response>


$ wget --load-cookies cookies.txt \
< "http://localhost:8088/mxml?action=waitevent" -O -

<ajax-response>
<response type='object' id='unknown'>
    <generic response='Success' message='Waiting for Event completed.' />
</response>
<response type='object' id='unknown'>
    <generic event='PeerStatus' privilege='system,all'
             channeltype='SIP' peer='SIP/0000FFFF0004'
             peerstatus='Registered' address='172.16.0.160:5060' />
</response>
<response type='object' id='unknown'>
    <generic event='WaitEventComplete' />
</response>
</ajax-response>
```

Вам потребуется разработать механизмы в вашем приложении чтобы гарантировать что буферизованные события часто опрашиваются.

## Пример использования

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

### Инициирование вызова

AMI имеет действие `Originate`, которое можно использовать для инициирования вызова. Многие из принятых заголовков совпадают с параметрами, размещенными в файлах вызовов. В Таблице 17-1 перечислены заголовки, принятые действием `Originate`.

_Таблица 17-1. Заголовки для действия Originate_

| Параметр | Пример значения | Описание |
| :--- | :--- | :--- |
| `ActionID` | `a3a58876-f7c9-4c28-aa97-50d8166f658d` | Этот заголовок принимается большинством действий AMI. Он используется для предоставления уникального идентификатора, который также будет включен во все ответы на действие. Это дает вам возможность определить с каким запросом связан ответ. Он важен, так как все действия, их ответы и события передаются по одному и тому же соединению (если только не используется AMI через HTTP). |
| `Channel` | `SIP/myphone` | Этот заголовок является критическим и обязательно должен быть указан. Он описывает исходящий вызов, который будет инициирован. Значение имеет тот же синтаксис, что и аргумент канала для приложения `Dial()` в диалплане. |
| `Context` | `default` | Этот заголовок используется для указания положения в диалплане, которое будет запущено после ответа на исходящий вызов. Заголовки `Context`, `Exten` и `Priority` должны быть использованы вместе. При использовании этих заголовков не следует использовать заголовки `Application` и `Data`. |
| `Exten` | `s` | Смотри документацию по заголовку `Context`. |
| `Priority` | `1` | Смотри документацию по заголовку `Context`. |
| `Application` | `ConfBridge` | Заголовки `Application` и `Data` можно использовать вместо заголовков `Context`, `Exten` и `Priority`. В этом случае исходящий вызов напрямую соединяется с одним приложением после ответа на вызов. |
| `Data` | `500` | Смотри документацию по заголовку `Application`. |
| `Timeout` | `30000` | Этот заголовок определяет, как долго (в миллисекундах) ждать ответа, прежде чем отказаться от исходящего вызова. Значение по умолчанию - 30000 миллисекунд (30 секунд). |
| `CallerID` | `Matthew Jordan <(555) 867-5309>` | Этот заголовок можно использовать для указания идентификатора вызывающего абонента, используемого для исходящего вызова. |
| `Account` | `someaccount` | Этот заголовок задает код учетной записи CDR для исходящего вызова. |
| `Variable` | `VARIABLE=VALUE` или `FUNCTION(arguments)=VALUE` | Заголовок `Variable` может использоваться для задания как переменных канала, так и функций канала на исходящем канале. Его можно задать несколько раз. |
| `Codecs` | `ulaw,alaw` | Этот параметр можно использовать для ограничения количества кодеков, разрешенных для исходящего вызова. Если этот параметр не указан, то набор кодеков, настроенных в файле конфигурации драйвера канала, будет по-прежнему учитываться. |
| `EarlyMedia` | `true` | Если этот заголовок указан и установлен в `true`, исходящий вызов будет подключен к указанному добавочному номеру или приложению, как только появится какой-либо медиапоток. |
| `Async` | `true` | Если этот заголовок задан и имеет значение `true`, то этот вызов будет инициирован асинхронно. Это позволит вам продолжить выполнение других действий на AMI-соединении во время обработки вызова. |

Самый простой пример использования действия `Originate` через `telnet`:

```
$ telnet localhost 5038

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Asterisk Call Manager/4.0.3
```

Как только соединение установлено Вам необходимо войти в систему.

```
Action: Login
Username: hello
Secret: world
```

```
Response: Success
Message: Authentication accepted
```

Теперь вы готовы инициировать свой звонок. Мы делаем практически то же самое что и с файлом вызова, только на этот раз с помощью AMI:

```
Action: Originate
Channel: PJSIP/SOFTPHONE_A
Context: sets
Exten: 103
Priority: 1
```

Вы должны услышать звонок `SOFTPHONE_A`. Как только вы ответите на него, вызов будет сделан на `SOFTPHONE_B`.

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

```
Action: Logoff
```

```
Response: Goodbye
Message: Thanks for all the fish.
Connection closed by foreign host.
```

Если вы уже повесили трубку - это не проблема. Вам просто нужно будет восстановить вызов, что, конечно же, вы можете сделать, просто позвонив по одному номеру с другого (101-103 или как пожелаете).

### Перенаправление вызова

Перенаправление (или transferring - передача) вызова из AMI - еще одна функция, заслуживающая упоминания. Действие AMI `Redirect` можно использовать для отправки одного или двух каналов на любой другой модуль в диалплане Asterisk. Если вам нужно перенаправить два канала, которые соединены вместе, сделайте это с обоими одновременно. В противном случае, как только один канал будет перенаправлен, другой будет отключен.

<table border="1" width="100%" cellpadding="5">
<tr>
<td>
<p>Важно понимать, что каналы Asterisk не существуют до тех пор, пока не будет выполнен вызов. Имя, которое мы все считаем именем канала (например, <code>SOFTPHONE_A</code>), на самом деле не является именем канала, а просто ссылкой на данные, которые используются для создания канала. Присвоение имени каналу происходит при возникновении вызова (то есть, когда канал фактически создан). Все это означает, что вы должны определить полное название канала, прежде чем сможете действовать на нем.</p>

<p>Инициируйте вызов, а затем просмотрите <code>Event: Newchannel</code>, и вы увидите имя канала под заголовком <code>Channel:</code>.</p>

<p><code><pre>
Action: Originate
Channel: PJSIP/SOFTPHONE_A
Context: sets
Exten: 103
Priority: 1
Response: Success
Message: Originate successfully queued
Event: Newchannel
Privilege: call,all
Channel: PJSIP/SOFTPHONE_A-00000013
ChannelState: 0
ChannelStateDesc: Down
CallerIDNum: <unknown>
CallerIDName: <unknown>
ConnectedLineNum: <unknown>
ConnectedLineName: <unknown>
Language: en
AccountCode:
Context: sets
Exten: s
Priority: 1
Uniqueid: 1538939479.29
Linkedid: 1538939479.29
</pre></code></p>

<p>Событие <code>Newchannel</code> предоставит имя созданного канала, которое в данном примере является <code>PJSIP/SOFTPHONE_A-00000013</code>.</p>
<p>Вам нужно будет отслеживать эти имена каналов, если хотите правильно выполнять действия по текущим вызовам. Как только вызов заканчивается, канал уничтожается. Новому вызову, использующему ту же конечную точку, будет присвоено другое имя канала. Одно определение канала может поддерживать несколько вызовов (например, возможны несколько вызовов на телефон), и именно поэтому имя канала отличается от определения канала.</p>
</td>
</tr>
</table>

Вы можете перенаправить один канал (другой будет отключен):

```
Action: Redirect
Channel: PJSIP/SOFTPHONE_A-00000013
Exten: 209
Context: sets
Priority: 1
```

Или можете перенаправить два канала:

```
Action: Redirect
Channel: PJSIP/SOFTPHONE_A-00000015
Context: sets
Exten: 209
Priority: 1
ExtraChannel: PJSIP/SOFTPHONE_B-00000016
ExtraContext: sets
ExtraExten: 209
ExtraPriority: 1
```

Функция перенаправления позволяет создавать мощные внешние приложения, которые могут управлять текущими вызовами.

## Разработка фреймворков

Многие разработчики приложений пишут код, который напрямую взаимодействует с AMI. Однако существует ряд фреймворков, которые были созданы с целью облегчить разработку приложений AMI. Если вы ищете фреймворки Asterisk на популярном языке программирования по вашему выбору, вы, скорее всего, найдете один. На вас лежит ответственность за определение пригодности структуры, в которой вы заинтересованы. Некоторые вещи, которые вы должны искать в рамках включают в себя:

**Зрелость**

Этот проект существует уже несколько лет? Зрелый проект гораздо менее вероятно будет иметь серьезные ошибки в нем.

**Поддержка**

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

**Качество кода**

Это хорошо написанная структура? Если он не был хорошо спроектирован, вы должны знать об этом, когда решаете, стоит ли доверять ему свой проект.

**Сообщество**

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

**Документация**

Код должен быть хорошо прокомментирован, но в идеале необходима вики или другая официальная документация для поддержки библиотеки.

В Таблице 17-2 перечислены некоторые структуры, которые, как мы обнаружили, на момент написания данной статьи соответствовали предыдущим критериям. Там могут быть и другие.

_Таблица 17-2. Разработка фреймворков AMI_

| Фреймворк | Язык |
| :--- | :--- |
| Adhearsion | Ruby |
| StarPy | Python |
| Asterisk-Java | Java |
| AsterNET | .NET |
| ami-io | Node.js |
| panoramisk | Python |

## Вывод

AMI предоставляет API для мониторинга событий из системы Asterisk, а также запрашивает Asterisk выполнять широкий спектр действий. Был предоставлен интерфейс HTTP, и был разработан ряд фреймворков, которые облегчают разработку приложений.

[Глава 16. Введение в интерактивное голосовое меню](glava-16.md) | [Содержание](SUMMARY.md) | [Глава 18. AGI](glava-18.md)
