wiki:Chapter12

Глава 12


Введение

В этой главе мы рассмотрим несколько возможностей и функций Астериска совместно с базой данных (далее БД). В операционной системе (далее ОС) Линукс доступны несколько типов баз данных, но мы ограничимся изучением одной — PostgreSQL. Несмотря на то, что база данных MySQL имеет огромную популярность, мы выбрали одну, так как наш опыт работы с PostgreSQL значителен. Все что мы скажем в этой главе актуально и для ODBC коннекторов, в этой связи ознакомьтесь с получением вашей любимой базой данных ODBC, некоторые сущности этой главы могут использоваться Вами.

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

Инсталляция базы данных

Первое, что необходимо сделать, это проинсталировать PostgreSQL сервер базы данных:

# yum install -y postgresql-server

Следует заметить, что для промышленных телекоммуникационных систем, вы возможно пожелаете инсталировать на отдельные друг от друга разные аппаратные системы Астериск и базу данных.

Для запуска базы данных, первый запуск (инициализация) которой, может занят несколько секунд, необходимо ввести:

# service postgresql start

Следующее, что необходимо — создать пользователя с именем asterisk, который будет использоваться для соединения и управления базой данных. Запустите следующие команды:

# su - postgres
$ createuser -P
to add: asterisk
Enter name of user
Enter password for new user:
Enter it again:
be a superuser? (y/n) n
Shall the new role
be allowed to create databases? (y/n) y
Shall the new user
be allowed to create more new users? (y/n) n
Shall the new user
CREATE USER

По умолчанию, PostgreSQL не прослушивает стек TCP/IP соединения, которое используется в Астериске. Мы должны модифицировать /var/lib/pgsql/data/postgresql.conf файл для того, чтобы разрешить Астериску соеденится с базой данных по протоколу IP. Чтобы это сделать, просто удалите комментарий в строках tcpip_socket и параметр порта — port. Убедитесь в том, что значение tcpip_socket изменено с false на true.

Далее, необходимо отредактировать файл /var/lib/pgsql/data/pg_hba.conf, чтобы разрешить ранее заведенному пользователю asterisk соеденятся с PostgreSQL серверу по TCP/IP. В конце этого файла, замените # Put your actual configuration here на следующее:

host all asterisk 127.0.0.1 255.255.255.255 md5

local all asterisk trust

Таким образом, мы создали базу данных, именуемую asterisk, c правом владельца asterisk. Эту базу мы будем использовать в этой главе.

$ createdb - -owner=asterisk asterisk

CREATE DATABASE

Перегрузите PostgreSQL сервер, после того как перешли от пользователя postrgres назад к пользователю root:

$exit

$service postgresql restart

Теперь мы можем проверить соединение с PostgreSQL сервером, посредством TCP/IP стека:

#psql -h 127.0.0.1 -U asterisk Password:

Welcom to psql 7.4.16, the PostgreSQL interactive terminal.

Type: \copyright for distribution terms

\ h for help with SQL commands
\? for help on internal slash commands
\g or terminate with semicolon to execute query
\q to quit

asterisk ⇒

В случае возникновения ошибок, повторно проверте Вашу конфигурацию, как было описано ранее. Вывод нижеуказанного сообщения, означет что TCP/IP соединения не разрешено:

psql: could not connect to server: Connection refused

IIs the server running on host "127.0.0.1" and accepting

TCP/IP connections on port 5432?

Инсталяция и конфигурирование ODBC

ODBC коннектор — это уровень абстрактного взаимодействия БД, что делает возможным соединить Астериск с множеством баз данных без дополнительных ресурсных затрат на создание коннекторов для той или иной выбраной БД. Это позволяет избежать дополнительных затрат для разработчика на написания кода для коннектора к БД. Использование ODBC вносит небольшую нагрузку на систему, так как мы создаем дополнительный программный уровень взаимодействия между БД и Астериском. Тем не мение, эти затраты несоизмеримы когда необходимо создать мощную и гибкую систему взаимодействия между Астериском и БД.
Перед инсталляцией коннектора в Астериске, нам необходимо инсталлировать ODBC в Линуксе. Для инсталляции ODBC драйверов, необходимо просто запустить команду:

# yum install -y unixODBC unixODBC-devel libtool-ltdl libtool-ltdl-devel
Необходимость установки unixODBC-devel пакета вызвана тем, что Астериск создает ODBC модули, которые мы будем использовать в этой главе.
Проверте, что PostgreSQL драйвер скрнфигурирован в /etc/odbcinst.ini файле. Он должен выглядеть примено так:

[PostgreSQL]
Description = ODBC for PostgreSQL
Driver = /usr/lib/libodbcpsql.so
Setup = /usr/lib/libodbcpsqlS.so
FileUsage? = 1

Далее проверьте, что ОС увидела драйвер посредством команды:

# odbcinst -q -d

Результатом успешной загрузки драйверов будет вывод названия базы PostgreSQL, как указанно ниже:

# odbcinst -q -d
[PostgreSQL]


После всего проделанного, приступим к конфигурировании /etc/odbc.ini файла. Этот файл используется для создания идентификатора, который Астериск будет использовать для связи с этой конфигурацией. Если в будущем с любой точки Вам необходимо изменить что-либо в БД, необходимо просто переконфигурировать этот файл, что позволит Астериску продолжить с того же места*.

[asterisk-connector]
Description = PostgreSQL connection to 'asterisk' database
Driver = PostgreSQL
Database = asterisk
Servername = localhost
UserName = asterisk
Password = welcome
Port = 5432
Protocol = 7.4
ReadOnly = No
RowVersioning = No
ShowSystemTables = No
ShowOidColumn = No
FakeOidIndex = No
ConnSettings =

* Примечание. На самом деле, Вам необходимо сменить лишь Driver, Database и Servername. Каждые username и password определены в другом месте, как будет показано ниже.

Давайте проверим, что мы можем соединится с нашей БД, используя isql-приложение. isql-приложение не выполняет соединение от пользователя root, поэтому необходимо его запустить от пользователя владельца БД (пр. перев. — в нашем случае asterisk). Исходящее соединение с ОС Линукс должно быть запущено от пользователя asterisk. В главе 14 мы расcкажем, как запустить Астериск от non-root пользователя.

# su - asterisk
$ echo "select 1" | isql -v asterisk-connector

+———————————————————-+
| Connected! |
| |
| sql-statement |
| help [tablename] |
| quit |
| |
+———————————————————-+
SQL> +——————+
| ?column? |
+——————+
| 1 |
+——————+
SQLRowCount returns 1
1 rows fetched
$ exit

