Elastix и chan_datacard

150px-Isotipo-elastix Когда стоит задача поднять asterisk и мы знаем о том что будут использоваться USB-3G-модемы для голоса, это не проблема, достаточно поставить его на дистрибютиве с новым ядром и потом можно прикрутить панель типа FreePBX или asterisk-gui. Но что делать когда уже стоит готовая сборка Elastix, с кучей настроек и в продакшене, а прикрутить chan_datacard просто необходимо. В этой статье я опишу всю процедуру от начала и до конца для Elastix 2.3.

Первая проблема это старое ядро 2.6.18 в Elastix, а chan_datacard обещает работать нормально с ядром не меньше 2.6.33. Можно конечно взять исходники необходимого ядра и скомпилировать его, но учитывая то, что Elastix был установлен на дохленький Atom и предвидя 2-3 часа компиляции, я отказался от этого варианта (мы ведь помним, АТС в продакшене, и простой должен быть минимальным) и пошел другим путем, установив новое ядро из добавленного репозитария.

Добавляем репозитарий ELRepo:

# rpm --import http://elrepo.org/RPM-GPG-KEY-elrepo.org
# rpm -Uvh http://elrepo.org/elrepo-release-5-4.el5.elrepo.noarch.rpm

проверяем в /etc/yum.repos.d/elrepo.repo в разделе [elrepo-kernel] — enabled=1

Актуальное ядро в этом репозитории на момен написания этой статьи 3.0.88, нам вполне подходит.
Устанавливаем необходимые пакеты и новое ядро:

# yum install gcc glibc-devel glibc-headers subversion
# yum install --skip-broken kernel-lt kernel-lt-devel kernel-lt-headers

Я добавил —skip-broken потому что ругалось на kernel-lt-headers.

меняем в /boot/grub/menu.lst:

default=1
на
default=0

видим что у нас появилось там новое ядро:

title CentOS (3.0.88-1.el5.elrepo)
root (hd0,0)
kernel /vmlinuz-3.0.88-1.el5.elrepo ro root=LABEL=/
initrd /initrd-3.0.88-1.el5.elrepo.img

обновляем grub — подставляем свой диск вместо /dev/sda:

# grub-install /dev/sda

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

необходимо скачать dahdi отсюда — на момент написания актуальная версия 2.7:

# cd /usr/src
# wget http://downloads.asterisk.org/pub/telephony/dahdi-linux-complete/dahdi-linux-complete-current.tar.gz
# tar -xzvf dahdi-linux-complete-current.tar.gz
# ln -s dahdi-linux-complete-2.7.0+2.7.0 dahdi

так же нам понадобиться поддержка oslec для DAHDI(модуль эхо-подавления), скачиваем:

# svn co http://svn.astfin.org/software/oslec/trunk/ oslec

далее переходим к подготовке DAHDI:

# mkdir -p /usr/src/dahdi/linux/drivers/staging/echo
# cp -fR /usr/src/oslec/kernel-test/* /usr/src/dahdi/linux/drivers/staging/echo/
# cp -fR /usr/src/kernels/3.0.88-1.el5.elrepo-x86_64/drivers/staging/echo/* /usr/src/dahdi/linux/drivers/staging/echo/

Скачиваем исходники chan_datacard

# svn co http://www.makhutov.org/svn/chan_datacard/trunk/ /usr/src/chan_datacard

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

сразу после перезагрузки надо скомпилировать DAHDI:

# cd /usr/src/dahdi
# make
# make install
# make config

# /etc/init.d/asterisk stop
# /etc/init.d/dahdi restart
# /etc/init.d/asterisk start

все должно пройти без проблем, смотрим:

# asterisk -vvvvr
CLI> dahdi show channels

получаем примерно такое:
dahdi_image1-png

На данный момент мы поставили новое ядро и драйвера DAHDI, то есть вернули систему в рабочее состояние но с новым ядром. Теперь необходимо установить chan_datacard:

# cd /usr/src/chan_datacard
# ./configure
# make
# make install
# cp etc/datacard.conf /etc/asterisk/

на 64-битной системе установка не произошла, потому как была попытка произвести установку в /usr/lib/asterisk/modules , а такого пути нет, копируем модуль ручками:

# cp chan_datacard.so /usr/lib64/asterisk/modules/

Я использовал модем huawei E1750 не привязанный к оператору, девственный как слеза 🙂 Стоит упомянуть, что перед использование модем необходимо переключить в режим «только модем», отправив ему команду AT^U2DIAG=0, через minicom например.

Переходим к настройке chan_datacard:
в файле /etc/asterisk/datacard.conf комментируем или удаляем все после строки:

;-----------------------------------------------------------------------------------

и вставляем:

