Сервис автоматизированной отправки факсов с помощью Asterisk

в 10:18, , рубрики: asterisk, Elastix, fax, service, телефония, метки: , , ,

Имеем некий web-сервис, которому необходимо отправлять очень много факсов.
Можно использовать сторонние сервисы, которые специализируются на этом.
Но если факсов много, то это выливается в нехилую такую копеечку. Поэтому мы будем создавать свой сервис.
Используя свой сервис, мы будем платить повременно за голосовой трафик.
Сервис будет получать запрос на отправку факса и рапортовать нам о результатах.

Использовать будем Asterisk, может он не самый производительный, но хорошо известный.
В нашей конфигурации мы будем использовать готовую сборку Elastix, т.к. она работает стабильнее, если верить Klistrod ( Битва титанов FreeSwitch vs. Asterisk — Тест производительности).
Так же плюсом является наличие Apache и php. Доставлять пакеты не придется.

В Asterisk есть несколько способов отправки факсов: голосом через G.711 и с использованием T.38.
Второй вариант предпочтительнее, т.к. вероятность доставки выше.

Так же есть 2 реализации отправки факсов:
1. Spandsp OpenSource проект
2. Digium Fax for Asterisk
Коммерческая реализация от авторов астериска, 1 конкурентная лицензия бесплатная.
Подробности установки( docs.digium.com/FAX/fax_for_asterisk_admin_manual.pdf )
Spandsp уже включена в поставку Elastix.

Логически систему можно разбить на 3 части.
1.Прием запросов
2.Ротация факсов
3.Отправка факса и рапорт о результатах.

Прием запросов на отправку.

Инициация отправки факса будет происходить посредством перемещения .call файла в папку выполения астериска.
В call файле находятся все необходимые Asteriskу параметры для отправки.

faxsend.call

Callerid:"FaxSender"<1111>
Maxretries:maxRetries
Waittime:300
Context:faxsend-t38
Extension:faxout
RetryTime:50
Priority:1
SetVar: T38CALL=1
Set:RETURNURL={returnUrl}
Set:TAGLINE=Fax from CompanyName
Set:RECEIVER=Number Of Receiver
Set:FAX_ID={faxId}
Set:TIFF_2_SEND={faxId}.tif';

Создаем скрипт который будет принимать параметры и файл для отправки. Файл для отправки будем получать в PDF.
Параметры на которые, думаю стоит обратить внимание:
maxRetries — количество попыток дозвона.
faxid – идентификатор факса для которого будет возвращаться статус.
returnUrl – адрес по которому будет возвращаться результат отправки.

index.php

<?php
define( "STATUS_SUCCESS", "success" );
define( "STATUS_ERROR", "fail" );
$pdfLocation = "//var//tmp//faxes//";
function sendStatus( $statusKind )
{
        header('Content-type: application/json');
        exit( "{"status": "" . $statusKind . ""}" );
}

$fax = $_REQUEST['fax'];
$faxId = $_REQUEST['faxId'];
$maxRetries = isset( $_REQUEST['maxRetries'] ) ? $_REQUEST['maxRetries']
: 0;
$returnUrl = $_REQUEST['returnUrl'];
$pdf = $faxId . '.pdf';

if ($fax == '' || $returnUrl == '' || $faxId == ''
)
{
        sendStatus( STATUS_ERROR );
}

if(!move_uploaded_file($_FILES['file']['tmp_name'], $pdfLocation .
$pdf))
{
        sendStatus( STATUS_ERROR );
}

$callFileBody = 'Channel:SIP/trunkname/{fax}
Callerid:"FaxSender"<1111>
Maxretries:{maxRetries}
Waittime:300
Context:faxsend-t38
Extension:faxout
RetryTime:50
Priority:1
SetVar: T38CALL=1
Set:RETURNURL={returnUrl}
Set:TAGLINE=Fax from Company
Set:RECEIVER={fax}
Set:FAX_ID={faxId}
Set:TIFF_2_SEND={faxId}.tif';

$callFileBody = str_replace("{fax}", $fax, $callFileBody);
$callFileBody = str_replace("{faxId}", $faxId, $callFileBody);
$callFileBody = str_replace("{maxRetries}", $maxRetries, $callFileBody);
$callFileBody = str_replace("{returnUrl}", $returnUrl, $callFileBody);

$callFilename = $faxId . ".call";

file_put_contents($pdfLocation . $callFilename, $callFileBody);

if (!file_exists($pdfLocation . $callFilename))
{
        sendStatus( STATUS_ERROR );
}

sendStatus( STATUS_SUCCESS );

?>

Таким образом в указанной папке pdfLocation появляется 2 файла: .call и .pdf.
Данный скрипт можно поместить в какую-нибудь из подпапок в /var/www/html
Я создал папку faxservice.
Таким образом наш сервис доступен по адресу http://serveradress/faxservice
По-умолчанию Elastix перенаправляет все запросы http на https. Что бы не заморачиваться с сертефикатами я этот редирект отключил:

/etc/httpd/conf.d/elastix.conf

# Apache-level configuration for Elastix administration interface
Timeout 300
# Default apache configuration specifies greater limits than these
#MaxClients       150
#MaxRequestsPerChild  1000
# Default apache User and Group diretives MUST be commented out
# in order for these to take effect.
User asterisk
Group asterisk
<Directory "/var/www/html">
    # Redirect administration interface to https
    #RewriteEngine off
    #RewriteCond %{HTTPS} off
    #RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