После того как мы проинсталлировали, сконфигурировали и проверили работоспособность unixODBC драйвера, необходимо пересобрать Астериск с поддержкой модулей ODBC. Для этого необходимо вернутся к исходникам Астериска и в каталоге установки Астериска запустить конфигурационный скрипт ./configure. Скрипт проверит наличие проинсталлированного unixODBC драйвера:

# cd /usr/src/asterisk-1.4
# make distclean
# ./configure
# make menuselect
# make install

Важно! Практически все инсталляции в этой главе запущены по умолчанию. Можно запустить скрипт make menuselect для подтверждения того, что ODBC модули доступны.Модули включают cdr_odbc, func_odbc, func_realtime, pbx_realtime, res_config_odbc, res_odbc. Для хранения голосовой почты в базе данных ODBC, необходимо выбрать в закладке меню "Voicemail Build Options" параметр ODBC_STORAGE. Мы также можем убедиться в существовании файлов в каталоге /usr/lib/
asterisk/modules/.

Конфигурация res_odbc.conf для доступа к БД

Все ODBC коннекторы конфигурируются в /etc/asterisk/res_odbc.conf файле, где можно установить все параметры настройки для различных модулей Астериска, используемые для соединения с БД.

Отредактируем файл res_odbc.conf, как показано ниже:

[asterisk]
enabled ⇒ yes
dsn ⇒ asterisk-connector
username ⇒ asterisk
password ⇒ welcom
pooling ⇒ no
limit ⇒ 0
pre-connect ⇒ yes

dsn — это опции, связанные с соединением с базой данных, которую мы сконфигурировали в файле /etc/odbc.ini. Опция pre-connect говорит Астериску открыть и подтвердить соединение с БД, когда загружается модуль res_odbc.so

После конфигурирования файла res_odbc.conf, запустите Астериск и проверьте соединение Астериск и БД с помощью команды odbc show:

*CLI>odbc show

[Name:asterisk Name: asterisk]

DSN: asterisk-connector

Опции pooling и limit необходимо использовать для MS SQL Server и Sybase базы данных. Это позволит Ваш осуществлять множество соединений (limit численно устанавливает это ограничение) к базе данных, вместе с тем убедитесь в том, что каждое соединение может иметь только одну выполняющуюся заявку в каждом соединении (это реализовано благодаря ограничениям в протоколе, который используется этими серверами БД).

Pooled:no''[[BR]]''Connected: yes

Использование Realtime

Asterisk Realtime Architecture (ARA) — Архитектура Реального Времени Астериска, метод хранения конфигурационных файлов (которые обычно находятся в каталоге /etc/asterisk/), опции которого могут хранится в таблице БД. Существует два типа realtime: static (статический) и dynamic (динамический). Статическая версия это просто классический метод чтения (пр. перевод. text parsing — парсинг текста), за исключением того что данные считываются с БД. Динамический метод используется подобно user и peer (для каналов SIP,IAX2), голосовой почты, которые загружаются и обновляют информацию по необходимости. Изменения статической информации требует перезагрузки, также как и изменения текстового файла в ОС, а динамическая информация, запрашиваемая Астериском по необходимости, не требует перезагрузки. Режим реального времени конфигурируется в etc/asterisk/extconfig.conf. Этот файл сообщает Астериску, что и когда будет загружено с БД, точно определить загружаемые файлы с БД и другие файлы, загружаемые со стандартных конфигурационных файлов.

Статическая архитектура реального времени

Статический realtime используется тогда, когда Вы хотите хранить информацию с конфигурационных файлов /etc/asterisk/, но загружать ее с БД. Можно без проблем использовать классические конфигурационные файлы в static realtime, подобно тому, как мы используем в командной строке Астериска CLI загрузку/перезагрузку модулей, связанных с соответствующими конфигурационными файлами (например, module reload chan_sip.so)

Использование директивы предзагрузки (pre-load)

Большинство файлов могут быть загружены посредством метода static realtime, но некоторые файлы не могут быть загружены используя этот метод. К таким файлам относят: asterisk.conf, extconfig.conf и logger.conf. Добавим, что файлы manager.conf, cdr.conf, rtp.conf не могут быть загружены с помощью static realtime, за исключением драйверов баз данных. Последние загружаются до загрузки главного ядра Астериска — это обьясняется тем, что конфигурационные данные необходимо загрузить до того как модуль прочитает эту конфигурацию.

Ввиду того, что мы используем ODBC драйвер в этой главе, нам необходимо добавить следующие строки в /etc/asterisk/modules.conf:

; /etc/asterisk/modules.conf
preload ⇒ res_odbc.so
preload ⇒ res_config_odbc.so

Когда мы используем static realtime, мы сообщаем Астериску, какие файлы мы хоти загрузить с базы данных. Для этого используем следующий синтаксис:

; /etc/asterisk/extconfig.conf
filename.conf ⇒ driver,database[,table]

Важно! Если имя таблицы неуказано, Астериск вместо этого будет использовать имя файла.

Модуль static realtime использует специальный формат таблицы для чтения конфигурации со статических файлов в/из базу данных. Вы можете определить таблицу для режима static realtime в PostgreSQL, как показано ниже:

CREATE TABLE ast_config
(
id serial NOT NULL,
cat_metric int4 NOT NULL DEFAULT 0,
var_metric int4 NOT NULL DEFAULT 0,
filename varchar(128) NOT NULL DEFAULT ''::character varying,
category varchar(128) NOT NULL DEFAULT 'default'::character varying,
var_name varchar(128) NOT NULL DEFAULT ''::character varying,
var_val varchar(128) NOT NULL DEFAULT ''::character varying,
commented int2 NOT NULL DEFAULT 0,
CONSTRAINT ast_config_id_pk PRIMARY KEY (id)
)
WITHOUT OIDS;

Коротко говоря, эти колонки необходимы для того, чтобы понять как Астериск получает строки и применяет их для конфигурации различных модулей, выможете загрузить:

cat_metric: Означает вес (пр. перев.: место) категории в файле. Чем ниже значение, тем оно выше (ранее) возникло в файле (см. главу "Несколько слов о метриках").

var_metric: Вес значения внутри категории . Чем ниже значение метрики, тем раньше оно возникло в файле. Это часто используется при определении порядка кодеков в файлах sip.conf, iax.conf, когда вы хотите изначально запретить использование кодеков опцией disallow=all (метрика 0), далее может следовать allow=ulaw (метрика 1), далее allow=gsm (метрика 2).

filename: Это модуль, который обычно читает с жесткого диска Вашей операционной системы (например, musiconhold.conf, sip.conf,iax.conf, и т. п.).