[datacard1]
context=from-gsm ; context для входящих звонков
audio=/dev/ttyUSB1 ; tty порт для аудио подключения
data=/dev/ttyUSB2 ; tty порт для управляющих AT комманд модема
group=4 ; Группа вызова
rxgain=6 ; Изменение громкости динамика
txgain=6 ; Изменение громкости микрофона
resetdatacard=yes ; Перезагрузка модема при перезапуске модуля
autodeletesms=yes ; Удаление смс с симкарты при перезапуске
usecallingpres=yes ; use the caller ID presentation or not
callingpres=allowed_passed_screen ; set caller ID presentation

сохраняем и закрываем файл.

переходим к редактированию файла /etc/asterisk/extensions_custom.conf , добавляем в конец файла:

[from-gsm]
exten => s,1,goto(from-trunk,${IMEI},1)
exten => sms,1,Verbose(Incoming SMS from ${CALLERID(num)} ${SMS})
exten => sms,n,System(echo '${STRFTIME(${EPOCH},,%Y-%m-%d %H:%M:%S)} -%- ${DATACARD} -%- ${CALLERID(num)} -%- ${SMS} -%-' >> /var/log/asterisk/sms.txt)
exten => sms,n,Hangup()
exten => ussd,1,Verbose(Incoming USSD: ${USSD})
exten => ussd,n,System(echo '${STRFTIME(${EPOCH},,%Y-%m-%d %H:%M:%S)} -%- ${DATACARD} -%- USSD -%- ${USSD} -%-' >> /var/log/asterisk/ussd.txt)
exten => ussd,n,Hangup()

Этот context будет принимать звонки и подставлять IMEI модема вместо DID, так что мы запросто сможем создавать правила входящих звонков прямо в панели управления Elastix. Так же будут приниматься смс и ussd и складываться в файлы /var/log/asterisk/sms.txt и /var/log/asterisk/ussd.txt соответственно. Позже мы будем обрабатывать данные файлы и посылать письма с пришедшими sms и ussd.

Осталось перезапустить asterisk:

# /etc/init.d/asterisk restart

заходим в консоль asterisk-а и проверяем:

# asterisk -vvvr
CLI> datacard show device datacard1

получим примерно следующее, не торопитесь, карта должна успеть инициализироваться 1-2 минуты:
dahdi_image2-png

Осталось прикрутить исходящие звонки, для этого заходим в панель Elastix-а , добавляем Custom Trunk:
dahdi_image3-png

И добавляем в Outbound Routes новое правило для звонков:
dahdi_image4-png

Теперь при выходе через 8, мы будем звонить через наш 3G-модем.

для настройки входящих звонков, делаем правило, используя в качестве DID IMEI-номер модема, и принимаем звонки по своему желанию, на номер, группу или очередь.

Следующим этапом будет настройка отправки пришедших смс и ussd на почту. Здесь я воспользовался скриптом отсюда, чуть изменив.
Создаем файл /var/www/html/sms.php следующего содержания — не забудьте изменить адрес отправки:

<?php
// путь до файла с смс
$file_name = '/var/log/asterisk/sms.txt';
// если размер файла больше 0 переходим к его обрботке
if (filesize($file_name)>0) {
// открываем файл для чтения и записи
$fobj = fopen($file_name,"r+");
$text = fread($fobj, filesize($file_name));
// в файл смс записываются в следуюшем формате
// дата и время -%- datacard -%- номер отправителя -%- текст смс
// формат и разделитель можно сменить в конфиг файле
// преобразовываем содержимое в массив используя -%- как разделитель
$text1 = explode("-%-", $text);
// делим массив а части по 4 элемента
$array = array_chunk($text1, 4);
// рисуем таблицу в которой будут выводится наши смс
$mes1 = "<table border='1' bordercolor='#000000' cellspacing='0' cellpadding='2'>";
$mes2 = "<tr align='center' valign='middle
'><td width='160'>Дата и время сообщения</td><td width='140'>Номер отправителя</td><td width='230'>Сообщение</td></tr>";
for ($i = 0; $i <= count($array)-2; $i++)
{
$sms = $array[$i];
$sms1[] = "<tr valign='middle
'><td align='center'>".$sms[0]."</td><td align='center'>".$sms[2]."</td><td align='left'>".$sms[3]."</td></tr>";
}
for ($mes3 = '', $j = 0; $j <= count($sms1); $j++)
{
$mes3 = $mes3.$sms1[$j];
}
$mes4 = "</table>";
// Теперь перейдем к отправке
// отправлять будем используя встроенную функцию mail()
// получаем текущую дату
$date = date("d.m.Y");
// получаем текущее время
$time = date("H:i:s");
// адрес куда будем отправлять письмо
$to = "your@email.com";
// тема письма
$subject = "New SMS Message(s) - ".$date." - ".$time;
// текст письма
$message = $mes1.$mes2.$mes3.$mes4;
// дополнительные заголовки письма
// кодировка письма
$headers = "Content-type: text/html; charset=utf-8 \r\n";
// отправитель письма
$headers .= "From: SMS Gate <sms@your-domain.org>\r\n";
// отправляем письмо, если отправка прошла успешно
// выводим сообщение иочищаем файл с смс
if (mail($to, $subject, $message, $headers)) {
echo "Soobshenie ojidaet otpravki";
ftruncate ($fobj, 0);
} else {
// если ошибка выводим это сообщение
echo "Oshibka pri otpravke";
}
// закрываем файл
fclose($file_name);
}
// если файл пустой выводим это сообщение
else { echo "Sms soobshenii net..."; }
?>

