среда, 12 ноября 2014 г.

Получаем русский CallerID из MySQL в Elastix 2.4 (FreePBX 2.8.1)

В дистрибутив Elastix (на базе FreePBX) включена очень полезная функция, позволяющая получить имя для любого занесённого в базу данных номера и высветить это имя при входящем внешнем вызове на дисплей телефона. В данной статье я расскажу о частном случае применения данной функции.
Имеется n-ное количество IP-телефонов, поддерживающих utf-8 (это обязательное условие для возможности отображения русских CallerID) и база данных MySQL, которая содержит телефонную книгу. Требуется все это подружить с Elastix.

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

Создаём БД
CREATE DATABASE `phonebook` CHARACTER SET utf8 COLLATE utf8_general_ci;

Таблица "Имена" (names)
CREATE TABLE IF NOT EXISTS `names` (
  `name_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`name_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 ;

Таблица "Телефоны" (phones)
CREATE TABLE IF NOT EXISTS `phones` (
  `phone_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `number` varchar(64) NOT NULL,
  `name_id` bigint(20) NOT NULL,
  PRIMARY KEY (`phone_id`),
  KEY `name_id` (`name_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 ;

Естественно, что БД надо сначала как-то заполнить для проведения тестирования.
Пример заполнения:

INSERT INTO `names` (`name_id`, `name`) VALUES (NULL, 'И.И.Иванов');
INSERT INTO `phones` (`phone_id`, `number`, `name_id`) VALUES (NULL, '79161234567', 1);

В поле name_id таблицы phones указываем полученное автоинкрементное значение из поля name_id таблицы names (в моём случае это 1). Номер телефона указываем реальный - тот, с которого будете тестировать.

Теперь идём в PBX -> PBX Configuration -> CallerID Lookup Sources.
Добавляем новый источник. Назовём его mysql-directory.
Указываем следующие параметры:

  • Хост: IP-адрес сервера, где запущена MySQL (или localhost, если MySQL работает локально)
  • База данных: Имя базы данных. В нашем случае phonebook
  • Запрос: SELECT CONVERT(n.name USING utf8) FROM names as n left join phones as p using (name_id) WHERE p.number = '[NUMBER]'
  • Имя пользователя: пользователь, имеющий права минимум на чтение таблиц в БД
  • Пароль: пароль этого самого пользователя
Сохраняем и применяем изменения.
Далее, нужно назначить наш новый источник поиска CallerID на один или несколько входящих маршрутов (DID'ов).
Для этого идём в Inbound Routes и выбираем там DID на котором требуется функционал отображения CallerID. В группе CallerID Lookup Source в параметре Source указываем наш новый источник mysql-directory, сохраняем и применяем изменения.

После всех этих манипуляций, по идее, всё должно заработать. Но не тут-то было. По какой-то причине FreePBX упорно не воспринимает utf8 даже в том случае, если поле таблицы, откуда мы получаем CallerID хранит данные в utf8, что приводит к высвечиванию кучи вопросительных знаков на дисплее телефона и в консоли:
   -- Executing [0007@from-trunk:3] Gosub("SIP/Cisco7206VXR-000005b8", "cidlookup,cidlookup_2,1") in new stack
    -- Executing [cidlookup_2@cidlookup:1] MYSQL("SIP/Cisco7206VXR-000005b8", "Connect connid xxx.xxx.xxx.xxx имя_пользователя_БД пароль_пользователя_БД phonebook") in new stack
    -- Executing [cidlookup_2@cidlookup:2] MYSQL("SIP/Cisco7206VXR-000005b8", "Query resultid 1 SELECT n.name FROM names as n left join phones as p using (name_id) WHERE p.number = '79161234567'") in new stack
    -- Executing [cidlookup_2@cidlookup:3] MYSQL("SIP/Cisco7206VXR-000005b8", "Fetch fetchid 2 CALLERID(name)") in new stack
    -- Executing [cidlookup_2@cidlookup:4] MYSQL("SIP/Cisco7206VXR-000005b8", "Clear 2") in new stack
    -- Executing [cidlookup_2@cidlookup:5] MYSQL("SIP/Cisco7206VXR-000005b8", "Disconnect 1") in new stack
..................
-- Executing [s@macro-user-callerid:21] NoOp("SIP/Cisco7206VXR-000005b8", "Using CallerID "?.?.???????" <79161234567>") in new stack
Решается это вроде бы просто. Нужно в контекст cidlookup в файле extensions_additional.conf добавить: 
exten => cidlookup_2,n,MYSQL(Query resultid ${connid} SET NAMES utf8)
Но это не выход, т.к. при перестроении конфигурации FreePBX затрёт изменения.
Модификация extensions_custom.conf путём добавления контекста [cidlookup-custom] также не помогла.

Выход был найден не совсем стандартный, но вполне рабочий. Это модификация кода FreePBX.
Сначала идём в /var/www/html/admin/modules/cidlookup и делаем резервную копию functions.inc.php
Далее редактируем functions.inc.php:
  • Примерно в районе строки 189 найдите такой текст $ext->add('cidlookup', 'cidlookup_'.$item['cidlookup_id'], '', new ext_mysql_connect('connid', $item['mysql_host'],  $item['mysql_username'],  $item['mysql_password'],  $item['mysql_dbname']));
  • После этой строки вставляем следующее: $ext->add('cidlookup', 'cidlookup_'.$item['cidlookup_id'], '', new ext_mysql_query('resultid', 'connid', 'SET NAMES utf8'));
  • Сохраняем файл и перестраиваем конфигурацию FreePBX (меняем какой-то малозначительный параметр туда-обратно и делаем Apply Configuration Changes)
Проверяем:
    -- Executing [0007@from-trunk:3] Gosub("SIP/Cisco7206VXR-00000696", "cidlookup,cidlookup_2,1") in new stack
    -- Executing [cidlookup_2@cidlookup:1] MYSQL("SIP/Cisco7206VXR-00000696", "Connect connid xxx.xxx.xxx.xxx имя_пользователя_БД пароль_пользователя_БД phonebook") in new stack
    -- Executing [cidlookup_2@cidlookup:2] MYSQL("SIP/Cisco7206VXR-00000696", "Query resultid 1 SET NAMES utf8") in new stack
    -- Executing [cidlookup_2@cidlookup:3] MYSQL("SIP/Cisco7206VXR-00000696", "Query resultid 1 SELECT CONVERT(n.name USING utf8) FROM names as n left join phones as p using (name_id) WHERE p.number = '79161234567'") in new stack
    -- Executing [cidlookup_2@cidlookup:4] MYSQL("SIP/Cisco7206VXR-00000696", "Fetch fetchid 2 CALLERID(name)") in new stack
    -- Executing [cidlookup_2@cidlookup:5] MYSQL("SIP/Cisco7206VXR-00000696", "Clear 2") in new stack
    -- Executing [cidlookup_2@cidlookup:6] MYSQL("SIP/Cisco7206VXR-00000696", "Disconnect 1") in new stack
..................
 -- Executing [s@macro-user-callerid:21] NoOp("SIP/Cisco7206VXR-00000696", "Using CallerID "И.И.Иванов" <79161234567>") in new stack
Видим результат работы добавленной строки кода. Самая последняя строка вывода консоли говорит нам о том, что всё отображается как надо.

Что мы сделали. Мы заставили FreePBX стандартно генерировать нужный нам кусок конфига в его специфические файлы конфигурации. Этот метод будет железно работать до тех пор, пока вы не установите обновление модуля CID Lookup. После этого все изменения в коде придётся повторить заново.





4 комментария:

  1. Этот комментарий был удален автором.

    ОтветитьУдалить
    Ответы
    1. Спасибо! Всё отлично!
      Единственно что:
      -я не замарачивался с такой структурой базы данных. Всёранвно в ручную я её заполнять не буду, а обработка в 1С будет сама заполнять таблицу... (правда обработка ещё не написана);
      - я запрос использовал такой: SELECT name FROM callerid WHERE number LIKE CONCAT ('%',SUBSTRING('[NUMBER]',-7),'%') LIMIT 1 который позволит исключить неточности заполнения контактной информации в базе 1С и, как следствие, в разнобой написанные номера телефонов (где-то 7 знаков как обычный местный, городской, где-то с кодом города (к примеру мобильный) а где-то менеджер прикольнулся и вписал с кодом страны)... а [NUMBER] передаётся всегда с кодом страны (а если 3G модем то и с "+")... поэтому я решил из передаваемого номера брать только последние 7 цифр. Ошибка определения - почти минимальная.
      К тому же можно вписывать несколько номеров телефонов в одну строку в столбце.

      Удалить
    2. Благодарю за отзыв! На самом деле структура БД (и соответственно запросов) здесь больше для целостности решения. Конечно же каждый сделает её свою под собственные нужды. Я тут описал лишь саму идею.

      Удалить
  2. Добрый день.
    Меня очень заинтересовала информация, которую Вы доступно описали в данной статье. Я не так давно стал интересоваться Asterisk-ом, и своими силами в небольшой организации запустил данную систему. Не могу назвать себя специалистом в данной теме, поэтому когда приходится задействовать очередной функционал (в избытке имеющийся в Asteriske), у меня очень много времени уходит на то, чтобы он заработал. Благо есть много доступной информации по многим вопросам из этой темы. Часто делая что-то по инструкции, в итоге я всё равно сталкиваюсь с тем, что запустить это "что-то" так и не получается. Как и в случае с моим желанием запустить в работающей системе подстановку имени вместо входящего номера извне, которое было внесено в адресную книгу Asterisk. Каким способом можно отследить проблемное место - почему не хочет работать эта подстановка?
    Во всех статьях прописаны 3 основных пункта, после выполнения которых данный функционал должен заработать.

    1. Создать записи в адресной книге Asterisk.
    2. В CID Lookup Soursec - создание источника с типом Internal.
    3. Указать этот источник в настройках входящей маршрутизации.

    Попробвал я так же и описанный Вами вариант с MySql.

    1. Создал базу, таблицы и внёс в них данные.
    2. В CID Lookup Soursec - создал новый источник с типом MySql и внёс данные во все поля.
    3. Указал этот источник в настройках входящей маршрутизации.
    Всё равно не работает.

    В CID Superfecta проверил на тестируемом номере работу определения по адресной книге - показала правильный результат. Но в реальной работе не функционирует.
    Что не так? Куда смотреть, чтобы понять причину?
    Если весь основной функционал работает - звонки внутри сети, звонки во внешний мир, приём звонков извне, фиксируются входящие и исходящие номера - значит ли это, что связка шлюз-Астериск работает исправно, и что искать причину неисправности определения номеров в ней нет смысла?

    Систему устанавливал из готового образа AsteriskNow, настройки через веб-интерфейс freepbx. На входе 1 gsm-voip шлюз (Goip 1).

    ОтветитьУдалить