category: Имя секции внутри файла, подобно секции [general], но не сохраняйте в базе данных, используя квадрадтные скобки.

var_name: Значение выражения, находящееся с левой стороны (например, в выражении disallow=all, var_name — это disallow).

var_val: Значение выражения, находящееся с правой стороны (например, в выражении disallow=all, var_val — это all).

commented: Любое значение отличное от нуля может быть оценено, как если бы оно было присоединено с помощью точки с запятой в теле файла (раскомментировано)

Коротко о метриках

Метрики в static realtime используются для контроля порядка в котором объекты будут считываться в память. Предполагают, что cat_metric и var_metric соответствуют оригинальным номерам строк в файле. Самое верхнее поле cat_metric обрабатывается первым (потому как Астериск обрабатывает категории снизу вверх — это и есть причиной, по которой порядок пользователей users и peers имеет значение в sip.conf или iax.conf). После, первой будет обработана категория var_metric, потому как Астериск внутри категории обрабатывает значения сверху вниз (например, disallow=all должно содержать значение ниже, чем опции allow внутри категории, что гарантирует обработку disallow первым, до обработки allow).

Примечание (перевод.) cat_metric действительно обрабатывается снизу вверх. Для того, чтобы убедится в этом, можно вывести в *CLI>sip show peers. При этом порядок вывода пользователей в CLI будет обратным порядку описания (заведения) их в sip.conf :)


Самый простой пример загрузка static realtime — загрузка musiconhold.conf. Для этого сделаем копию файла musiconhold.conf:
# cd /etc/asterisk
# mv musiconhold.conf musiconhold.conf.old


чтобы выгрузить из памяти этот класс, необходимо перегрузить Астериск. Чтобы убедится, что класс moh выгружен, в CLI Астериска необходимо сделать следующее:
*CLI> restart now
*CLI> moh show classes
*CLI>


Теперь можно положить класс [default] назад в файл musiconhold.conf, но теперь мы загрузим его с БД. Соединимся с PostgreSQL и выполним следующую конструкцию INSERT:
INSERT INTO ast_config (filename,category,var_name,var_val)
VALUES ('musiconhold.conf','general','mode','files');
INSERT INTO ast_config (filename,category,var_name,var_val)
VALUES ('musiconhold.conf','general','directory','/var/lib/asterisk/moh');

Теперь осталось лишь модифицировать файл etc/asterisk/extconfig.conf для того, чтобы Астериск знал, какие данные необходимо получить с БД. Для этого добавим строку в конец файла extconfig.conf, после чего сохраним изменения:
musiconhold.conf ⇒ odbc,asterisk,ast_config
После всего вышеуказанного, присоединимся консолью к серверу Астериск и выполним перегрузку:
*CLI> module reload

Теперь необходимо убедится, что класс moh загружен с БД, запустив CLI moh show classes:
*CLI> moh show classes
Class: general
Mode: files
Directory: /var/lib/asterisk/moh

Теперь класс moh загружен с БД. Можно выполнить те же шаги для загрузки другий файлов с БД.

Динамическмя архитектура реального времени (dynamic realtime)

Динамическая загрузка используется в том случае, если загружаемые объекты часто изменяются (например, SIP/IAX2 пользователи (users и peers)) очереди и их участники (members), голосовая почта. Учитывая то, что информация в системе может быть либо изменена, либо добавляться новые записи постоянно, мы можем использовать всю мочь БД, чтобы загрузить это информацию по необходимости.

Вся конфигурация происходит в /etc/asterisk/extconfig.conf, но dynamic realtime имеет строго определенные имена, так называемые sippeers. Определение подобны определению SIP пиров и имеют следующий формат:

; extconfig.conf
sippeers ⇒ driver,database[,table]


Как видно из описания, имя таблицы используется опционально, в случае, если Астериск будет использовать предопределенное имя (например, sippeers) в качестве таблицы для поиска данных. В нашем примере, мы будем использовать ast_sippeers таблицу для хранения информации о пирах (пр. перев.: peer — пользователь).

Примечание! Запомните, что мы имеем дело как с sip peer, так и с sip user; peer — это конечная точка, которой мы посылаем звонок, а user — это пользователь, которыей принимает звонок. Friend — условное обозначения связки peer+user.

Таким образом, для конфигурации Астериска осуществлять загрузку SIP пиров c БД динамически, нам необходимо определит что-то вроде этого;
; extconfig.conf
sippeers ⇒ odbc,asterisk,ast_sipfriends


Также, для загрузки наших SIP пользователей с БД, необходимо определить что-то вроде этого:
sipusers ⇒ odbc,asterisk,ast_sipfriends

Вы могли заметить, что мы использовали одну и ту же таблицу, как для sipusers, так и для sippeers. Это объясняется тем, мы имеем тип поля (только в том случае, если он определен в файле sip.conf), который дает нам возможность определить тип friend=user+peer. Когда мы определяем таблицу для SIP user и peer, нам необходимо как минимум следующее:
+———+————+———-+————+——-+——————+—————+
|name |host |secret | ipaddr | port| regseconds | username |
+———+————+———-+————+——-+——————+—————+
|100 |dynamic |welcome| | |1096954152 | 1000 |
+———+————+———-+————+——-+——————+—————+

Столбцы port, regseconds и ipaddr необходимы для сообщения Астериску хранить регистрационную информацию для пиров с той целью, чтобы указавать, куда отправлять исходящий звонок. Так можно сделать при условии, что хост указан динамически (пр. перевод.: host=dynamic), однако, если пир указан статически, мы должны сообщить ipaddr самостоятельно в нашем поле. Поле port опционально, и может использовать стандартный порт, указанный в секции [general] и regseconds оставлять пустым. Существует множество опций для SIP friend, которые мы можем определить, например CallerID. Чтобы это сделать, просто необходимо добавить эту информацию callerid в колонку таблицы. См. sip.conf.sample файл для всех опций, которые вы можите определить для SIP friends.

Хранение Call Detail Records

Call Detail Records (далее CDR) хранит информацию о звонках, обработанных Астериском. Они (прим перевод.: записи) будут рассмотрены в главе 13. Это очень распространенный способ использовать CDR в Астериске, так как CDR'ами можно легко управлять, если мы храним записи в БД (например, нам необходимо отслеживать состояния нескольких систем на базе Астериска в одной таблице).

Установка опции systemname для Globally Unique ID (Глобалных уникальных ID, прим. Перевод.)