ставим задание в cron, каждые 5 минут делать проверку:

*/5 * * * * /usr/bin/wget --no-check-certificate -O /dev/null -q https://192.168.10.190/sms.php

Посылаем sms на вставленный в модем номер и ждем пока нам придем сообщение на почту. Всё должно пройти успешно.

Аналогичный файл создаем для ussd, меняя тему письма, например. Теперь Вы можете полноценно использовать свой номер в модеме, звонить и принимать звонки, принимать смс. Отправлять смс можно командой в панели Elastix-а или в asterisk-cli, либо написать свой фротн-энд для этого, но текст должен содержать только латиницу. Входящие же sms могут быть и на кириллице.

Отправить sms или ussd можно следующим образом, из asterisk-cli или панели Elastix-а (PBX->Tools->Asterisk-Cli)

datacard ussd datacard1 *100#
datacard sms datacard1 +79261001001 Test hello

После пары дней тестов, я заметил, что один раз модем завис, посему наваял простенький скрипт для проверки регистрации модема и сброса его в случае её отсутствия, создаем файл /root/bin/test-restart-dc со следующим содержимым:

#!/bin/bash

dclogfile=/root/bin/dc.log
dcdate=`/bin/date "+%Y-%m-%d %H:%M:%S"`
dcstatus=`/usr/sbin/asterisk -rx "datacard show device datacard1"|grep "GSM Registration Status"|grep "Registered"`
RES1=$?

if [ $RES1 -ne 0 ];then
	/usr/sbin/asterisk -rx "datacard reset datacard1"
	echo "Reset "$dcdate >>$dclogfile
	echo $dcdate" -%- datacard1 -%- RESET -%- Datacard Resetted -%-" >> /var/log/asterisk/ussd.txt
	exit 1
else
        echo "OK "$dcdate >>$dclogfile
        exit 0
fi

выставляем ему права на выполнение chmod a+x /root/bin/test-restart-dc ,и ставим в крон, проверку каждый час:

3 * * * */root/bin/test-restart-dc

В случае, если скрип отработает reset карты, Вам придет письмо на почту, для этого просто добавляется строка в файл /var/log/asterisk/ussd.txt , который мы обрабатываем выше. Так же результаты проверки пишутся в лог — /root/bin/dc.log , с указанием даты и времени.

Вот и всё!