</Directory>

Просто закомментировал реврайты.

Ротация факсов

Нам необходим мониторинг этой папки на предмет появления новых заданий, и после появления заданий, передачи в очередь на отправку в Asterisk. Так же необходимо конвертация нашего pdf в tif
Данный функционал мы реализуем с помощью bash скрипта:

faxrotate

#!/bin/bash
SOURCEDIR="/var/tmp/faxes/"
for file in `ls $SOURCEDIR*.pdf `;
do
        callfile="${file/pdf/call}"
        if [ ! -e "$callfile" ]
        then
               continue
        fi 
        `gs -q -dNOPAUSE -dBATCH -sDEVICE=tiffg4 -sPAPERSIZE=letter -sOutputFile=${file/pdf/tif} $file`
        mv ${file/pdf/call} /var/spool/asterisk/outgoing
        rm $file
done

Подвешиваем выполнение данного скрипта каждую минуту от имени Asterisk.

«Отработанные» файлы tif будем удалять каждую ночь, хранить их будем 2 дня (на тот случай, если количество попыток будет большим, а промежутки между ними еще больше.):

/usr/bin/find /var/tmp/faxes -name "*tif" -mtime +2 –delete
Отправка факсов Asteriskом

Осталось теперь только научить Asterisk отправлять факсы через T.38 и рапортовать нам о результатах.
Для начала необходимо «научить» Elastix работать с T.38

Для этого в sip_general_custom.conf добавляем:

t38pt_udptl=yes

А в настройках транспорта не забываем добавить

transport=udp,udptl

Примерный вид настроек транка:

[trunkname]
username=username
type=friend
transport=udp,udptl
secret=password
qualify=yes
nat=yes
insecure=port,invite
host=voip.host.com
disalow=all
directmedia=yes
context=from-pstn
canreinvite=yes
allow=ulaw&alaw

Далее необходимо создать контекст, который будет вызываться из call файла
Его добавляем в

extensions_custom.conf

[faxsend-t38]
exten => faxout,1,Set(STARTTIME=${SHELL(date +%s)} )
exten => faxout,n,Wait(1)
exten => faxout,n,Playback(fax24,skip)
exten => faxout,n,Wait(1)
exten => faxout,n,NoOp(**** SENDING FAX ****)
; Set FAXOPTs
exten => faxout,n,NoOp(**** SETTING FAXOPT ****)
exten => faxout,n,Set(FAXFILE=${TIFF_2_SEND})
exten => faxout,n,Set(FAXOPT(ecm)=yes)
exten => faxout,n,Set(FAXOPT(headerinfo)=${TAGLINE})
exten => faxout,n,Set(FAXOPT(maxrate)=14400)
exten => faxout,n,Set(FAXOPT(minrate)=4800)
; Send the fax
exten => faxout,n,NoOp(**** SENDING FAX : ${FAXFILE} ****)
exten => faxout,n,SendFAX(/var/tmp/faxes/${FAXFILE},dfzs)

;Calculating Time of Sending
exten => faxout,n,Set(ENDTIME=${SHELL(date +%s)} )
exten => faxout,n,Set(TRANSFERTIME=${MATH(${ENDTIME}-${STARTTIME},int)})
;Actions after sending fax
exten => faxout,n,Set(NORMURL=${FAXOPT(error)})
exten => faxout,n,Set(STATUSMESSAGE=${REPLACE(NORMURL, ,+)})
exten => faxout,n,Set(FAXOPTRATE=${FAXOPT(rate)})
exten => faxout,n,Hangup

;Actions if no answer or busy
exten => failed,1,Set(FAXSTATUS=FAILED)
exten => failed,2,Set(STATUSMESSAGE=number+no+answer+or+busy)
exten => failed,3,Set(FAXOPTRATE=none)

exten => h,1,NoOP(------------------- FAX to ${EXTEN} with ${FAXSTATUS} -----------------)
exten => h,2,Set(CURLRESULT=${CURL(${RETURNURL}?fax=${RECEIVER}&faxId=${FAX_ID}&status=${FAXSTATUS}&message=${STATUSMESSAGE})})
exten => h,4,Set(LOGFAXOUT=${SHELL(echo "${STRFTIME(${EPOCH},,%d%m%Y-%H:%M:%S)} : ${FAX_ID} : ${RECEIVER} : ${FAXSTATUS} : ${STATUSMESSAGE} : ${TRANSFERTIME}s : ${FAXOPTRATE}" >> /var/log/asterisk/faxout.log)})
exten => h,3,NoOp(${RECEIVER}:${FAX_ID}:${FAXSTATUS}:${STATUSMESSAGE}:${FAXOPTRATE})

Данный контекст пытается дозвониться и отправить факс, при недозвоне он возвращает number+no+answer+or+busy.
При удачном дозвоне и попытке отправки возвращает статус и расшифровку статусного сообщения.
Эту информацию он возвращает по адресу RETURNURL из php скрипта.
Так же он пишет лог файл для дальнейших разборов полетов по каким-то спорным вопросам.
Для работы голосового приветствия необходимо записать файл в формате PCM Encoded, 16 Bits, at 8000Hz и поместить его в /var/lib/asterisk/sounds.

Автор: volhonsky

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js