CDR содержит уникальный идентификатор и несколько полей информации о звонке (включая информацию о канале-источнике, канале-назначения звонка, длительность звонка последнее выполненное приложение и так далее). В случае кластерной архитектуры Астерисков, теоретически, может возникнуть ситуация дублирования уникальных идентификаторов (UID), так как каждый сервер Астериска имеет собственную систему идентификации звонков. Адресуя их, мы можем автоматически прикреплять системный идентификатор к началу UID, используя опцию в /etc/asterisk/asterisk.conf. Для каждого сервера Астериск установите уникальный идентификатор системы, например так:
[options]
systemname=toronto


Теперь, создадим таблицу в нашей БД для хранения CDR. Зарегистрируемся на сервере PostgreSQL посредством запуска приложения psql:
# psql -U asterisk -h localhost asterisk
Password:

Далее, создадим таблицу asterisk_cdr:
asterisk⇒ CREATE TABLE asterisk_cdr
(
id bigserial NOT NULL,
calldate timestamptz,
clid varchar(80),
src varchar(80),
dst varchar(80),
dcontext varchar(80),
channel varchar(80),
dstchannel varchar(80),
lastapp varchar(80),
lastdata varchar(80),
duration int8,
billsec int8,
disposition varchar(45),
amaflags int8,
accountcode varchar(20),
uniqueid varchar(40),
userfield varchar(255),
CONSTRAINT asterisk_cdr_id_pk PRIMARY KEY (id)
)
WITHOUT OIDS;

Мы можем проверить, что таблица создана, используя следующую команду \dt (описания таблиц):
asterisk⇒ \dt asterisk_cdr
List of relations
Schema | Name | Type | Owner
————+———————+———-+—————
public | asterisk_cdr | table | asterisk
(1 row)

Теперь сконфигурируем Астериск для хранения собственных CDR в БД. Это выполняется в в /etc/asterisk/cdr_odbc.conf с помощью следующей записи:
[global]
dsn=asterisk-connector
username=asterisk
password=welcome
loguniqueid=yes
table=asterisk_cdr

Если Астериск уже запущен, с *CLI> выполните следующее:
*CLI> module reload cdr_odbc.so

Мы можем также перегрузить все конфигурационный настройки Астериска с помощью:
*CLI> reload

Проверьте статус CDR, путем ввода следующей команды поиска зарегистрированных CDR точек входа:
*CLI> cdr status
CDR logging: enabled
CDR mode: simple
CDR registered backend: cdr-custom
CDR registered backend: cdr_manager
CDR registered backend: ODBC

Сейчас позвоните с помощью Астериска и убедитесь, что данные о звонке появились в таблице asterisk_cdr. Самый простой способ протестировать звонок — это инициировать его с CLI Астериска:

*CLI> console dial (убедитесь в то, что вы имеете звуковую карту и проинсталлированный драйвер chan_oss). Таким образом, вы можете проверить люьой метод с помощью вызова с консоли:
*CLI> console dial 100@default
— Executing [100@default:1] Playback("OSS/dsp", "tt-weasels") in new stack
— <OSS/dsp> Playing 'tt-weasels' (language 'en')

Далее, соединитесь с БД и выполните запрос SELECT, чтобы удостоверится в том, что
Для этого выполните запрос SELECT * FROM asterisk _cdr; но таким образом мы получим немного больше данных:
# psql -U asterisk -h localhost asterisk
Password:
asterisk⇒ SELECT id,dst,channel,uniqueid,calldate FROM asterisk_cdr;
id | dst | channel | uniqueid | calldate
——+——-+————-+———————————+————————————
1 | 100 | OSS/dsp | toronto-1171611019.0 | 2007-02-16 !02:30:19-05
(1 rows)

Удивительная функция func_odbc: Hot-Desking

Функция func_odbc — наиболее мощное средство из всех функций доступных в номерном плане Астериска. Функция позволяет создавать и использовать простые функции номерного плана, которые отыскивают и используют информацию с БД прямо в номерном плане. Существует несколько вариантов использования, например, управление пользователями или делать доступной динамическую информацию внутри Астерисков, заведенных в кластер. Что func_odbc позволяет Вам выполнить — определяется SQL-запросами, которым вы назначаете имя функций. По сути, вы создаете пользовательские функции, которые возвращают результат выполнения запроса назад в БД. Файл func_odbc.conf содержит связь между созданными Вами именами функций и SQL-конструкциями, которые будут выполнены по факту вызова ново-спеченных функций. Посредством именных ссылок в номерном плане мы можем найти и обновить значение в БД.

Примечание! Используя внешние скрипты для взаимодействия с БД (с их помощью создается файл, который потом будет прочитан Астериском) имеет преимущество (если БД была записана, Ваша система продолжает функционировать и скрипт не обновит не один файл до тех пор, пока не будет восстановлена возможность связи с БД). Главный недостаток в том, что любое изменение, сделанное пользователем не доступно до тех пор, пока не будет запущен скрипт. Возможно, что этот факт не совсем важен в маленьких системах, но в больших промышленных решениях, ожидание вступления в силу изменений может быть причиной, например, во время паузы при осуществлении звонку, в то время как загружается и проверяется большой файл. Вы можете решить это с помощью репликаций БД. Начиная с версии 1.4. Астериск (и до текущей) синтаксис файла /etc/asterisk/func_odbc.conf изменился незначительно, но получена возможность преодоления отказа (отказоустойчивость) и перераспределением ролей основного и дублирующего узлов по отношению к другой БД, используя модель master-master (pgcluster; Slony-II), или модель master-slave (Slony-I) систем репликации.

Чтобы дать Вам правильную картинку о том, что мы сказали, мы хотим Вам показать картинку Dagwood sandwich* (прим. Перевод.: http://en.wikipedia.org/wiki/Dagwood_sandwich).

* Dagwood sandwich — огромный, многослойный сэндвич, сделанный с огромного количества мяса, сыра и специй. Он был назван в честь Dagwood Bumstead, вымышленного персонажа, популярного в комиксах, который собственно и делал очень часто огромные сэндвичи. В сегодняшнем понимании Dagwood sandwich означает пережиток прошлого, который включает большое количество вареного мяса, ломтиков сыра с добавлением кусочков хлеба.

Можете ли вы отвлечься от житейского опыта, например от демонстрации на картинке томатов, или от ломтика сыра? Не тяжело? Этот загадочный пример (прим. Перевод.: действительно очень загадочный!!! :) ) демонстрирует попытку дать Вам практичный пример того, на сколько функция fucn_odbc мощная. Таким образом, мы решили построить полный сэндвич для Вас. Это только часть пирога, но после, нескольких укусов, арахис лучше чем желе и никогда не будет тем же самым (прим. Перевод.: ну америкоса понесло….)