Удачи в настройках.
© shadow_alone

  • FRFRFRF

    i translated ur topic to english and applied it on my server , it works like a charm 🙂
    thank you from egypt:)

  • andrey0003

    U R welcome!

  • jess

    hi andery ,it causes kernel panic when detecting digium cards !!
    did you test it using sangoma or digium cards in your server ??

  • Andrej

    Добрый день. Нуждаюсь в помоще по привязке двух USB-3G-модемов к Elastix 2.4, Сообщите пожалуйста сколько будет стоить Ваша работа. Заранее благодарен. Андрей. 95123 @ tut.by

  • Axeffekt

    Подскажите, в чем ошибка?

    [root@localhost chan_datacard]# svn co http://www.makhutov.org/svn/chan_datacard/trunk/ /usr/src/chan_datacard

    Checked out revision 192.

    [root@localhost chan_datacard]# ./configure

    configure complete, now type ‘make’

    [root@localhost chan_datacard]# make

    gcc -Wall -Wextra -fPIC -DAST_MODULE=»chan_datacard» -D_THREAD_SAFE -I. -I/usr/include -O2 -DICONV_CONST=»» -D__DEBUG__ -D__APP__ -c chan_datacard.c

    In file included from chan_datacard.c:69:

    chan_datacard.h:257: предупреждение: initialization from incompatible pointer type

    In file included from chan_datacard.c:78:

    __app.c: In function ‘app_status_exec’:

    __app.c:17: предупреждение: пропущен инициализатор

    __app.c:17: предупреждение: (где-то рядом с инициализацией для ‘args.device’)

    __app.c: In function ‘app_send_sms_exec’:

    __app.c:71: предупреждение: пропущен инициализатор

    __app.c:71: предупреждение: (где-то рядом с инициализацией для ‘args.device’)

    In file included from chan_datacard.c:85:

    __channel.c: In function ‘channel_new’:

    __channel.c:18: предупреждение: implicit declaration of function ‘ast_channel_linkedid’

    __channel.c:18: предупреждение: несоответствие типов указатель/целое в условном выражении

    __channel.c:22: предупреждение: implicit declaration of function ‘ast_channel_tech_pvt_set’

    __channel.c:23: предупреждение: implicit declaration of function ‘ast_channel_tech_set’

    __channel.c:24: предупреждение: implicit declaration of function ‘ast_format_set’

    __channel.c:24: предупреждение: implicit declaration of function ‘ast_channel_readformat’

    __channel.c:25: предупреждение: implicit declaration of function ‘ast_channel_writeformat’

    __channel.c:26: предупреждение: implicit declaration of function ‘ast_format_cap_add’

    __channel.c:26: предупреждение: implicit declaration of function ‘ast_channel_nativeformats’

    __channel.c:30: предупреждение: implicit declaration of function ‘ast_channel_rings_set’

    __channel.c:50: предупреждение: implicit declaration of function ‘ast_channel_language_set’

    __channel.c: In function ‘channel_request’:

    __channel.c:95: предупреждение: implicit declaration of function ‘ast_format_cap_iscompatible’

    __channel.c:98: предупреждение: passing argument 3 of ‘ast_getformatname_multiple’ makes integer from pointer without a cast

    __channel.c: In function ‘channel_call’:

    __channel.c:360: предупреждение: implicit declaration of function ‘ast_channel_tech_pvt’

    __channel.c:360: предупреждение: initialization makes pointer from integer without a cast

    __channel.c:381: предупреждение: implicit declaration of function ‘ast_channel_state’

    __channel.c:383: предупреждение: implicit declaration of function ‘ast_channel_name’

    __channel.c:383: предупреждение: format ‘%s’ expects type ‘char *’, but argument 6 has type ‘int’

    __channel.c:396: предупреждение: format ‘%s’ expects type ‘char *’, but argument 8 has type ‘int’

    __channel.c:402: предупреждение: implicit declaration of function ‘ast_channel_connected’

    __channel.c:402: ошибка: неверный аргумент для ‘->’

    __channel.c: In function ‘channel_hangup’:

    __channel.c:438: предупреждение: initialization makes pointer from integer without a cast

    __channel.c: In function ‘channel_answer’:

    __channel.c:482: предупреждение: initialization makes pointer from integer without a cast

    __channel.c: In function ‘channel_digit_begin’:

    __channel.c:504: предупреждение: initialization makes pointer from integer without a cast

    __channel.c: In function ‘channel_read’:

    __channel.c:530: предупреждение: initialization makes pointer from integer without a cast

    __channel.c:544: предупреждение: implicit declaration of function ‘ast_channel_fdno’

    __channel.c:554: предупреждение: implicit declaration of function ‘ast_format_copy’

    __channel.c:554: ошибка: ‘union ast_frame_subclass’ has no member named ‘format’

    __channel.c: In function ‘channel_write’:

    __channel.c:654: предупреждение: initialization makes pointer from integer without a cast

    __channel.c:658: ошибка: ‘union ast_frame_subclass’ has no member named ‘format’

    __channel.c:660: предупреждение: format ‘%s’ expects type ‘char *’, but argument 7 has type ‘int’

    __channel.c: In function ‘channel_fixup’:

    __channel.c:741: предупреждение: initialization makes pointer from integer without a cast

    __channel.c: In function ‘channel_indicate’:

    __channel.c:792: предупреждение: initialization makes pointer from integer without a cast

    __channel.c: In function ‘channel_queue_hangup’:

    __channel.c:879: предупреждение: implicit declaration of function ‘ast_channel_hangupcause_set’

    chan_datacard.c: In function ‘unload_module’:

    chan_datacard.c:621: предупреждение: implicit declaration of function ‘ast_format_cap_destroy’

    chan_datacard.c: In function ‘load_module’:

    chan_datacard.c:685: предупреждение: implicit declaration of function ‘ast_format_cap_alloc’

    chan_datacard.c:690: предупреждение: assignment makes pointer from integer without a cast

    make: *** [chan_datacard.o] Ошибка 1

  • andrey0003

    Возможно ядро 3.2, поэтому дает такую ошибку.

  • aledinkin

    Добрый день!
    Получилось у Вас собрать таки модуль chan_datacard? Столкнулся с такой же проблемой что и ваша…