Например, мы хотим что-то реализовать такое, что для нас будет полезным. Продемонстрируем небольшую компанию, которая состоит из 5 менеджеров по продажам. Они делят между собой два стола. Это страшно неудобно, потому как их семья расходует большинство времени на дорогу. Сами же менеджеры в неделю находятся от силы один день в офисе. Тем не менее, когда они работают в офисе, система знает, какой рабочий стол они занимают, таким образом, их звонки могут быть перенаправлены непосредственно туда. Таким образом, руководитель может иметь возможность отслеживать, когда они находятся в офисе и регулировать привилегии осуществления вызовов с их телефонов, когда никого там (прим. Перевод.: в офисе) нет.

Это решается типичным способом, что мы называем hot-desking возможность. Это можно построить, как будет показано ниже, с помощью вызова мощной функции func_odbc.
Создадим два настольных телефона в sip.conf:
; sip.conf
; HOT DESK USERS
[desk_1]
type=friend
host=dynamic
secret=my_special_secret
context=hotdesk
qualify=yes

[desk_2]
type=friend
host=dynamic
secret=my_special_secret
context=hotdesk
qualify=yes
; END HOT DESK USERS (конец описания hot-desktop пользователей)

Оба телефона входят в контекст [hotdesk] номерного плана (extensions.conf). Если мы действительно хотим создать эти два телефона, нам необходимо настроить эти параметры на соответствующих телефонах, об этом шла речь в главе 4.

Это все, что необходимо для sip.conf. Мы получили два куска хлеба. Но это едва ли можно назвать сэндвичем.

Теперь давайте настроим сторону БД (мы договорились, что создали ODBC БД, как было оговорено выше в этой главе). Первое, что на необходимо — соединится с БД в консоле:
# su - postgres
$ psql -U asterisk -h localhost asterisk

Password:

Далее, создадим таблицу с таким кодом:
CREATE TABLE ast_hotdesk
(
id serial NOT NULL,
extension int8,
first_name text,
last_name text,
cid_name text,
cid_number varchar(10),
pin int4,
context text,
status bool DEFAULT false,
"location" text,
CONSTRAINT ast_hotdesk_id_pk PRIMARY KEY (id)
)
WITHOUT OIDS;


Таким образом, мы заполнили БД информацией (некоторые значения, которые мы видим, на самом деле могут быть изменены только после того, как отработан номерной план, что имеет место в нашем примере). С консоли сервера PostgreSQL, запустим следующие команды:
asterisk⇒ INSERT INTO ast_hotdesk ('extension', 'first_name', 'last_name', 'cid_name',
'cid_number', 'pin', 'context', 'location') \
VALUES (1101, 'Leif', 'Madsen', 'Leif Madsen', '4165551101', '555', 'longdistance',
'desk_1');

Повторите предыдущею строку и измените значение VALUES для всех записей, которые вы хотите иметь в БД. Вы можете просмотреть данные в ast_hotdesk таблице, путем запуска запроса SELECT с консоли сервера PostgreSQL:
asterisk⇒ SELECT * FROM ast_hostdesk;

Вывод SQL-запроса будет похож на это:
| id | extension | first_name | last_name | cid_name | cid_number | pin
+—————-+——————+————————+————————-+——————-+——
| 1 | 1101 | "Leif" | "Madsen" | "Leif Madsen" | "4165551101" | "555"
| 2 | 1102 | "Jim" | "Van Meggelen" | "Jim Van Meggelen" | "4165551102" | "556"
| 3 | 1103 | "Jared" | "Smith" | "Jared Smith" | "4165551103" | "557"
| 4 | 1104 | "Mark" | "Spencer" | "Mark Spencer" | "4165551104" | "558"
| 5 | 1105 | "Kevin" | "Fleming" | "Kevin Fleming" | "4165551105" | "559"

| context | status | location |$
+————————-+————-+—————+
| "longdistance" | "TRUE" | "desk_1" |
| "longdistance" | "FALSE" | "" |
| "local" | "FALSE" | "" |
| "international" | "FALSE" | "" |
| "local" | "FALSE" | "" |

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

Примечание! До того, как Вы будете печатать это на своем сервере, возьмите на заметку тот факт, что все примеры написаны в Приложении H. Таким образом Мы поддержали Вас следовать так долго этим примерам, Вы также можете увидеть цельную картину, проверив Приложение (и воспользоваться копипастом, если вы читаете электронную версию книги).

В каком-нибудь месте номерного плана (extensions.conf) мы создадим контекст [hotdesk]. Для начала, определим маску экстеншена, который позволит пользователю войти под своей учетной записью:
; extensions.conf
; Hot Desking Feature
[hotdesk]
; Hot Desk Login
exten ⇒ _110[1-5],1,NoOp()
exten ⇒ _110[1-5],n,Set(E=${EXTEN})
exten ⇒ _110[1-5],n,Verbose(1|Hot Desk Extension ${E} is changing status)
exten ⇒ _110[1-5],n,Verbose(1|Checking current status of extension ${E})
exten ⇒ _110[1-5],n,Set(${E}_STATUS=${HOTDESK_INFO(status,${E})})
exten ⇒ _110[1-5],n,Set(${E}_PIN=${HOTDESK_INFO(pin,${E})})

Мы еще не написали этот экстеншен, но сделаем паузу и увидим на сколько мы далеко продвинулись.

Когда агент садится за стол, он логинится (log-in) посредством набора своего номера. В нашем случае, мы разрешили номера с 1100 до 1105 с помощью маски _110[1-5]. Можно создать менее ограниченную маску, например 11XX (позволяет обрабатывать номера с 1100 до 1199). Как бы там ни было, эти экстеншены используют func_odbc для выполнения поиска с HOTDESK_INFO() функции номерного плана, которую мы вскоре создадим. Эта пользовательская функция (которую мы определим в func_odbc.conf файле) выполняет SQL конструкцию и возвращает что-либо, извлекая информацию с БД.

Теперь определим новую функцию HOTDESK_INFO() в func_odbc.conf так:
[INFO]
prefix=HOTDESK
dsn=asterisk
read=SELECT ${ARG1} FROM ast_hotdesk WHERE extension = '${ARG2}'

Таким образом, мы заполнили несколько строк. Давайте быстро рассмотрим их, пока мы не двинулись дальше.
В первую очередь, опция prefix. Если вы не cконфигурировали prefix, Астериск добавит в имя функции «ODBC (в нашем случае INFO)», что означает, что функция станет ODBC_INFO(). Это не совсем наглядно, что делает функция, таким образом может быть полезно указывать prefix, что поможет установить связь с Вашей ODBC функцией, задавая ей смысловую нагрузку с учетом того, что она выполняет. В нашем случае, мы выбрали HOTDESK, что означает, что пользовательская должна быть именована как HOTDESK_INFO().

Dsn атрибут сообщает Астериску, что соединение используется с res_odbc.conf. Так как несколько соединений баз данных могут быть сконфигурированны в res_odbc.conf, мы определили какая из них используется здесь. На рисунке 12-1, мы показали отношение между различными конфигурационными файлами и как они связана в цепочку соединения с БД.

Мы, после этого, определим нашу SQL-конструкцию с атрибутом read. Функции номерного плана имеют два различных формата, таким образом, могут быть вызваны либо для восстановления информации, либо для ее установления. Атрибут read используется в том случае, если мы вызываем HOTDESK_INFO() функцию в формате восстановления (и мы можем выполнить отдельно отдельно SQL- конструкцию с атрибутом write; мы обсудим формат для write атрибута немного позже в этой главе).
Считывание значения этой функции можно с помощью формата, подобно тому, как это сделано в номерном плане:
exten ⇒ s,n,Set(RETURNED_VALUE=${HOTDESK_INFO(status,1101)})

Это выражение возвращает значение, размещенное в БД в колонке status, где номер колонке эквивалентен 1101. Status и 1101 мы передали в функцию HOTDESK_INFO(), а после разместили в SQL конструкцию с атрибутом read, доступны как аргумент ${ARG1} и ${ARG2}, в соответствующем порядке. Если нам необходимо передать еще третью опцию, это можно сделать с помощью аргумента ${ARG3}.

Сдесь должен быть рисунок
Рис.12-1 Взаимодействие между func_odbc.conf, res_щdbc.conf, /etc/odbc.ini (unixODBC) и соединением с БД

Примечание! Убедитесь в том, что данные уникальные, таким образом, Вы получите только один ряд назад. С PostgreSQL, Вы можете добавить LIMIT 1 в конец Вашего SQL-конструкции для того, чтобы поставить ограничения на возвращаемый ряд, но это не совсем хорошая практика. Сделав несколько дополнений в этой секции, мы можем увидеть, как использовать LIMIT и OFFSET функции PostgreSQL для закольцовывания через множество рядов данных.

После выполнения SQL-конструкции, возвращенное значение помещается в канальную переменную RETURNED_VALUE.

Использование функции ARRAY()

В нашем примере мы использовали две отдельные БД звонков и поместили их значение в пару канальных переменных, (${E}_STATUS и ${E}_PIN). Это можно просто выполнить так:
exten ⇒ _110[1-5],n,Set(${E}_STATUS=${HOTDESK_INFO(status,${E})})
exten ⇒ _110[1-5],n,Set(${E}_PIN=${HOTDESK_INFO(pin,${E})})

Как альтернативный способ, мы можем вернуть несколько колонок в несколько переменных, используя массив ARRAY() — функцию номерного плана. Если мы предопределили SQL-конструкцию в файле func_odbc.conf так:
read=SELECT pin,status FROM ast_hotdesk WHERE extension = '${E}'

мы можем использовать функцию ARRAY() для хранения каждой колонки информации на ряду с переменными с помощью одного обращения к БД:
exten ⇒ _110[1-5],n,Set(ARRAY(${E}_PIN,${E}_STATUS)=${HOTDESK_INFO(${E})})

Таким образом, в первые линии нашего блока кода мы передали значение status и значение, содержащееся в переменной ${E}, (например 1101) в функцию HOTDESK_INFO(). После, два эти значения перенесены в SQL-конструкцию с ${ARG1} и ${ARG2}, соответственно, после выполнения SQL-конструкции, возвращаемое значение назначили в канальную переменную ${E}_STATUS.
Теперь давайте закончим написание маски:
exten ⇒ _110[1-5],n,Set(${E}_STATUS=${HOTDESK_INFO(status,${E})})
exten ⇒ _110[1-5],n,Set(${E}_PIN=${HOTDESK_INFO(pin,${E})})
exten ⇒ _110[1-5],n,GotoIf($[${ISNULL(${${E}_STATUS})}]?invalid_user,1)
; check if ${E}_STATUS is NULL — проверка переменной на предмет нулевого значения
exten ⇒ _110[1-5],n,GotoIf($[${${E}_STATUS} = 1]?logout,!1:login,1)

После, значение переменной, находящейся в колонке status, помещается в канальную переменную ${E}_STATUS (если вы набираете 1101, имя переменной может быть 1101_STATUS), мы проверяем если мы получили значение назад с БД (проверка на ошибку).
Самая последняя строка в блоке проверяет cтатус телефона и, если в в состоянии «зарегистрирован», переводит в состояние «не зарегистрирован». Если еще не зарегистрирован, эта строка в блоке кода переводит в экстеншен login с приоритетом 1 в том же контексте.

Примечание! Начиная с версии Астериска 1.4 (и до текущей), Вы можете использовать канальную переменную ${ODBCROWS}, выполняя конструкцию через readsql. Мы можем перенести GotoIf() так:
exten ⇒ _110[1-5],n,GotoIf($[${ODBCROWS} < 0]?invalid_user,1)

Экстеншен login запускает инициализацию проверки ввода пин-кода агентом. Мы позволим агенту сделать три попытки ввода пин-кода и, если он введен неверно, мы отсылаем звонок в экстеншен login_fail (который мы напишем позже):
exten ⇒ login,1,NoOp() ; set counter initial value
exten ⇒ login,n,Set(PIN_TRIES=0) ; set max number of login attempts
exten ⇒ login,n,Set(MAX_PIN_TRIES=3)
exten ⇒ login,n(get_pin),NoOp() ; increase pin try counter
exten ⇒ login,n,Set(PIN_TRIES=$[${PIN_TRIES} + 1])
exten ⇒ login,n,Read(PIN_ENTERED|enter-password|${LEN(${${E}_PIN})})
exten ⇒ login,n,GotoIf($[${PIN_ENTERED} = ${${E}_PIN}]?valid_login,1)
exten ⇒ login,n,Playback(invalid-pin)
exten ⇒ login,n,GotoIf($[${PIN_TRIES} ⇐${MAX_PIN_TRIES}]?get_pin:login_fail,1)

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

Если введенный пин-код принят, мы подтверждаем вход с помощью экстеншена valid_login. Сначала мы использовали канальную переменную CHANNEL, чтобы вычислить какой телефон сейчас осуществляет вызов. Обычно канальная переменная CHANNEL содержит запись, подобную этой — SIP/desk_1-ab4034c, таким образом мы можем использовать функцию cut() к первой части SIP-строки и определить расположение LOCATION. После того, как мы обрезали -ab4034c. Избавьтесь от нее и назначьте тому, что осталось от усечения — переменной LOCATION.
exten ⇒ valid_login,1,NoOp()
; CUT off the channel technology and assign to the LOCATION variable
; вырезаем канальный идентификатор и помещаем его значение в переменную LOCATION
exten ⇒ valid_login,n,Set(LOCATION=${CUT(CHANNEL,/,2)})
; CUT off the unique identifier and save the remainder to the LOCATION variable
; вырезмаем уникальный идентификатор и оставшееся помещаем в переменную LOCATION
exten ⇒ valid_login,n,Set(LOCATION=${CUT(LOCATION,-,1)})

Мы еще не использовали пользовательскую функцию HOTDESK_CHECK_PHONE_LOGINS(), созданную в файле func_odbc.conf и проверяет любого пользователя на предмет регистрации на телефоне и если он забыл выйти со своей учетной записи (log out). Если номер зарегистрированного пользователя был больше ноля (а это должен быть 1, но мы так или иначе проверили и перегрузили это тоже). Это запущено в логике экстеншена logout_login.

Если ни один из агентов не был зарегистрирован, мы обновили статус регистрации для этого пользователя с помощью функции HOTDESK_STATUS():
exten ⇒ valid_login,n,Set(ARRAY(USERS_LOGGED_IN)=${HOTDESK_CHECK_PHONE_
LOGINS(${LOCATION})})
exten ⇒ valid_login,n,GotoIf($[${USERS_LOGGED_IN} > 0]?logout_login,1)
exten ⇒ valid_login,n(set_login_status),NoOp()
; Set the status for the phone to '1' and where we're logged into
; Устанавливаем статус для телефона в значение «1»
и где мы зарегистрировались
; NOTE: we need to escape the comma here because the Set() application has arguments
; Заметьте, что нам необходимо здесь избегать запятой, потому как приложение Set() имее аргументы
exten ⇒ valid_login,n,Set(HOTDESK_STATUS(${E})=1\,${LOCATION})
exten ⇒ valid_login,n,GotoIf($[${ODBCROWS} < 1]?error,1)
exten ⇒ valid_login,n,Playback(agent-loginok)
exten ⇒ valid_login,n,Hangup()

В файле func_odbc.conf напишем следующее:
[STATUS]
prefix=HOTDESK
dsn=asterisk
write=UPDATE ast_hotdesk SET status = '${VAL1}', location = '${VAL2}' WHERE extension
= '${ARG1}'

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

Первое, на что необходимо обратить внимание, что мы используем обе переменные ${VALx} и ${ARGx} в нашей SQL-конструкции. Эти переменные содержать значение, которые мы передаем в функцию с номерного плана. В нашем случае, мы имеем дело с двумя переменными и одним аргументом ARG, которую была установлена с номерного плана посредством конструкции:
Set(HOTDESK_STATUS(${E})=1\,${LOCATION})

Примечание! В связи с тем, что приложение номерного плана Set() также может иметь аргументы (Вы можете установить множество переменных со значением, разделяя их запятой или «трубой»

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

Следует отметить, что синтаксис незначительно отличается от того, в котором мы привыкли читать. Это сообщает Астериску тот факт, что Вы хотите выполнить запись (такой же синтаксис, как и в синтаксис других функций номерного плана).

Мы передаем значение переменной ${E} в функцию HOTDESK_STATUS(), значение которой после доступно для SQL-конструкции, содержащуюся в func_odbc.conf с переменной ${ARG1}. Мы после этого передаем два значения:1 и ${LOCATION}. Это становится доступным для SQL-конструкции ${VAL1} и ${VAL2} переменных, соответственно.

Как было уже сказано ранее, если мы имели log out для одного или нескольких агентов то того как они log in, мы должны проверить их с помощью символьного экстеншена logout_login. Такая номерная логи используется в приложением While() в номерном плане, чтобы зациклить БД и выполнить какую-либо коррекцию в БД, что может случиться. Вероятней всего выполнится только один цикл, но это хороши пример того, как можно обновить или проверить синтаксис нескольких строк записи в БД:
exten ⇒ logout_login,1,NoOp()
; set all logged in users on this device to logged out status
${USERS_LOGGED_IN}
exten ⇒ logout_login,n,Set(ROW_COUNTER=0)
exten ⇒ logout_login,n,While($[${ROW_COUNTER} < $ {USERS_LOGGED_IN}])

Переменная ${USERS_LOGGED_IN} была предварительно установлена с помощью функции HOTDESK_CHECK_PHONE_LOGINS()
, которой присвоено значение 1 или больше. Мы сделали это путем подсчета количества строк, которые были затронуты:
; func_odbc.conf
[CHECK_PHONE_LOGINS]
prefix=HOTDESK
dsn=asterisk
read=SELECT COUNT(status) FROM ast_hotdesk WHERE status = '1' AND location = '${ARG1}'

Мы после этого получили номер экстеншена пользователя, который зарегистрирован с помощью функции HOTDESK_LOGGED_IN_USER(). Переменная LOCATION заполнена значением desk_1, которая говорит нам, с каким устройством мы хотим начать работу. Переменная ${ROW_COUNTER} содержит количество проходов цикла. Обе переменные переданы в функцию номерного плана в качестве переменных. Результат работы помещается в переменную WHO:
exten ⇒ logout_login,n,Set(WHO=${HOTDESK_LOGGED_IN_USER(${LOCATION},${ROW_COUNTER})})

Функция HOTDESK_LOGGED_IN_USER(), которая вытаскивает ряд строк из БД, которые, таким образом, подобно итерациям (повторам) цикла мы применяли к процессу.
[LOGGED_IN_USER]
prefix=HOTDESK
dsn=asterisk
read=SELECT extension FROM ast_hotdesk WHERE status = '1'
AND location = '${ARG1}' ORDER BY id LIMIT '1' OFFSET '${ARG2}'

Теперь мы знаем какой экстеншен мы хотим обновить. Мы запишем функцию HOTDESK_STATUS() и запишем 0 в колонку status, где номер экстеншена содержит значение в переменной ${WHO}, например 1101 После мы закончим цикл с помощью функции EndWhile() и возвращаемся назад в valid_login экстеншен с меткой приоритета set_login_status (как было обсуждено ранее):
exten ⇒ logout_login,n,Set(HOTDESK_STATUS(${WHO})=0) ; logout phone
exten ⇒ logout_login,n,Set(ROW_COUNTER=$[${ROW_COUNTER} + 1])
exten ⇒ logout_login,n,EndWhile()
exten ⇒ logout_login,n,Goto(valid_login,set_login_status) ; return to logging in

Один момент для Вас может быть непонятен, а именно: как использовать канальную переменную ${ODBCROWS}, которая устанавливается с помощью функции HOTDESK_STATUS(). Собственно, это переменная сообщает нам, сколько строк было обновлено в запросе SQL UPDATE, которой мы присвоили 1 (Пр. Перевод.: присвоили значение переменной ${ODBCROWS}). Если значение ${ODBCROWS} меньше 1, тогда мы имеем дело с ошибкой и обрабатываем соответственно:

exten ⇒ logout,1,NoOp()
exten ⇒ logout,n,Set(HOTDESK_STATUS(${E})=0)
exten ⇒ logout,n,GotoIf($[${ODBCROWS} < 1]?error,1)
exten ⇒ logout,n,Playback(silence/1&agent-loggedoff)
exten ⇒ logout,n,Hangup()
exten ⇒ login_fail,1,NoOp()
exten ⇒ login_fail,n,Playback(silence/1&login-fail)
exten ⇒ login_fail,n,Hangup()


exten ⇒ error,1,NoOp()
exten ⇒ error,n,Playback(silence/1&connection-failed)
exten ⇒ error,n,Hangup()
exten ⇒ invalid_user,1,NoOp()
exten ⇒ invalid_user,n,Verbose(1|Hot Desk extension ${E} does not exist)
exten ⇒ invalid_user,n,Playback(silence/2&invalid)
exten ⇒ invalid_user,n,Hangup()

Мы также включим контекст [hotdesk_outbound], который обрабатывает наши исходящие звонки после того, как агенты вошли в систему:
include ⇒ hotdesk_outbound

Собственно контекст [hotdesk_outbound] объединяет все набранные номера со стороны настольных телефонов. Мы первоначально установим нашу LOCATION переменную, используя переменную CHANNEL, после, определим какой номер (агент) вошел в систему и поместим это в переменную WHO. В том случае, если переменная NULL, исходящий звонок будет сброшен. В противном случае. Мы можем получить информацию об агенте с помощью функции HOTDESK_INFO() и присвоить ее нескольким CHANNEL переменным. Это включить в контекст обработки звонков, где мы выполним Goto() в контекст который ассоциирован с исходящими звонками.

Если мы попытаемся выполнить набор номера, который не обрабатывается нашим контекстом (или один из транзитных контекстов, например, звонок в межгород, международный звонок и пр.), создадим экстеншен i, который будет проигрывать PlayBack сообщение в случае, если действие (набранный номер) не может быть обработан и как результат — завершение звонка.
[hotdesk_outbound]
exten ⇒ _X.,1,NoOp()
exten ⇒ _X.,n,Set(LOCATION=${CUT(CHANNEL,/,2)})
exten ⇒ _X.,n,Set(LOCATION=${CUT(LOCATION,-,1)})
exten ⇒ _X.,n,Set(WHO=${HOTDESK_PHONE_STATUS(${LOCATION})})
exten ⇒ _X.,n,GotoIf($[${ISNULL(${WHO})}]?no_outgoing,1)
exten ⇒ _X.,n,Set(${WHO}_CID_NAME=${HOTDESK_INFO(cid_name,${WHO})})
exten ⇒ _X.,n,Set(${WHO}_CID_NUMBER=${HOTDESK_INFO(cid_number,${WHO})})
exten ⇒ _X.,n,Set(${WHO}_CONTEXT=${HOTDESK_INFO(context,${WHO})})
exten ⇒ _X.,n,Goto(${${WHO}_CONTEXT},${EXTEN},1)
[international] ; международные звонки, например
exten ⇒ _011.,1,NoOp()
exten ⇒ _011.,n,Set(E=${EXTEN})
exten ⇒ _011.,n,Goto(outgoing,call,1)
exten ⇒ i,1,NoOp()
exten ⇒ i,n,Playback(silence/2&sorry-cant-let-you-do-that2)
exten ⇒ i,n,Hangup()
include ⇒ longdistance
[longdistance]; межгород, например
exten ⇒ _1NXXNXXXXXX,1,NoOp()
exten ⇒ _1NXXNXXXXXX,n,Set(E=${EXTEN})
exten ⇒ _1NXXNXXXXXX,n,Goto(outgoing,call,1)
exten ⇒ _NXXNXXXXXX,1,Goto(1${EXTEN},1)
exten ⇒ i,1,NoOp()
exten ⇒ i,n,Playback(silence/2&sorry-cant-let-you-do-that2)
exten ⇒ i,n,Hangup()
include ⇒ local
[local] ; по городу, например
exten ⇒ _416NXXXXXX,1,NoOp()
exten ⇒ _416NXXXXXX,n,Set(E=${EXTEN})
exten ⇒ _416NXXXXXX,n,Goto(outgoing,call,1)
exten ⇒ i,1,NoOp(); обработка номера, который не попал не под одну из вышеперечисленных (контекстах) масок (или шаблонов обработки набранного номера)
exten ⇒ i,n,Playback(silence/2&sorry-cant-let-you-do-that2)
exten ⇒ i,n,Hangup()

Если набранный номер удовлетворяет требования хотя бы один из контекстов обработки звонка (за исключением i (invalid) контекста), звонок маршрутизируется в контекст [outgoing] обработки исходящих звонков, где Caller ID имя и номер устанавливаются с помощью CALLERID() функции. Звонки далее размещаются в SIP канал, используя учетную запись service_provider в sip.conf файле:
[outgoing]
exten ⇒ call,1,NoOp()
exten ⇒ call,n,Set(CALLERID(name)=${${WHO}_CID_NAME})
exten ⇒ call,n,Set(CALLERID(number)=${${WHO}_CID_NUMBER})
exten ⇒ call,n,Dial(SIP/service_provider/${E})
exten ⇒ call,n,Playback(silence/2&pls-try-call-later)
exten ⇒ call,n,Hangup()

Наш service_provider может выглядеть так:
[service_provider]
type=friend
host=switch1.service_provider.net
username=my_username
fromuser=my_username
secret=welcome
context=incoming
canreinvite=no
disallow=all
allow=ulaw

И это все!!! Все вышеописанное можно узреть в Приложении G

Сколько вещей можно сделать, используя func_odbc ? Смотрите обо всем ниже.



















Attachments

  • AFOT.doc Download (195.5 KB) - added by litnimax 3 years ago. Завершение главы 12 (Андрей поленился освоить WikiFormatting)